diff --git a/.gitignore b/.gitignore index 2a55979b..c4cad8a5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ docs/agile-readme.md .claude/ .superpowers/ docs/superpowers/ +.mcp.json # Docker dev environment ha-config/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 26ff60fb..6a32e8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,12 +15,18 @@ All notable changes to this project will be documented in this file. - By Activity: circuits sorted by power consumption with expandable graphs and search filtering - By Area: circuits grouped by Home Assistant area with live area registry updates - Shared tab bar across panel and card with configurable text/icon style +- **Cross-panel Favorites view** (span-card 0.9.4) — A synthetic "Favorites" entry in the dashboard panel dropdown aggregates favorited circuits and sub-devices + (BESS, EVSE) across every configured SPAN panel into a single workspace. Heart toggles in the Graph Settings and per-circuit / per-sub-device side panels + persist favorites and the view to the integration storage so the Favorites view is reconstituted on restart. See the Favorites explanation in the frontend + dashboard link via the README.md. ### Fixed - **Dashboard goes blank after idle** — Panel and card migrated to LitElement and refresh after losing focus (span-card 0.9.1) - **Dashboard graph fidelity** — Circuit charts now use step interpolation instead of linear, eliminating misleading diagonal ramps between data points. Continuous signals (PV solar output, BESS SoC/SoE) retain linear interpolation to faithfully represent their gradual behavior. +- **Panel status showing "Connected" while the panel is offline** — the panel status sensor now reflects the true connection state and updates within a second + of the panel going offline or coming back online (including the bump to span-panel-api v2.6.1) ## [2.0.5] - 4/2026 diff --git a/custom_components/span_panel/__init__.py b/custom_components/span_panel/__init__.py index cc89ff70..dba10167 100644 --- a/custom_components/span_panel/__init__.py +++ b/custom_components/span_panel/__init__.py @@ -27,7 +27,7 @@ from span_panel_api.mqtt.models import MqttClientConfig # Import config flow to ensure it's registered -from . import config_flow # noqa: F401 # type: ignore[misc] +from . import config_flow # noqa: F401 from .const import ( CONF_API_VERSION, CONF_EBUS_BROKER_HOST, @@ -45,7 +45,7 @@ PANEL_FRONTEND_DIR as PANEL_FRONTEND_DIR, PANEL_URL as PANEL_URL, _async_ensure_lovelace_resource as _async_ensure_lovelace_resource, - async_apply_panel_registration, + async_apply_panel_registration as async_apply_panel_registration, async_load_panel_settings as async_load_panel_settings, async_save_panel_settings as async_save_panel_settings, ) @@ -53,6 +53,7 @@ from .migrations import CURRENT_CONFIG_VERSION, async_migrate_entry # noqa: F401 from .options import SNAPSHOT_UPDATE_INTERVAL from .services import ( # noqa: F401 + _async_register_favorites_services, _async_register_graph_horizon_services, _async_register_monitoring_services, _async_register_services, @@ -93,6 +94,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _async_register_services(hass) _async_register_monitoring_services(hass) _async_register_graph_horizon_services(hass) + _async_register_favorites_services(hass) await async_apply_panel_registration(hass) @@ -246,15 +248,25 @@ async def async_setup_entry(hass: HomeAssistant, entry: SpanPanelConfigEntry) -> async def async_unload_entry(hass: HomeAssistant, entry: SpanPanelConfigEntry) -> bool: - """Unload a config entry.""" + """Unload a config entry. + + Unload the platforms first; only tear the coordinator down if that + succeeded. If a platform raises during unload, HA retries with the + coordinator still alive — shutting it down first would leave + entities pointing at a closed MQTT client. + """ _LOGGER.debug("Unloading SPAN Panel integration") + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + if not unload_ok: + return False + if hasattr(entry, "runtime_data") and entry.runtime_data is not None: if entry.runtime_data.coordinator.current_monitor is not None: entry.runtime_data.coordinator.current_monitor.async_stop() await entry.runtime_data.coordinator.async_shutdown() - return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + return True async def async_remove_config_entry_device( diff --git a/custom_components/span_panel/alert_dispatcher.py b/custom_components/span_panel/alert_dispatcher.py index 3cf69175..c49deb78 100644 --- a/custom_components/span_panel/alert_dispatcher.py +++ b/custom_components/span_panel/alert_dispatcher.py @@ -164,7 +164,8 @@ def dispatch_alert( over_threshold_since: str | None = None, ) -> None: """Dispatch alert through all enabled notification channels.""" - local_time = dt_util.now().strftime("%-I:%M %p") + # `%-I` is glibc-only; strip any leading zero ourselves for portability. + local_time = dt_util.now().strftime("%I:%M %p").lstrip("0") event_data: dict[str, Any] = { "alert_source": alert_source, @@ -249,7 +250,8 @@ def dispatch_test_alert( utilization_pct = 91.5 panel_serial = "TEST" window_duration_s = 300 - local_time = dt_util.now().strftime("%-I:%M %p") + # `%-I` is glibc-only; strip any leading zero ourselves for portability. + local_time = dt_util.now().strftime("%I:%M %p").lstrip("0") raw_targets = settings.get(NOTIFY_TARGETS, "") if isinstance(raw_targets, str): diff --git a/custom_components/span_panel/binary_sensor.py b/custom_components/span_panel/binary_sensor.py index ddf3f245..25944d57 100644 --- a/custom_components/span_panel/binary_sensor.py +++ b/custom_components/span_panel/binary_sensor.py @@ -13,7 +13,7 @@ ) from homeassistant.core import HomeAssistant from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.entity import EntityCategory # type: ignore[attr-defined] +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from span_panel_api import SpanEvseSnapshot, SpanPanelSnapshot diff --git a/custom_components/span_panel/config_flow.py b/custom_components/span_panel/config_flow.py index f8dc2ef1..75e149fd 100644 --- a/custom_components/span_panel/config_flow.py +++ b/custom_components/span_panel/config_flow.py @@ -10,7 +10,6 @@ from homeassistant import config_entries from homeassistant.config_entries import ( - ConfigEntry, ConfigFlowContext, ConfigFlowResult, ) @@ -634,7 +633,7 @@ def create_new_entry( def _update_v2_entry(self, entry_id: str) -> ConfigFlowResult: """Update an existing config entry with new v2 MQTT credentials.""" - entry: ConfigEntry[Any] | None = self.hass.config_entries.async_get_entry(entry_id) + entry: SpanPanelConfigEntry | None = self.hass.config_entries.async_get_entry(entry_id) if entry is None: _LOGGER.error("Config entry %s does not exist during v2 reauth", entry_id) return self.async_abort(reason="reauth_failed") diff --git a/custom_components/span_panel/config_flow_options.py b/custom_components/span_panel/config_flow_options.py index f4bbceb1..024af7b3 100644 --- a/custom_components/span_panel/config_flow_options.py +++ b/custom_components/span_panel/config_flow_options.py @@ -72,7 +72,7 @@ def get_general_options_defaults( ), ENERGY_REPORTING_GRACE_PERIOD: config_entry.options.get(ENERGY_REPORTING_GRACE_PERIOD, 15), ENABLE_ENERGY_DIP_COMPENSATION: config_entry.options.get( - ENABLE_ENERGY_DIP_COMPENSATION, False + ENABLE_ENERGY_DIP_COMPENSATION, True ), } diff --git a/custom_components/span_panel/coordinator.py b/custom_components/span_panel/coordinator.py index bcc1f227..0c08410d 100644 --- a/custom_components/span_panel/coordinator.py +++ b/custom_components/span_panel/coordinator.py @@ -6,14 +6,14 @@ from datetime import timedelta import logging from time import time as _epoch_time -from typing import TYPE_CHECKING, Any, Protocol +from typing import TYPE_CHECKING, Protocol if TYPE_CHECKING: + from . import SpanPanelConfigEntry from .current_monitor import CurrentMonitor from .graph_horizon import GraphHorizonManager from homeassistant.components.persistent_notification import async_create -from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.exceptions import ( ConfigEntryAuthFailed, @@ -23,11 +23,10 @@ from homeassistant.helpers import entity_registry as er from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from span_panel_api import SpanMqttClient, SpanPanelSnapshot -from span_panel_api.exceptions import SpanPanelAuthError +from span_panel_api.exceptions import SpanPanelAuthError, SpanPanelStaleDataError from .const import DOMAIN from .id_builder import build_circuit_unique_id -from .options import ENERGY_REPORTING_GRACE_PERIOD from .schema_validation import collect_sensor_definitions, validate_field_metadata @@ -37,6 +36,7 @@ class SpanCircuitEnergySensorProtocol(Protocol): @property def energy_offset(self) -> float: """Cumulative dip compensation offset.""" + ... _LOGGER = logging.getLogger(__name__) @@ -66,22 +66,21 @@ def __init__( self, hass: HomeAssistant, client: SpanMqttClient, - config_entry: ConfigEntry, + config_entry: SpanPanelConfigEntry, ) -> None: """Initialize the coordinator.""" self._client = client - self.config_entry: ConfigEntry[Any] = config_entry + self.config_entry: SpanPanelConfigEntry = config_entry # Track last tick for visibility into cadence self._last_tick_epoch: float | None = None # Flag to track if a reload was requested self._reload_requested = False # Flag to track if panel is offline/unreachable self._panel_offline = False - # Track last grace period value for comparison - self._last_grace_period = config_entry.options.get(ENERGY_REPORTING_GRACE_PERIOD, 15) # Streaming state self._unregister_streaming: Callable[[], None] | None = None + self._unregister_connection: Callable[[], None] | None = None # Hardware capability tracking — detect when BESS/PV are commissioned # and trigger a reload so the factory creates the appropriate sensors. @@ -142,13 +141,19 @@ def _mark_panel_online(self) -> None: _LOGGER.info("%s is back online", self.config_entry.title or "SPAN Panel") self._panel_offline = False - def _mark_panel_offline(self, err: Exception) -> None: - """Mark the panel offline and log the transition once.""" + def _mark_panel_offline(self, reason: Exception | str) -> None: + """Mark the panel offline and log the transition once. + + `reason` is rendered with %s — both Exception and str format + correctly. The broker-disconnect path (from the MQTT client + connection callback) passes a short string; the snapshot-poll + path passes a SpanPanelStaleDataError or unexpected Exception. + """ if not self._panel_offline: _LOGGER.info( "%s is unavailable: %s", self.config_entry.title or "SPAN Panel", - err, + reason, ) self._panel_offline = True @@ -209,11 +214,34 @@ async def _fire_dip_notification(self) -> None: # --- Streaming --- async def async_setup_streaming(self) -> None: - """Set up push streaming.""" + """Set up push streaming and broker-connection state listening.""" + self._unregister_connection = self._client.register_connection_callback( + self._on_connection_change + ) self._unregister_streaming = self._client.register_snapshot_callback(self._on_snapshot_push) await self._client.start_streaming() _LOGGER.info("MQTT push streaming started") + def _on_connection_change(self, connected: bool) -> None: + """Handle a broker connection state edge from the MQTT client. + + Called on the event loop when the bridge transitions between + connected and disconnected. Flips the panel-offline flag and + pushes an immediate listener update so sensors enter or exit + grace-period logic without waiting for the 60 s fallback poll. + + Listener fan-out is guarded by a real state change so a misbehaving + or future-version library that re-emits the same edge does not + trigger spurious entity re-renders. + """ + was_offline = self._panel_offline + if connected: + self._mark_panel_online() + else: + self._mark_panel_offline("MQTT broker disconnected") + if self._panel_offline != was_offline: + self.async_update_listeners() + async def _on_snapshot_push(self, snapshot: SpanPanelSnapshot) -> None: """Handle a pushed snapshot from MQTT streaming.""" self._mark_panel_online() @@ -223,6 +251,10 @@ async def _on_snapshot_push(self, snapshot: SpanPanelSnapshot) -> None: async def async_shutdown(self) -> None: """Shut down the coordinator and release resources.""" + if self._unregister_connection is not None: + self._unregister_connection() + self._unregister_connection = None + if self._unregister_streaming is not None: self._unregister_streaming() self._unregister_streaming = None @@ -466,12 +498,20 @@ async def _async_update_data(self) -> SpanPanelSnapshot: except ConfigEntryAuthFailed: raise - except Exception as err: + except SpanPanelStaleDataError as err: + # Expected offline path — the library signals the client + # isn't live. Same handling as other offline errors. self._mark_panel_offline(err) + if self.data is not None: + return self.data + raise - # Return last known data to keep coordinator updating for grace period logic. - # On first refresh (self.data is None), re-raise so async_config_entry_first_refresh - # surfaces the error properly. + except Exception as err: + # Unexpected error — log the transition but keep the + # coordinator ticking on last-known data for grace-period logic. + # On first refresh (self.data is None), re-raise so + # async_config_entry_first_refresh surfaces the error properly. + self._mark_panel_offline(err) if self.data is not None: return self.data raise diff --git a/custom_components/span_panel/current_monitor.py b/custom_components/span_panel/current_monitor.py index 47d3a441..dee074c5 100644 --- a/custom_components/span_panel/current_monitor.py +++ b/custom_components/span_panel/current_monitor.py @@ -30,6 +30,7 @@ DOMAIN, ) from .helpers import build_circuit_unique_id, build_panel_unique_id +from .id_builder import extract_circuit_uuid_from_unique_id from .options import ( CONTINUOUS_THRESHOLD_PCT, COOLDOWN_DURATION_M, @@ -41,6 +42,7 @@ WINDOW_DURATION_M, ) from .threshold_evaluator import ( + MonitoringSettings, check_continuous, check_spike, is_monitoring_disabled, @@ -84,11 +86,11 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self._entry = entry self._circuit_states: dict[str, MonitoredPointState] = {} self._mains_states: dict[str, MonitoredPointState] = {} - self._circuit_overrides: dict[str, dict[str, Any]] = {} - self._mains_overrides: dict[str, dict[str, Any]] = {} - self._global_settings: dict[str, Any] = {} + self._circuit_overrides: dict[str, MonitoringSettings] = {} + self._mains_overrides: dict[str, MonitoringSettings] = {} + self._global_settings: MonitoringSettings = {} self._last_snapshot: SpanPanelSnapshot | None = None - self._store: Store = Store( + self._store: Store[dict[str, Any]] = Store( hass, _STORAGE_VERSION, f"{_STORAGE_KEY_PREFIX}.{entry.entry_id}", @@ -110,7 +112,7 @@ def get_mains_state(self, leg: str) -> MonitoredPointState | None: """Return tracking state for a mains leg, or None if not monitored.""" return self._mains_states.get(leg) - def set_circuit_override(self, circuit_id: str, overrides: dict[str, Any]) -> None: + def set_circuit_override(self, circuit_id: str, overrides: MonitoringSettings) -> None: """Set per-circuit threshold overrides.""" existing = self._circuit_overrides.get(circuit_id, {}) existing.update(overrides) @@ -126,7 +128,7 @@ def clear_circuit_override(self, circuit_id: str) -> None: self._circuit_states.pop(circuit_id, None) self._hass.async_create_task(self.async_save_overrides()) - def set_mains_override(self, leg: str, overrides: dict[str, Any]) -> None: + def set_mains_override(self, leg: str, overrides: MonitoringSettings) -> None: """Set per-mains-leg threshold overrides.""" existing = self._mains_overrides.get(leg, {}) existing.update(overrides) @@ -136,7 +138,7 @@ def set_mains_override(self, leg: str, overrides: dict[str, Any]) -> None: self._mains_overrides[leg] = existing self._hass.async_create_task(self.async_save_overrides()) - def _is_redundant_override(self, override: dict[str, Any]) -> bool: + def _is_redundant_override(self, override: MonitoringSettings) -> bool: """Check if an override matches global defaults (and can be removed). An override is redundant if monitoring is enabled (or not set, defaulting @@ -163,7 +165,7 @@ def clear_mains_override(self, leg: str) -> None: self._mains_states.pop(leg, None) self._hass.async_create_task(self.async_save_overrides()) - def get_global_settings(self) -> dict[str, Any]: + def get_global_settings(self) -> MonitoringSettings: """Get the effective global monitoring settings. Returns stored global settings if available, otherwise falls back @@ -191,7 +193,7 @@ def get_global_settings(self) -> dict[str, Any]: NOTIFICATION_PRIORITY: opts.get(NOTIFICATION_PRIORITY, DEFAULT_NOTIFICATION_PRIORITY), } - def set_global_settings(self, settings: dict[str, Any]) -> None: + def set_global_settings(self, settings: MonitoringSettings) -> None: """Update global monitoring settings in storage.""" valid_keys = { CONTINUOUS_THRESHOLD_PCT, @@ -239,13 +241,9 @@ def resolve_entity_to_circuit_id(self, entity_id: str) -> str: entity_reg = er.async_get(self._hass) entry = entity_reg.async_get(entity_id) if entry is not None and entry.unique_id: - # unique_id format: span_{serial}_{circuit_id}_{suffix} - parts = entry.unique_id.split("_") - # Find the circuit_id — it's the UUID segment between serial and suffix - # Serial is parts[1], suffix is last part(s). The circuit UUID is a 32-char hex. - for part in parts: - if len(part) == 32 and all(c in "0123456789abcdef" for c in part): - return part + uuid = extract_circuit_uuid_from_unique_id(entry.unique_id) + if uuid is not None: + return uuid # Fall through: assume it's already a circuit_id return entity_id @@ -415,7 +413,7 @@ async def async_load_overrides(self) -> None: @staticmethod async def async_is_enabled(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Check if monitoring was previously enabled by reading storage.""" - store: Store = Store( + store: Store[dict[str, Any]] = Store( hass, _STORAGE_VERSION, f"{_STORAGE_KEY_PREFIX}.{entry.entry_id}", @@ -549,4 +547,4 @@ def _evaluate_mains(self, snapshot: SpanPanelSnapshot) -> None: ) # Backward-compatible static alias kept for existing callers/tests. - _format_notification = staticmethod(format_notification) # type: ignore[assignment] + _format_notification = staticmethod(format_notification) diff --git a/custom_components/span_panel/entity_resolver.py b/custom_components/span_panel/entity_resolver.py index d2f0cf8f..7cd2032d 100644 --- a/custom_components/span_panel/entity_resolver.py +++ b/custom_components/span_panel/entity_resolver.py @@ -243,7 +243,11 @@ def construct_multi_circuit_entity_id( # Use friendly name pattern: sensor.span_panel_solar_inverter_power circuit_part = slugify(friendly_name) - # Build the entity ID + # Build the entity ID. + # `False` default preserves legacy installs (no device prefix). Single-circuit + # entities default to True — see construct_single_circuit_entity_id below — + # because they never had a prefix-less form historically; multi-circuit + # synthetics did. use_device_prefix = coordinator.config_entry.options.get(USE_DEVICE_PREFIX, False) parts = [] diff --git a/custom_components/span_panel/frontend.py b/custom_components/span_panel/frontend.py index 2138b529..14d669db 100644 --- a/custom_components/span_panel/frontend.py +++ b/custom_components/span_panel/frontend.py @@ -6,15 +6,19 @@ from __future__ import annotations +import asyncio import hashlib +import logging import os -from typing import Any +from typing import Any, Literal from homeassistant.components.http import StaticPathConfig from homeassistant.core import HomeAssistant from homeassistant.helpers.storage import Store -from .const import DOMAIN, PANEL_ADMIN_ONLY, PANEL_SHOW_SIDEBAR +from .const import PANEL_ADMIN_ONLY, PANEL_SHOW_SIDEBAR + +_LOGGER = logging.getLogger(__name__) PANEL_URL = "/span_panel_frontend" PANEL_FRONTEND_DIR = os.path.join(os.path.dirname(__file__), "frontend", "dist") @@ -55,6 +59,128 @@ async def async_save_panel_settings(hass: HomeAssistant, settings: dict[str, Any await store.async_save(settings) +FavoriteKind = Literal["circuits", "sub_devices"] + +_FAVORITE_KINDS: tuple[FavoriteKind, ...] = ("circuits", "sub_devices") + + +def _empty_panel_favorites() -> dict[str, list[str]]: + return {kind: [] for kind in _FAVORITE_KINDS} + + +def _normalize_favorites_blob(raw: Any) -> dict[str, dict[str, list[str]]]: + """Normalize raw stored favorites into the canonical nested shape. + + Tolerates the legacy ``{panel_id: [uuid, ...]}`` shape (read transparently + as circuits-only). Logs a warning on dropped/malformed entries so storage + corruption is visible during debugging. + """ + result: dict[str, dict[str, list[str]]] = {} + if not isinstance(raw, dict): + return result + for panel_id, value in raw.items(): + if not isinstance(panel_id, str): + _LOGGER.warning( + "span_panel_settings: dropping favorites entry with non-string panel id %r", + panel_id, + ) + continue + # Legacy shape: panel_id maps to a flat list of circuit uuids. + if isinstance(value, list): + circuits = [u for u in value if isinstance(u, str) and u] + if circuits: + result[panel_id] = {"circuits": circuits, "sub_devices": []} + continue + if not isinstance(value, dict): + _LOGGER.warning( + "span_panel_settings: dropping favorites entry %s with unsupported value type %s", + panel_id, + type(value).__name__, + ) + continue + circuits_raw = value.get("circuits", []) + sub_devices_raw = value.get("sub_devices", []) + circuits = ( + [u for u in circuits_raw if isinstance(u, str) and u] + if isinstance(circuits_raw, list) + else [] + ) + sub_devices = ( + [u for u in sub_devices_raw if isinstance(u, str) and u] + if isinstance(sub_devices_raw, list) + else [] + ) + if circuits or sub_devices: + result[panel_id] = {"circuits": circuits, "sub_devices": sub_devices} + return result + + +# Module-level lock serializing favorites read-then-write so concurrent +# add/remove calls don't clobber each other. HA's Store serializes writes +# but not the surrounding load → mutate → save sequence. +_FAVORITES_LOCK = asyncio.Lock() + + +async def async_get_favorites( + hass: HomeAssistant, +) -> dict[str, dict[str, list[str]]]: + """Return the stored favorites map. + + Shape: ``{panel_device_id: {"circuits": [uuid, ...], "sub_devices": [devid, ...]}}``. + + Empty/missing storage returns an empty dict. Old single-list per-panel + shape ``{panel_id: [uuid, ...]}`` is read transparently as circuits-only + so favorites that were stored before sub-device support are preserved. + """ + settings = await async_load_panel_settings(hass) + return _normalize_favorites_blob(settings.get("favorites")) + + +async def async_set_favorite( + hass: HomeAssistant, + panel_device_id: str, + kind: FavoriteKind, + target_id: str, + favorited: bool, +) -> dict[str, dict[str, list[str]]]: + """Add or remove a circuit or sub-device from the favorites map. + + ``kind`` is either ``"circuits"`` or ``"sub_devices"``. ``target_id`` is + the circuit uuid or sub-device HA device id, respectively. Deduplicates + on add; drops empty lists and empty panel entries on remove. Returns + the updated full favorites map. + + Holds a module-level lock around load → mutate → save so concurrent + callers can't race; also persists the normalized shape so legacy data + is migrated in place on the first write touching it. + """ + if kind not in _FAVORITE_KINDS: + raise ValueError(f"Unknown favorite kind: {kind!r}") + + async with _FAVORITES_LOCK: + settings = await async_load_panel_settings(hass) + favorites = _normalize_favorites_blob(settings.get("favorites")) + + panel_entry = favorites.get(panel_device_id) or _empty_panel_favorites() + current = list(panel_entry.get(kind, [])) + if favorited: + if target_id not in current: + current.append(target_id) + else: + if target_id in current: + current.remove(target_id) + + panel_entry[kind] = current + if any(panel_entry[k] for k in _FAVORITE_KINDS): + favorites[panel_device_id] = panel_entry + else: + favorites.pop(panel_device_id, None) + + settings["favorites"] = favorites + await async_save_panel_settings(hass, settings) + return favorites + + async def _async_ensure_lovelace_resource(hass: HomeAssistant, url: str) -> None: """Ensure the card JS is registered as a Lovelace resource. @@ -135,7 +261,6 @@ async def async_apply_panel_registration(hass: HomeAssistant) -> None: module_url=f"{PANEL_URL}/span-panel.js?v={cache_tag}", require_admin=admin_only, config={}, - config_panel_domain=DOMAIN, ) else: _remove_panel(hass, "span-panel", warn_if_unknown=False) diff --git a/custom_components/span_panel/frontend/dist/span-panel-card.js b/custom_components/span_panel/frontend/dist/span-panel-card.js index 9d806e70..39b2709f 100644 --- a/custom_components/span_panel/frontend/dist/span-panel-card.js +++ b/custom_components/span_panel/frontend/dist/span-panel-card.js @@ -1,61 +1,116 @@ -let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","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.all_targets":"All","notification.event_bus_target":"Event Bus (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:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","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.configure_subdevice":"Configure device","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)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","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.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (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:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","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.configure_subdevice":"Configurar dispositivo","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)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","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.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (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 :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","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.configure_subdevice":"Configurer l'appareil","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)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","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.all_targets":"すべて","notification.event_bus_target":"イベントバス (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":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","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.configure_subdevice":"デバイスを設定","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)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","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.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (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:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","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.configure_subdevice":"Configurar dispositivo","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)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){e=n&&t[n]?n:"en"}function i(n){return t[e]?.[n]??t.en?.[n]??n}const s="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}},r="span_panel",c="CLOSED",l="pv",d="bess",h="evse",p="sub_",u=500,g={power:{entityRole:"power",label:()=>i("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:()=>i("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},_={soc:{entityRole:"soc",label:()=>i("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>i("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:g.power},f={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>i("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>i("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>i("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>i("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>i("shedding.unknown")}},m="#ff9800";function v(e,t,n,i){var s,o=arguments.length,a=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,n,i);else for(var r=e.length-1;r>=0;r--)(s=e[r])&&(a=(o<3?s(a):o>3?s(t,n,a):s(t,n))||a);return o>3&&a&&Object.defineProperty(t,n,a),a}"function"==typeof SuppressedError&&SuppressedError; +let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","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.all_targets":"All","notification.event_bus_target":"Event Bus (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:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","error.panel_offline":"SPAN Panel unreachable","error.panel_reconnected":"SPAN Panel reconnected","error.panel_offline_named":"{name} unreachable","error.panel_reconnected_named":"{name} reconnected","error.discovery_failed":"Unable to connect to SPAN Panel","error.relay_failed":"Unable to toggle relay","error.shedding_failed":"Unable to update shedding priority","error.threshold_failed":"Unable to save threshold","error.graph_horizon_failed":"Unable to update graph time horizon","error.favorites_fetch_failed":"Unable to load favorites","error.favorites_toggle_failed":"Unable to update favorite","error.history_failed":"Unable to load historical data","error.monitoring_failed":"Unable to load monitoring status","error.graph_settings_failed":"Unable to load graph settings","error.areas_failed":"Area assignments may be out of sync","error.retry":"Retry","card.connecting":"Connecting to SPAN Panel...","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","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.configure_subdevice":"Configure device","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.favorites_subtitle":"Favorites","sidepanel.global_default":"Global Default","sidepanel.list_view_columns":"List View Columns","sidepanel.columns":"Columns","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.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","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.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","panel.favorites":"Favorites","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.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)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","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.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (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:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","error.panel_offline":"SPAN Panel inaccesible","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inaccesible","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"No se puede conectar al SPAN Panel","error.relay_failed":"No se pudo cambiar el relé","error.shedding_failed":"No se pudo actualizar la prioridad de desconexión","error.threshold_failed":"No se pudo guardar el umbral","error.graph_horizon_failed":"No se pudo actualizar el horizonte temporal del gráfico","error.favorites_fetch_failed":"No se pudieron cargar los favoritos","error.favorites_toggle_failed":"No se pudo actualizar el favorito","error.history_failed":"No se pudieron cargar los datos históricos","error.monitoring_failed":"No se pudo cargar el estado de monitoreo","error.graph_settings_failed":"No se pudo cargar la configuración del gráfico","error.areas_failed":"Las asignaciones de áreas pueden estar desincronizadas","error.retry":"Reintentar","card.connecting":"Conectando al SPAN Panel...","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","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.configure_subdevice":"Configurar dispositivo","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.favorites_subtitle":"Favoritos","sidepanel.global_default":"Predeterminado Global","sidepanel.list_view_columns":"Columnas de la lista","sidepanel.columns":"Columnas","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.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","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.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","panel.favorites":"Favoritos","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.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)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","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.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (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 :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","error.panel_offline":"SPAN Panel inaccessible","error.panel_reconnected":"SPAN Panel reconnecté","error.panel_offline_named":"{name} inaccessible","error.panel_reconnected_named":"{name} reconnecté","error.discovery_failed":"Impossible de se connecter au SPAN Panel","error.relay_failed":"Impossible de basculer le relais","error.shedding_failed":"Impossible de mettre à jour la priorité de délestage","error.threshold_failed":"Impossible d'enregistrer le seuil","error.graph_horizon_failed":"Impossible de mettre à jour l'horizon temporel du graphique","error.favorites_fetch_failed":"Impossible de charger les favoris","error.favorites_toggle_failed":"Impossible de mettre à jour le favori","error.history_failed":"Impossible de charger les données historiques","error.monitoring_failed":"Impossible de charger l'état de surveillance","error.graph_settings_failed":"Impossible de charger les paramètres du graphique","error.areas_failed":"Les affectations de zones peuvent être désynchronisées","error.retry":"Réessayer","card.connecting":"Connexion au SPAN Panel...","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","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.configure_subdevice":"Configurer l'appareil","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.favorites_subtitle":"Favoris","sidepanel.global_default":"Défaut Global","sidepanel.list_view_columns":"Colonnes de la liste","sidepanel.columns":"Colonnes","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.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","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.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","panel.favorites":"Favoris","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.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)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","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.all_targets":"すべて","notification.event_bus_target":"イベントバス (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":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","error.panel_offline":"SPANパネルに接続できません","error.panel_reconnected":"SPANパネルが再接続されました","error.panel_offline_named":"{name}に接続できません","error.panel_reconnected_named":"{name}が再接続されました","error.discovery_failed":"SPANパネルへの接続に失敗しました","error.relay_failed":"リレーの切り替えに失敗しました","error.shedding_failed":"シェディング優先度の更新に失敗しました","error.threshold_failed":"しきい値の保存に失敗しました","error.graph_horizon_failed":"グラフの時間範囲の更新に失敗しました","error.favorites_fetch_failed":"お気に入りの読み込みに失敗しました","error.favorites_toggle_failed":"お気に入りの更新に失敗しました","error.history_failed":"履歴データの読み込みに失敗しました","error.monitoring_failed":"監視ステータスの読み込みに失敗しました","error.graph_settings_failed":"グラフ設定の読み込みに失敗しました","error.areas_failed":"エリア割り当てが同期されていない可能性があります","error.retry":"再試行","card.connecting":"SPANパネルに接続中...","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","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.configure_subdevice":"デバイスを設定","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.favorites_subtitle":"お気に入り","sidepanel.global_default":"グローバルデフォルト","sidepanel.list_view_columns":"リスト表示の列数","sidepanel.columns":"列","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","panel.favorites":"お気に入り","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.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)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","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.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (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:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","error.panel_offline":"SPAN Panel inacessível","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inacessível","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"Não foi possível conectar ao SPAN Panel","error.relay_failed":"Não foi possível alternar o relé","error.shedding_failed":"Não foi possível atualizar a prioridade de desligamento","error.threshold_failed":"Não foi possível salvar o limite","error.graph_horizon_failed":"Não foi possível atualizar o horizonte temporal do gráfico","error.favorites_fetch_failed":"Não foi possível carregar os favoritos","error.favorites_toggle_failed":"Não foi possível atualizar o favorito","error.history_failed":"Não foi possível carregar os dados históricos","error.monitoring_failed":"Não foi possível carregar o status de monitoramento","error.graph_settings_failed":"Não foi possível carregar as configurações do gráfico","error.areas_failed":"As atribuições de áreas podem estar fora de sincronização","error.retry":"Tentar novamente","card.connecting":"Conectando ao SPAN Panel...","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","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.configure_subdevice":"Configurar dispositivo","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.favorites_subtitle":"Favoritos","sidepanel.global_default":"Padrão Global","sidepanel.list_view_columns":"Colunas da Lista","sidepanel.columns":"Colunas","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.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","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.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","panel.favorites":"Favoritos","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.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)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){e=n&&t[n]?n:"en"}function i(n){return t[e]?.[n]??t.en?.[n]??n}function s(n,i){return(t[e]?.[n]??t.en?.[n]??n).replace(/\{(\w+)\}/g,(e,t)=>Object.prototype.hasOwnProperty.call(i,t)?i[t]:`{${t}}`)}const o="power",r="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}},l="span_panel",c="CLOSED",d="pv",h="bess",p="evse",u="sub_",g=500,_={power:{entityRole:"power",label:()=>i("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:()=>i("metric.current"),unit:()=>"A",format:e=>Math.abs(e).toFixed(1)}},f={soc:{entityRole:"soc",label:()=>i("metric.soc"),unit:()=>"%",format:e=>String(Math.round(e)),fixedMin:0,fixedMax:100},soe:{entityRole:"soe",label:()=>i("metric.soe"),unit:()=>"kWh",format:e=>e.toFixed(1)},power:_.power},m={always_on:{icon:"mdi:battery",icon2:"mdi:router-wireless",color:"#4caf50",label:()=>i("shedding.always_on")},never:{icon:"mdi:battery",color:"#4caf50",label:()=>i("shedding.never")},soc_threshold:{icon:"mdi:battery-alert-variant-outline",color:"#9c27b0",label:()=>i("shedding.soc_threshold"),textLabel:"SoC"},off_grid:{icon:"mdi:transmission-tower",color:"#ff9800",label:()=>i("shedding.off_grid")},unknown:{icon:"mdi:help-circle-outline",color:"#888",label:()=>i("shedding.unknown")}},v="#ff9800";function b(e,t,n,i){var s,o=arguments.length,r=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(e,t,n,i);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(r=(o<3?s(r):o>3?s(t,n,r):s(t,n))||r);return o>3&&r&&Object.defineProperty(t,n,r),r}"function"==typeof SuppressedError&&SuppressedError; /** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const b=globalThis,y=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,w=Symbol(),x=new WeakMap;let S=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==w)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(y&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=x.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&x.set(t,e))}return e}toString(){return this.cssText}};const C=e=>new S("string"==typeof e?e:e+"",void 0,w),$=y?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return C(t)})(e):e,{is:E,defineProperty:z,getOwnPropertyDescriptor:k,getOwnPropertyNames:A,getOwnPropertySymbols:M,getPrototypeOf:P}=Object,L=globalThis,N=L.trustedTypes,T=N?N.emptyScript:"",D=L.reactiveElementPolyfillSupport,H=(e,t)=>e,O={toAttribute(e,t){switch(t){case Boolean:e=e?T:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},R=(e,t)=>!E(e,t),I={attribute:!0,type:String,converter:O,reflect:!1,useDefault:!1,hasChanged:R}; +const y=globalThis,w=y.ShadowRoot&&(void 0===y.ShadyCSS||y.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,x=Symbol(),S=new WeakMap;let C=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==x)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(w&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=S.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&S.set(t,e))}return e}toString(){return this.cssText}};const $=e=>new C("string"==typeof e?e:e+"",void 0,x),E=w?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return $(t)})(e):e,{is:k,defineProperty:z,getOwnPropertyDescriptor:P,getOwnPropertyNames:A,getOwnPropertySymbols:N,getPrototypeOf:M}=Object,L=globalThis,I=L.trustedTypes,D=I?I.emptyScript:"",T=L.reactiveElementPolyfillSupport,H=(e,t)=>e,O={toAttribute(e,t){switch(t){case Boolean:e=e?D:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},F=(e,t)=>!k(e,t),R={attribute:!0,type:String,converter:O,reflect:!1,useDefault:!1,hasChanged:F}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Symbol.metadata??=Symbol("metadata"),L.litPropertyMetadata??=new WeakMap;let j=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=I){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&z(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:s}=k(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??I}static _$Ei(){if(this.hasOwnProperty(H("elementProperties")))return;const e=P(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(H("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(H("properties"))){const e=this.properties,t=[...A(e),...M(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift($(e))}else void 0!==e&&t.push($(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(y)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=b.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:O).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:O;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,n,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),n??=o.getPropertyOptions(e),!((n.hasChanged??R)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:s},o){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};j.elementStyles=[],j.shadowRootOptions={mode:"open"},j[H("elementProperties")]=new Map,j[H("finalized")]=new Map,D?.({ReactiveElement:j}),(L.reactiveElementVersions??=[]).push("2.1.2"); + */Symbol.metadata??=Symbol("metadata"),L.litPropertyMetadata??=new WeakMap;let j=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=R){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&z(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:s}=P(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??R}static _$Ei(){if(this.hasOwnProperty(H("elementProperties")))return;const e=M(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(H("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(H("properties"))){const e=this.properties,t=[...A(e),...N(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift(E(e))}else void 0!==e&&t.push(E(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(w)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=y.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:O).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:O;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,n,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),n??=o.getPropertyOptions(e),!((n.hasChanged??F)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:s},o){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};j.elementStyles=[],j.shadowRootOptions={mode:"open"},j[H("elementProperties")]=new Map,j[H("finalized")]=new Map,T?.({ReactiveElement:j}),(L.reactiveElementVersions??=[]).push("2.1.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const q=globalThis,F=e=>e,W=q.trustedTypes,U=W?W.createPolicy("lit-html",{createHTML:e=>e}):void 0,B="$lit$",G=`lit$${Math.random().toFixed(9).slice(2)}$`,V="?"+G,Q=`<${V}>`,J=document,X=()=>J.createComment(""),K=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Z=Array.isArray,Y="[ \t\n\f\r]",ee=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,te=/-->/g,ne=/>/g,ie=RegExp(`>|${Y}(?:([^\\s"'>=/]+)(${Y}*=${Y}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),se=/'/g,oe=/"/g,ae=/^(?:script|style|textarea|title)$/i,re=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ce=Symbol.for("lit-noChange"),le=Symbol.for("lit-nothing"),de=new WeakMap,he=J.createTreeWalker(J,129);function pe(e,t){if(!Z(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==U?U.createHTML(t):t}const ue=(e,t)=>{const n=e.length-1,i=[];let s,o=2===t?"":3===t?"":"",a=ee;for(let t=0;t"===c[0]?(a=s??ee,l=-1):void 0===c[1]?l=-2:(l=a.lastIndex-c[2].length,r=c[1],a=void 0===c[3]?ie:'"'===c[3]?oe:se):a===oe||a===se?a=ie:a===te||a===ne?a=ee:(a=ie,s=void 0);const h=a===ie&&e[t+1].startsWith("/>")?" ":"";o+=a===ee?n+Q:l>=0?(i.push(r),n.slice(0,l)+B+n.slice(l)+G+h):n+G+(-2===l?t:h)}return[pe(e,o+(e[n]||"")+(2===t?"":3===t?"":"")),i]};class ge{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let s=0,o=0;const a=e.length-1,r=this.parts,[c,l]=ue(e,t);if(this.el=ge.createElement(c,n),he.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=he.nextNode())&&r.length0){i.textContent=W?W.emptyScript:"";for(let n=0;nZ(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==le&&K(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=ge.createElement(pe(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new fe(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=de.get(e.strings);return void 0===t&&de.set(e.strings,t=new ge(e)),t}k(e){Z(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const s of e)i===t.length?t.push(n=new me(this.O(X()),this.O(X()),this,this.options)):n=t[i],n._$AI(s),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=le}_$AI(e,t=this,n,i){const s=this.strings;let o=!1;if(void 0===s)e=_e(this,e,t,0),o=!K(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const i=e;let a,r;for(e=s[0],a=0;ae,W=U.trustedTypes,B=W?W.createPolicy("lit-html",{createHTML:e=>e}):void 0,G="$lit$",V=`lit$${Math.random().toFixed(9).slice(2)}$`,Q="?"+V,K=`<${Q}>`,J=document,X=()=>J.createComment(""),Z=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Y=Array.isArray,ee="[ \t\n\f\r]",te=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ne=/-->/g,ie=/>/g,se=RegExp(`>|${ee}(?:([^\\s"'>=/]+)(${ee}*=${ee}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),oe=/'/g,re=/"/g,ae=/^(?:script|style|textarea|title)$/i,le=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ce=Symbol.for("lit-noChange"),de=Symbol.for("lit-nothing"),he=new WeakMap,pe=J.createTreeWalker(J,129);function ue(e,t){if(!Y(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==B?B.createHTML(t):t}const ge=(e,t)=>{const n=e.length-1,i=[];let s,o=2===t?"":3===t?"":"",r=te;for(let t=0;t"===l[0]?(r=s??te,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?se:'"'===l[3]?re:oe):r===re||r===oe?r=se:r===ne||r===ie?r=te:(r=se,s=void 0);const h=r===se&&e[t+1].startsWith("/>")?" ":"";o+=r===te?n+K:c>=0?(i.push(a),n.slice(0,c)+G+n.slice(c)+V+h):n+V+(-2===c?t:h)}return[ue(e,o+(e[n]||"")+(2===t?"":3===t?"":"")),i]};class _e{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let s=0,o=0;const r=e.length-1,a=this.parts,[l,c]=ge(e,t);if(this.el=_e.createElement(l,n),pe.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=pe.nextNode())&&a.length0){i.textContent=W?W.emptyScript:"";for(let n=0;nY(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==de&&Z(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=_e.createElement(ue(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new me(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=he.get(e.strings);return void 0===t&&he.set(e.strings,t=new _e(e)),t}k(e){Y(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const s of e)i===t.length?t.push(n=new ve(this.O(X()),this.O(X()),this,this.options)):n=t[i],n._$AI(s),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=de}_$AI(e,t=this,n,i){const s=this.strings;let o=!1;if(void 0===s)e=fe(this,e,t,0),o=!Z(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const i=e;let r,a;for(e=s[0],r=0;r{const i=n?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=n?.renderBefore??null;i._$litPart$=s=new me(t.insertBefore(X(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}}$e._$litElement$=!0,$e.finalized=!0,Ce.litElementHydrateSupport?.({LitElement:$e});const Ee=Ce.litElementPolyfillSupport;Ee?.({LitElement:$e}),(Ce.litElementVersions??=[]).push("4.2.2"); + */class Ee extends j{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=((e,t,n)=>{const i=n?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=n?.renderBefore??null;i._$litPart$=s=new ve(t.insertBefore(X(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}}Ee._$litElement$=!0,Ee.finalized=!0,$e.litElementHydrateSupport?.({LitElement:Ee});const ke=$e.litElementPolyfillSupport;ke?.({LitElement:Ee}),($e.litElementVersions??=[]).push("4.2.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const ze={attribute:!0,type:String,converter:O,reflect:!1,hasChanged:R},ke=(e=ze,t,n)=>{const{kind:i,metadata:s}=n;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,s,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const s=this[i];t.call(this,n),this.requestUpdate(i,s,e,!0,n)}}throw Error("Unsupported decorator location: "+i)}; +const ze=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)},Pe={attribute:!0,type:String,converter:O,reflect:!1,hasChanged:F},Ae=(e=Pe,t,n)=>{const{kind:i,metadata:s}=n;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,s,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const s=this[i];t.call(this,n),this.requestUpdate(i,s,e,!0,n)}}throw Error("Unsupported decorator location: "+i)}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Ae(e){return(t,n)=>"object"==typeof n?ke(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)} + */function Ne(e){return(t,n)=>"object"==typeof n?Ae(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function Me(e){return Ae({...e,state:!0,attribute:!1})}const Pe={"&":"&","<":"<",">":">",'"':""","'":"'"};function Le(e){return String(e).replace(/[&<>"']/g,e=>Pe[e]??e)}const Ne=g.power;function Te(e){return Ne.unit(e)}function De(e){return(e<0?"-":"")+Ne.format(e)}function He(e){return(Math.abs(e)/1e3).toFixed(1)}function Oe(e){return Math.ceil(e/2)}function Re(e){return e%2==0?1:0}function Ie(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return Oe(t)===Oe(n)?"row-span":Re(t)===Re(n)?"col-span":"row-span"}function je(e){const t=e.chart_metric??s;return g[t]??g[s]}function qe(e,t){const n=function(e){return je(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class Fe{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 s=await e.callWS({type:"call_service",domain:r,service:"get_monitoring_status",service_data:i,return_response:!0});this._status=s?.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 We(e,t){return e?.circuits?e.circuits[t]??null:null}function Ue(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}function Be(e,t,n,s,o,a,r,d,h,p=!1){const u=t.entities?.power,g=u?a.states[u]:null,_=g&&parseFloat(g.state)||0,v=t.device_type===l||_<0,b=t.entities?.switch,y=b?a.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===c,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",C=Le(t.name||i("grid.unknown")),$=je(r);let E;if("current"===$.entityRole){const e=t.entities?.current,n=e?a.states[e]:null,i=n&&parseFloat(n.state)||0;E=`${$.format(i)}A`}else E=`${De(_)}${Te(_)}`;const z=h||"unknown";let k="";if("unknown"!==z){const e=f[z]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};k=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}const A=d&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(d),M=A?m:"#555",P=``;let L="";if(null!=d?.utilization_pct){const e=d.utilization_pct;L=`${Math.round(e)}%`}const N=function(e){return!!e&&null!=e.over_threshold_since}(d);return`\n
\n
\n
\n ${S?`${S}`:""}\n ${C}\n
\n
\n \n ${E}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${i(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${k}\n ${L}\n ${P}\n
\n
\n
\n `}function Ge(e,t){return`\n
\n \n
\n `}const Ve={names:["power","battery power"],suffixes:["_power"]},Qe={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},Je={names:["state of energy"],suffixes:["_soe_kwh"]},Xe={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function Ke(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 Ze(e){return Ke(e,Ve)}function Ye(e){return Ke(e,Qe)}function et(e){return Ke(e,Je)}function tt(e){return Ke(e,Xe)}function nt(e,t,n,i){const s=n.visible_sub_entities||{};let o="";if(!e.entities)return o;for(const[n,a]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==s[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")}o+=`\n
\n ${Le(c)}:\n ${Le(d)}\n
\n `}return o}function it(e,t,n,s,o,a){if(n){const t=[{key:`${p}${e}_soc`,title:i("subdevice.soc"),available:!!o},{key:`${p}${e}_soe`,title:i("subdevice.soe"),available:!!a},{key:`${p}${e}_power`,title:i("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Le(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function st(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 ot(e){const t=a[e];return t?t.ms:a[o].ms}function at(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function rt(e){return Math.max(500,Math.floor(e/5e3))}function ct(e,t,n,i,s,o){e.has(t)||e.set(t,[]);const a=e.get(t);a.push({time:i,value:n});const r=a.findIndex(e=>e.time>=s);r>0?a.splice(0,r):-1===r&&(a.length=0),a.length>o&&a.splice(0,a.length-o)}function lt(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 dt(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=i/36e5>72?"hour":"5minute",r=await e.callWS({type:"recorder/statistics_during_period",start_time:o,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 o=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];t.sort((e,t)=>e.time-t.time),s.set(i,t)}}}async function ht(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),a=await e.callWS({type:"history/history_during_period",start_time:o,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),r=at(i),c=rt(i);for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const o=[];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&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];s.set(i,lt(t,r,c))}}}function pt(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:Ze(i)};i.type===d&&(e.soc=Ye(i),e.soe=et(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${p}${n}_${i}`,devId:n})}return t}async function ut(e,t,n,i,s,o){if(!t||!e)return;const a=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=qe(i,n);if(!t)continue;let o;o=s&&s.has(e)?ot(s.get(e)):st(n),a.has(o)||a.set(o,{entityIds:[],uuidByEntity:new Map});const r=a.get(o);r.entityIds.push(t),r.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:s}of pt(t)){let t;t=o&&o.has(s)?ot(o.get(s)):st(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(dt(e,n.entityIds,n.uuidByEntity,t,i)):r.push(ht(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function gt(e,t,n,i,o,a,r,c,l){const{options:d,series:h}=function(e,t,n,i,o,a=!1){n||(n=g[s]);const r=i?"140, 160, 220":"77, 217, 175",c=`rgb(${r})`,l=Date.now(),d=l-t,h=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=(e??[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),u=[{type:"line",data:p,showSymbol:!1,smooth:!1,...a?{}:{step:"end"},lineStyle:{width:1.5,color:c},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${r}, 0.35)`},{offset:1,color:`rgba(${r}, 0.02)`}]}},itemStyle:{color:c}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):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}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),o&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),u.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}}),u.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}}));const m={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:e=>{if(!e||0===e.length)return"";const t=e[0],i=new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),s=parseFloat(t.value[1].toFixed(2));return`
${i}
${n.format(s)} ${n.unit(s)}
`}},animation:!1};return{options:m,series:u}}(n,i,o,a,c,l),p=r??120;e.style.minHeight=p+"px";let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.hass=t,e.innerHTML="",e.appendChild(u));const _=e.clientHeight;u.height=(_>0?_:p)+"px",u.hass=t,u.options=d,u.data=h}function _t(e,t,n,s,o,a){if(!e||!n||!t)return;const r=st(s);let d=0;for(const[,e]of Object.entries(n.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==l&&(d+=Math.abs(s))}!function(e,t,n,i,s){const o="current"===(i.chart_metric||"power"),a=e.querySelector(".stat-consumption .stat-value"),r=e.querySelector(".stat-consumption .stat-unit");if(o){const e=n.panel_entities?.site_power,i=e?t.states[e]: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 e=n.panel_entities?.site_power;if(e){const n=t.states[e];n&&(s=Math.abs(parseFloat(n.state)||0))}a&&(a.textContent=He(s)),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(o){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=He(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(o){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=He(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(o){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=He(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,n,s,d);const h=je(s),p="current"===h.entityRole;for(const[s,d]of Object.entries(n.circuits)){const n=e.querySelector(`[data-uuid="${s}"]`);if(!n)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,m=d.device_type===l||_<0,v=d.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===c,w=n.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${De(_)}${Te(_)}`;const x=n.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=i(y?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!y),n.classList.toggle("circuit-producer",m),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const C=f[S]??f.unknown,$=n.querySelector(".shedding-icon");$&&($.setAttribute("icon",C.icon),$.style.color=C.color,$.title=C.label());const E=n.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 z=n.querySelector(".shedding-label");z&&(C.textLabel?(z.textContent=C.textLabel,z.style.color=C.color,z.style.display=""):z.style.display="none");const k=n.querySelector(".chart-container");if(k){const e=o.get(s)||[],i=n.classList.contains("circuit-col-span")?200:100,c=a?.has(s)?ot(a.get(s)):r,p=d.device_type===l;gt(k,t,e,c,h,m,i,d.breaker_rating_a??void 0,p)}}}class ft{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 s=await e.callWS({type:"call_service",domain:r,service:"get_graph_settings",service_data:i,return_response:!0});this._settings=s?.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 mt(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function vt(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class bt{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new Fe,this.graphSettingsCache=new ft,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._showMonitoring=!1,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}set showMonitoring(e){this._showMonitoring=e}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(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,mt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,vt(e,t))}async fetchAndBuildHorizonMaps(){try{await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)}catch{}}async loadHistory(){await ut(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(!a[i]?.useRealtime)continue;const s=qe(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=ot(i),d=at(l),h=rt(l),p=e-l,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time0&&this._topology)for(const{key:e,devId:t}of pt(this._topology))n.has(t)&&i.add(e);const s=new Map;try{await ut(this._hass,this._topology,this._config,s,t,n);for(const e of t.keys()){const t=s.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}for(const e of i){const t=s.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}this.updateDOM(e)}catch{}}updateDOM(e){this._hass&&this._topology&&this._config&&(_t(e,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(e,t,n,i,s,o){if(!n.sub_devices)return;const a=st(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const c=Ze(r);if(c){const e=t.states[c],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${De(i)} ${Te(i)}`)}const l=n.querySelectorAll("[data-chart-key]");for(const e of l){const n=e.dataset.chartKey;if(!n)continue;const r=s.get(n)||[];let c=_.power;n.endsWith("_soc")?c=_.soc:n.endsWith("_soe")&&(c=_.soe);const l=!!e.closest(".bess-chart-col");gt(e,t,r,o?.has(i)?ot(o.get(i)):a,c,!1,l?120:150,void 0,n.endsWith("_soc")||n.endsWith("_soe"))}for(const e of Object.keys(r.entities||{})){const i=n.querySelector(`[data-eid="${e}"]`);if(!i)continue;const s=t.states[e];if(s){let e;if(t.formatEntityState)e=t.formatEntityState(s);else{e=s.state;const t=s.attributes.unit_of_measurement||"";t&&(e+=" "+t)}if("Wh"===(s.attributes.unit_of_measurement||"")){const t=parseFloat(s.state);isNaN(t)||(e=(t/1e3).toFixed(1)+" kWh")}i.textContent=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 s=t.querySelector(".slide-confirm");if(!s||!s.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const o=i.closest("[data-uuid]");if(!o||!this._topology||!this._hass)return;const a=o.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?.current??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,showMonitoring:this._showMonitoring,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 s=!1,o=0,a=0;const r=t=>{e.classList.contains("confirmed")||(s=!0,o=t-n.offsetLeft,a=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},c=e=>{if(!s)return;const t=Math.max(2,Math.min(e-o,a));n.style.left=t+"px"},l=()=>{if(!s)return;s=!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 yt(e=""){const t=e?` value="${Le(e)}"`:"",n=e?"":"display:none;";return`\n
\n \n \n
\n `}function wt(e){const t="current"===(e.chart_metric||"power");return`\n
\n \n \n
\n `}function xt(e,t,n,s,o,a,r){const l=t.entities?.power,d=l?n.states[l]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?n.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===c,_=t.breaker_rating_a,m=_?`${Math.round(_)}A`:"",v=Le(t.name||i("grid.unknown")),b=je(s),y="current"===b.entityRole;let w;if(g)if(y){const e=t.entities?.current,i=e?n.states[e]:null,s=i&&parseFloat(i.state)||0;w=`${b.format(s)}A`}else w=`${De(h)}${Te(h)}`;else w="";const x=a||"unknown";let S="";if("unknown"!==x){const e=f[x]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};S=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let C="";if(null!=o?.utilization_pct){const e=o.utilization_pct;C=`${Math.round(e)}%`}const $=g?'ON':'OFF';return`\n
\n ${m?`${m}`:""}\n ${v}\n ${S}\n ${C}\n ${$}\n \n ${w}\n \n \n
\n `}function St(e,t,n,i,s,o){const a=Be(e,t,0,"1","single",n,i,s,o,!0);return`
${a}
`}function Ct(e){return`
${Le(e)}
`}function $t(e,t,n){const i=e.entities?.switch,s=i?t.states[i]:null,o=e.entities?.power,a=o?t.states[o]:null,r=s?"on"===s.state:(a?.attributes?.relay_state||e.relay_state)===c;let l;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;l=i?Math.abs(parseFloat(i.state)||0):0}else l=a?Math.abs(parseFloat(a.state)||0):0;return{isOn:r,value:l}}function Et(e,t){if(e.always_on)return"always_on";const n=e.entities?.select,i=n?t.states[n]:null;return i?i.state:"unknown"}function zt(e,t,n){return e.sort((e,i)=>{const s=$t(e[1],t,n),o=$t(i[1],t,n);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value})}function kt(e){return e.entities?.current??e.entities?.power??""}class At{constructor(e){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._ctrl=e}renderActivityView(e,t,n,i,s){this._unbindEvents(),this._hass=t,this._topology=n,this._config=i,this._monitoringStatus=s;const o=zt(Object.entries(n.circuits),t,i);let a=yt(this._searchQuery)+wt(i);a+='
';for(const[e,n]of o){const o=We(s,kt(n)),r=Et(n,t),c=this._expandedUuids.has(e);a+=xt(e,n,t,i,o,r,c),c&&(a+=St(e,n,t,i,o,r))}a+="
",a+="",e.innerHTML=a;const r=e.querySelector("span-side-panel");r&&(r.hass=t),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}renderAreaView(e,t,n,s,o){this._unbindEvents(),this._hass=t,this._topology=n,this._config=s,this._monitoringStatus=o;const a=i("list.unassigned_area"),r=new Map;for(const[e,t]of Object.entries(n.circuits)){const n=t.area??a,i=r.get(n);i?i.push([e,t]):r.set(n,[[e,t]])}const c=[...r.keys()].sort((e,t)=>e===a?1:t===a?-1:e.localeCompare(t));let l=yt(this._searchQuery)+wt(s);l+='
';for(const e of c){const n=r.get(e);if(!n)continue;const i=zt(n,t,s);l+=Ct(e);for(const[e,n]of i){const i=We(o,kt(n)),a=Et(n,t),r=this._expandedUuids.has(e);l+=xt(e,n,t,s,i,a,r),r&&(l+=St(e,n,t,s,i,a))}}l+="
",l+="",e.innerHTML=l;const d=e.querySelector("span-side-panel");d&&(d.hass=t),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}updateCollapsedRows(e,t,n,i){const s=je(i),o="current"===s.entityRole,a=e.querySelectorAll(".list-row[data-row-uuid]");for(const e of a){const a=e.dataset.rowUuid;if(!a)continue;const r=n.circuits[a];if(!r)continue;const{isOn:c,value:l}=$t(r,t,i),d=e.querySelector(".list-power-value");if(d)if(c)if(o)d.innerHTML=`${s.format(l)}A`;else{const e=r.entities?.power,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;d.innerHTML=`${De(i)}${Te(i)}`}else d.innerHTML="";const h=e.querySelector(".list-status-badge");h&&(h.textContent=c?"ON":"OFF",h.classList.toggle("list-status-on",c),h.classList.toggle("list-status-off",!c)),e.classList.toggle("circuit-off",!c)}}stop(){this._unbindEvents(),this._expandedUuids.clear(),this._searchQuery="",this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_bindEvents(e){this._container=e,this._clickHandler=t=>{const n=t.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const e=i.dataset.expandUuid;return void(e&&this._toggleExpand(e))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(t,e);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(t,e);if(n.closest(".list-search-clear")){const t=e.querySelector(".list-search");return void(t&&(t.value="",t.dispatchEvent(new Event("input",{bubbles:!0}))))}const s=n.closest(".unit-btn");if(s){const t=s.dataset.unit;t&&e.dispatchEvent(new CustomEvent("unit-changed",{detail:t,bubbles:!0,composed:!0}))}},this._inputHandler=t=>{const n=t.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(e))},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(e).then(()=>{this._ctrl.updateDOM(e)}).catch(()=>{})},e.addEventListener("click",this._clickHandler),e.addEventListener("input",this._inputHandler),e.addEventListener("graph-settings-changed",this._graphSettingsHandler)}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_applyFilter(e){const t=e.querySelector(".list-search-clear");t&&(t.style.display=this._searchQuery?"":"none");const n=e.querySelectorAll(".list-row[data-row-uuid]");for(const t of n){const n=t.querySelector(".list-circuit-name"),i=(n?.textContent?.toLowerCase()??"").includes(this._searchQuery);t.style.display=i?"":"none";const s=t.dataset.rowUuid;if(s){const t=e.querySelector(`.list-expanded-content[data-expanded-uuid="${s}"]`);t&&(t.style.display=i?"":"none")}}const i=e.querySelectorAll(".area-header");for(const e of i){let t=!1,n=e.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-row")&&"none"!==n.style.display){t=!0;break}n=n.nextElementSibling}e.style.display=t?"":"none"}}_toggleExpand(e){if(!(this._container&&this._hass&&this._topology&&this._config))return;const t=this._container.querySelector(`.list-row[data-row-uuid="${e}"]`),n=this._container.querySelector(`.list-expand-toggle[data-expand-uuid="${e}"]`);if(t)if(this._expandedUuids.has(e)){this._expandedUuids.delete(e);const i=this._container.querySelector(`.list-expanded-content[data-expanded-uuid="${e}"]`);i&&i.remove(),n&&n.classList.remove("expanded"),t.classList.remove("list-row-expanded")}else{this._expandedUuids.add(e);const i=this._topology.circuits[e];if(!i)return;const s=We(this._monitoringStatus,kt(i)),o=Et(i,this._hass),a=St(e,i,this._hass,this._config,s,o);t.insertAdjacentHTML("afterend",a),n&&n.classList.add("expanded"),t.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}}}async function Mt(e,t){const[n,i,s]=await Promise.all([e.callWS({type:"config/area_registry/list"}),e.callWS({type:"config/entity_registry/list"}),e.callWS({type:"config/device_registry/list"})]),o=new Map;for(const e of n)o.set(e.area_id,e.name);const a=new Map;for(const e of i)e.area_id&&a.set(e.entity_id,e.area_id);const r=new Map;for(const e of s)r.set(e.id,e.area_id);let c;if(t.device_id){const e=r.get(t.device_id);e&&(c=o.get(e))}for(const e of Object.values(t.circuits)){let t;for(const n of Object.values(e.entities)){if(!n)continue;const e=a.get(n);if(e){t=o.get(e);break}}t||(t=c),e.area=t}}function Pt(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 Lt(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 Nt=Object.keys(f).filter(e=>"unknown"!==e&&"always_on"!==e);class Tt 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 s=document.createElement("div");s.className="panel",t.appendChild(s),e.panelMode?this._renderPanelMode(s):e.subDeviceMode?this._renderSubDeviceMode(s,e):this._renderCircuitMode(s,e)}_renderPanelMode(e){const t=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.global_defaults"));e.appendChild(n);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,h=c?.circuits??{},p=document.createElement("div");p.className="section";const g=document.createElement("div");g.className="section-label",g.textContent=i("sidepanel.graph_horizon"),p.appendChild(g);const _=document.createElement("div");_.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=i("sidepanel.global_default"),_.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s: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}`))}),_.appendChild(m),p.appendChild(_),s.appendChild(p),l?.circuits){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=i("sidepanel.circuit_scales"),e.appendChild(t);const n=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,s]of n){const n=document.createElement("div");n.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=s.name||t,o.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",n.appendChild(o);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(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`circuit-${t}`,u,()=>{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}`))})}),n.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=i("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}`))}),n.appendChild(e)}e.appendChild(n)}s.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=i("sidepanel.subdevice_scales"),e.appendChild(t);const n=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[t,s]of n){const n=document.createElement("div");n.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=s.name||t,o.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",n.appendChild(o);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(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===c&&(t.selected=!0),l.appendChild(t)}if(l.addEventListener("change",()=>{this._debounce(`subdev-${t}`,u,()=>{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}`))})}),n.appendChild(l),r.has_override){const e=document.createElement("button");e.textContent="↺",e.title=i("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}`))}),n.appendChild(e)}e.appendChild(n)}s.appendChild(e)}e.appendChild(s)}_renderCircuitMode(e,t){const n=`${Le(String(t.breaker_rating_a))}A · ${Le(String(t.voltage))}V · Tabs [${Le(String(t.tabs))}]`,i=this._createHeader(Le(t.name),n);e.appendChild(i);const s=document.createElement("div");s.className="panel-body",e.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,t),this._renderSheddingSection(s,t),this._renderGraphHorizonSection(s,t),t.showMonitoring&&this._renderMonitoringSection(s,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(Le(t.name),Le(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i);const s=document.createElement("div");s.className="error-msg",s.id="error-msg",s.style.display="none",i.appendChild(s),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceHorizonSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=i("sidepanel.graph_horizon"),n.appendChild(s);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:i("sidepanel.global")}];for(const e of Object.keys(a))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:n}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=n,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const n=t.subDeviceId;"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",{subdevice_id:n}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${i("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_subdevice_graph_horizon",{subdevice_id:n,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${i("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(s)}n.appendChild(h),e.appendChild(n)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),s=Le(e),o=Le(t);i.innerHTML=`
${s}
`+(o?`
${o}
`:"");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 n=document.createElement("div");n.className="section",n.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("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(`${i("sidepanel.relay_failed")} ${e.message??e}`))}),s.appendChild(o),s.appendChild(a),n.appendChild(s),e.appendChild(n)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("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 Nt){const t=f[e];if(!t)continue;const n=document.createElement("option");n.value=e,n.textContent=i(`shedding.select.${e}`)||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(`${i("sidepanel.shedding_failed")} ${e.message??e}`))}),s.appendChild(o),s.appendChild(a),n.appendChild(s),e.appendChild(n)}_renderGraphHorizonSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=i("sidepanel.graph_horizon"),n.appendChild(s);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:i("sidepanel.global")}];for(const e of Object.keys(a))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:n}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=n,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const n=t.uuid;"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",{circuit_id:n}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${i("sidepanel.clear_graph_horizon_failed")} ${e.message??e}`))):(g(e),this._callDomainService("set_circuit_graph_horizon",{circuit_id:n,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>this._showError(`${i("sidepanel.graph_horizon_failed")} ${e.message??e}`)))}),h.appendChild(s)}n.appendChild(h),e.appendChild(n)}_renderMonitoringSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=i("sidepanel.monitoring"),o.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",""),s.appendChild(o),s.appendChild(a),n.appendChild(s);const l=document.createElement("div");l.dataset.role="monitoring-details",l.style.display=c?"block":"none",n.appendChild(l);const d=!0===r?.has_override,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,f=r?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(i("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(i("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(i("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),p.appendChild(this._createDurationRow(i("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),l.appendChild(p),a.addEventListener("change",()=>{const e=a.checked;l.style.display=e?"block":"none";const n=t.entities?.power||t.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:n,monitoring_enabled:e}).catch(e=>this._showError(`${i("sidepanel.monitoring_toggle_failed")} ${e.message??e}`))});const m=h.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e=t.entities?.power||t.uuid;this._callDomainService("clear_circuit_threshold",{circuit_id:e}).catch(e=>this._showError(`${i("sidepanel.clear_monitoring_failed")} ${e.message??e}`))}});e.appendChild(n)}_createThresholdRow(e,t,n,s){const o=document.createElement("div");o.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-${t}`,r.addEventListener("input",()=>{this._debounce(`threshold-${t}`,u,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),n=e.querySelector('[data-role="threshold-spike"]'),o=e.querySelector('[data-role="threshold-window-m"]'),a=e.querySelector('[data-role="threshold-cooldown-m"]'),r=s.entities?.power||s.uuid;this._callDomainService("set_circuit_threshold",{circuit_id:r,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:o?Number(o.value):void 0,cooldown_duration_m:a?Number(a.value):void 0}).catch(e=>this._showError(`${i("sidepanel.save_threshold_failed")} ${e.message??e}`))})}),o.appendChild(a),o.appendChild(r),o}_createDurationRow(e,t,n,s,o,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(s),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${t}`,c&&(p.disabled=!0);const g=document.createElement("span");return g.textContent=a,h.appendChild(p),h.appendChild(g),c||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,u,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),n=e.querySelector('[data-role="threshold-spike"]'),s=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:n?Number(n.value):void 0,window_duration_m:s?Number(s.value):void 0}).catch(e=>this._showError(`${i("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:r,service:e,service_data:t}):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)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",Tt)}catch{}const Dt=[{name:"Kitchen",watts:"120",path:"M0,28 L8,26 L16,24 L24,22 L32,25 L40,20 L48,18 L56,22 L64,19 L72,16 L80,18 L88,15 L96,17 L104,14 L112,16 L120,13"},{name:"Living Room",watts:"85",path:"M0,22 L8,24 L16,20 L24,26 L32,18 L40,22 L48,16 L56,20 L64,24 L72,18 L80,22 L88,20 L96,16 L104,22 L112,18 L120,20"},{name:"Master Bed",watts:"193",path:"M0,8 L8,10 L16,8 L24,12 L32,10 L40,8 L48,10 L56,8 L64,10 L72,8 L80,12 L88,10 L96,8 L104,10 L112,8 L120,10"},{name:"HVAC",watts:"64",path:"M0,30 L8,28 L16,26 L24,22 L32,18 L40,14 L48,18 L56,22 L64,26 L72,22 L80,18 L88,22 L96,26 L104,22 L112,18 L120,22"}];let Ht=class extends $e{constructor(){super(...arguments),this._config={},this._discovered=!1,this._discovering=!1,this._discoveryError=null,this._topology=null,this._activeTab="panel",this._panelDevice=null,this._panelSize=0,this._historyLoaded=!1,this._ctrl=new bt,this._listCtrl=new At(this._ctrl),this._areaUnsub=null,this._tabBarCleanup=null,this._onVisibilityChange=null}get _configEntryId(){return this._panelDevice?.config_entries?.[0]??null}connectedCallback(){super.connectedCallback(),this._ctrl.startIntervals(this.shadowRoot),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&(this._ctrl.recordSamples(),this._ctrl.updateDOM(this.shadowRoot))},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._ctrl.stopIntervals(),this._listCtrl.stop(),this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._tabBarCleanup&&(this._tabBarCleanup(),this._tabBarCleanup=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),super.disconnectedCallback()}setConfig(e){this._config=e,this._discovered=!1,this._discovering=!1,this._historyLoaded=!1,this._discoveryError=null,this._topology=null,this._panelDevice=null,this._panelSize=0,this._activeTab="panel",this._ctrl.reset(),this._ctrl.setConfig(e)}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:s,show_panel:!0,show_battery:!0,show_evse:!0}}render(){return n(this.hass?.language),this._config.device_id?this._discovered?re` + */function Me(e){return Ne({...e,state:!0,attribute:!1})}const Le={"&":"&","<":"<",">":">",'"':""","'":"'"};function Ie(e){return String(e).replace(/[&<>"']/g,e=>Le[e]??e)}const De="span_panel_list_columns";function Te(){try{const e=localStorage.getItem(De);if(!e)return 1;const t=parseInt(e,10);return 1===t||2===t||3===t?t:1}catch{return 1}}function He(e){try{localStorage.setItem(De,String(e))}catch{}}function Oe(e,t,n={}){const s=Ie(e.device_name||i("header.default_name")),o=Ie(e.serial||""),r=Ie(e.firmware||""),a="current"===(t.chart_metric||"power"),l=!1!==n.showSwitches;return`\n
\n
\n
\n

${s}

\n ${o}\n \n ${l?`
\n ${Ie(i("header.enable_switches"))}\n
\n \n
\n
`:""}\n
\n ${function(e,t){const n="current"===(t.chart_metric||"power"),s=!!e.panel_entities?.site_power,o=!!e.panel_entities?.dsm_state,r=!!e.panel_entities?.current_power,a=!!e.panel_entities?.feedthrough_power,l=!!e.panel_entities?.pv_power,c=!!e.panel_entities?.battery_level;return`\n
\n ${s?`\n
\n ${i("header.site")}\n
\n 0\n ${n?"A":"kW"}\n
\n
`:""}\n ${o?`\n
\n ${i("header.grid")}\n
\n --\n
\n
`:""}\n ${r?`\n
\n ${i("header.upstream")}\n
\n --\n ${n?"A":"kW"}\n
\n
`:""}\n ${a?`\n
\n ${i("header.downstream")}\n
\n --\n ${n?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${i("header.solar")}\n
\n --\n ${n?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${i("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n `}(e,t)}\n
\n
\n
\n ${r}\n
\n \n \n
\n
\n
\n ${Object.entries(m).filter(([e])=>"unknown"!==e).map(([,e])=>{const t=Ie(e.icon),n=Ie(e.color),i=Ie(e.label());let s;return s=e.icon2?``:e.textLabel?`${Ie(e.textLabel)}`:``,`
${s}${i}
`}).join("")}\n
\n
\n
\n `}const Fe=_.power;function Re(e){return Fe.unit(e)}function je(e){return(e<0?"-":"")+Fe.format(e)}function Ue(e){return(Math.abs(e)/1e3).toFixed(1)}function qe(e){return Math.ceil(e/2)}function We(e){return e%2==0?1:0}function Be(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return qe(t)===qe(n)?"row-span":We(t)===We(n)?"col-span":"row-span"}function Ge(e){const t=e.chart_metric??o;return _[t]??_[o]}function Ve(e,t){const n=function(e){return Ge(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}function Qe(e){return new Promise(t=>setTimeout(t,e))}class Ke{constructor(e){this._store=e}async callWS(e,t,n){const i=n?.retries??3,s=n?.errorId??`ws:${String(t.type??"unknown")}`;return this._withRetry(()=>e.callWS(t),i,s,n?.errorMessage)}async callService(e,t,n,i,s,o){const r=o?.retries??3,a=o?.errorId??`svc:${t}.${n}`;return this._withRetry(()=>e.callService(t,n,i,s),r,a,o?.errorMessage)}async _withRetry(e,t,n,s){if(this._store.hasAnyPanelOffline())try{const t=await e();return this._store.remove(n),t}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this._store.add({key:n,level:"error",message:s??i("error.panel_offline"),persistent:!1}),t}let o;for(let i=0;i<=t;i++)try{const t=await e();return this._store.remove(n),t}catch(e){if(o=e instanceof Error?e:new Error(String(e)),i{try{const n={};t&&(n.config_entry_id=t);const o={type:"call_service",domain:l,service:"get_monitoring_status",service_data:n,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:i("error.monitoring_failed")}):await e.callWS(o),a=r?.response??null;return s===this._generation&&(this._status=a,this._lastFetch=Date.now()),a}catch(e){return console.warn("SPAN Panel: monitoring status fetch failed",e),s===this._generation&&(this._status=null),this._retry||this._errorStore?.add({key:"fetch:monitoring",level:"warning",message:i("error.monitoring_failed"),persistent:!1}),null}finally{this._inflight?.gen===s&&(this._inflight=null)}})();return this._inflight={gen:s,promise:o},o}invalidate(){this._lastFetch=0,this._generation++}get status(){return this._status}clear(){this._status=null,this._lastFetch=0,this._generation++}}class Xe{constructor(){this._caches=new Map,this._errorStore=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e;for(const t of this._caches.values())t.errorStore=e}async fetchOne(e,t){let n=this._caches.get(t);return n||(n=new Je,n.errorStore=this._errorStore,this._caches.set(t,n)),n.fetch(e,t)}invalidate(){for(const e of this._caches.values())e.invalidate()}clear(){this._caches.clear()}}function Ze(e,t){return e?.circuits?e.circuits[t]??null:null}function Ye(e){return!!e&&void 0!==e.continuous_threshold_pct}function et(e,t,n,i){const s=[];return n||s.push("circuit-off"),i&&s.push("circuit-producer"),function(e){return!!e&&null!=e.over_threshold_since}(t)&&s.push("circuit-alert"),Ye(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function tt(e,t,n,s,o,r,a,l,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,_=g&&parseFloat(g.state)||0,f=t.device_type===d||_<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===c,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",C=Ie(t.name||i("grid.unknown")),$=Ge(a);let E;if("current"===$.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;E=`${$.format(i)}A`}else E=`${je(_)}${Re(_)}`;const k=h||"unknown";let z="";if("unknown"!==k){const e=m[k]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Ie(e.label()),n=Ie(e.icon),i=Ie(e.color);if(e.icon2){z=`\n \n \n `}else if(e.textLabel){z=`\n \n ${Ie(e.textLabel)}\n `}else z=``}const P=l&&Ye(l)?v:"#555",A=``;let N="",M=l?.utilization_pct??null;if(null==M&&t.breaker_rating_a){const e=t.entities?.current,n=e?r.states[e]:null,i=n?Math.abs(parseFloat(n.state)||0):0;M=Math.round(i/t.breaker_rating_a*1e3)/10}if(null!=M){N=`=80?"utilization-warning":"utilization-normal"}">${Math.round(M)}%`}return`\n
\n
\n
\n ${S?`${S}`:""}\n ${N}\n ${C}\n
\n
\n \n ${E}\n \n ${!1!==t.is_user_controllable&&t.entities?.switch?`\n
\n ${i(w?"grid.on":"grid.off")}\n \n
\n `:""}\n
\n
\n
\n ${z}\n ${A}\n
\n
\n
\n `}function nt(e,t){return`\n
\n \n
\n `}const it={names:["power","battery power"],suffixes:["_power"]},st={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},ot={names:["state of energy"],suffixes:["_soe_kwh"]},rt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function at(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 lt(e){return at(e,it)}function ct(e){return at(e,st)}function dt(e){return at(e,ot)}function ht(e){return at(e,rt)}function pt(e,t,n,i){const s=n.visible_sub_entities||{};let o="";if(!e.entities)return o;for(const[n,r]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const a=t.states[n];if(!a)continue;let l=r.original_name||a.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(a);else{d=a.state;const e=a.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(a.attributes.unit_of_measurement||"")){const e=parseFloat(a.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${Ie(l)}:\n ${Ie(d)}\n
\n `}return o}function ut(e,t,n,s,o,r){if(n){const t=[{key:`${u}${e}_soc`,title:i("subdevice.soc"),available:!!o},{key:`${u}${e}_soe`,title:i("subdevice.soe"),available:!!r},{key:`${u}${e}_power`,title:i("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Ie(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function gt(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 _t(e){const t=a[e];return t?t.ms:a[r].ms}function ft(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function mt(e){return Math.max(500,Math.floor(e/5e3))}function vt(e,t,n,i,s,o){e.has(t)||e.set(t,[]);const r=e.get(t);r.push({time:i,value:n});const a=r.findIndex(e=>e.time>=s);a>0?r.splice(0,a):-1===a&&(r.length=0),r.length>o&&r.splice(0,r.length-o)}function bt(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 yt(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),r=i/36e5>72?"hour":"5minute",a=await e.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:t,period:r,types:["mean"]});for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const o=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];t.sort((e,t)=>e.time-t.time),s.set(i,t)}}}async function wt(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),r=await e.callWS({type:"history/history_during_period",start_time:o,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),a=ft(i),l=mt(i);for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const o=[];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&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];s.set(i,bt(t,a,l))}}}function xt(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:lt(i)};i.type===h&&(e.soc=ct(i),e.soe=dt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${u}${n}_${i}`,devId:n})}return t}async function St(e,t,n,i,s,o){if(!t||!e)return;const r=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=Ve(i,n);if(!t)continue;let o;o=s&&s.has(e)?_t(s.get(e)):gt(n),r.has(o)||r.set(o,{entityIds:[],uuidByEntity:new Map});const a=r.get(o);a.entityIds.push(t),a.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:s}of xt(t)){let t;t=o&&o.has(s)?_t(o.get(s)):gt(n),r.has(t)||r.set(t,{entityIds:[],uuidByEntity:new Map});const a=r.get(t);a.entityIds.push(e),a.uuidByEntity.set(e,i)}const a=[];for(const[t,n]of r){if(0===n.entityIds.length)continue;t>2592e5?a.push(yt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(wt(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function Ct(e,t,n,i,s,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,s,r=!1){n||(n=_[o]);const a=i?"140, 160, 220":"77, 217, 175",l=`rgb(${a})`,c=Date.now(),d=c-t,h=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=(e??[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),u=[{type:"line",data:p,showSymbol:!1,smooth:!1,...r?{}:{step:"end"},lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.18)`},{offset:1,color:`rgba(${a}, 0.18)`}]}},itemStyle:{color:l}}],g=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):0,f={type:"value",splitNumber:4,axisLabel:{fontSize:10,formatter:g<10?e=>0===e?"0":e.toFixed(1):e=>n.format(e)},splitLine:{lineStyle:{opacity:.15}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):g<1&&(f.min=0,f.max=1),s&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*s),u.push({type:"line",data:[[d,.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}}),u.push({type:"line",data:[[d,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const m={xAxis:{type:"time",min:d,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||0===e.length)return"";const t=e[0],i=new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),s=parseFloat(t.value[1].toFixed(2));return`
${i}
${n.format(s)} ${n.unit(s)}
`}},animation:!1};return{options:m,series:u}}(n,i,s,r,l,c),p=a??120;e.style.minHeight=p+"px";let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.hass=t,e.innerHTML="",e.appendChild(u));const g=e.clientHeight;u.height=(g>0?g:p)+"px",u.hass=t,u.options=d,u.data=h}function $t(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Et(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&function(e,t,n,i,s){const o="current"===(i.chart_metric||"power"),r=e.querySelector(".stat-consumption .stat-value"),a=e.querySelector(".stat-consumption .stat-unit");if(o){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,s=i?parseFloat(i.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),a&&(a.textContent="A")}else{let e=s;const i=n.panel_entities?.site_power;if(i){const n=t.states[i];n&&(e=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=Ue(e)),a&&(a.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(o){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=Ue(e),c&&(c.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(o){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=Ue(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(o){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=Ue(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:"--"}}(o,t,n,i,s)}class kt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new Ke(e):null}constructor(){this._errorStore=null,this._retry=null,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 n={};t&&(n.config_entry_id=t);const s={type:"call_service",domain:l,service:"get_graph_settings",service_data:n,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await e.callWS(s);this._settings=o?.response??null,this._lastFetch=Date.now()}catch(e){console.warn("SPAN Panel: graph settings fetch failed",e),this._settings=null,this._retry||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function zt(e,t){if(!e)return r;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??r}function Pt(e,t){if(!e)return r;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??r}class At{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new Je,this.monitoringMultiCache=new Xe,this.graphSettingsCache=new kt,this._errorStore=null,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._favRefs=null,this._perPanelInfo=new Map,this._panelFavorites=null,this._showMonitoring=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this.monitoringCache.errorStore=e,this.graphSettingsCache.errorStore=e,this.monitoringMultiCache.errorStore=e}get hass(){return this._hass}set hass(e){this._hass=e}get topology(){return this._topology}get config(){return this._config}set showMonitoring(e){this._showMonitoring=e}init(e,t,n,i){this._topology=e,this._config=t,this._hass=n,this._configEntryId=i}setFavoriteRefs(e){this._favRefs=e}clearFavoriteRefs(){this._favRefs=null}setPanelFavorites(e){this._panelFavorites=e}setFavoritesPerPanelInfo(e){this._perPanelInfo=e??new Map}get _inFavoritesView(){return null!==this._favRefs}setConfig(e){this._config=e}buildHorizonMaps(e){if(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,zt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Pt(e,t))}async fetchAndBuildHorizonMaps(){try{this._favRefs?await this._buildFavoritesHorizonMaps():(await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings))}catch(e){console.warn("SPAN Panel: graph settings fetch failed",e),this.graphSettingsCache.errorStore||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:i("error.graph_settings_failed"),persistent:!1})}}async fetchMergedMonitoringStatus(e){if(!this._hass||0===e.length)return null;const t=this._hass;return function(e){let t=!1;const n={},i={};for(const s of e)s&&(t=!0,s.circuits&&Object.assign(n,s.circuits),s.mains&&Object.assign(i,s.mains));return t?{circuits:n,mains:i}:null}(await Promise.all(e.map(e=>this.monitoringMultiCache.fetchOne(t,e))))}async _buildFavoritesHorizonMaps(){if(!this._hass||!this._favRefs||!this._topology)return;const e=new Set;for(const t of Object.values(this._favRefs))t.configEntryId&&e.add(t.configEntryId);const t=new Map;await Promise.all(Array.from(e).map(async e=>{t.set(e,await this._fetchGraphSettingsFresh(e))})),this.horizonMap.clear(),this.subDeviceHorizonMap.clear();for(const e of Object.keys(this._topology.circuits)){const n=this._favRefs[e],i=n?.configEntryId?t.get(n.configEntryId)??null:null,s=n?.targetId??e;this.horizonMap.set(e,zt(i,s))}if(this._topology.sub_devices)for(const e of Object.keys(this._topology.sub_devices)){const n=this._favRefs[e],i=n?.configEntryId?t.get(n.configEntryId)??null:null,s=n?.targetId??e;this.subDeviceHorizonMap.set(e,Pt(i,s))}}async loadHistory(){await St(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)??r;if(!a[i]?.useRealtime)continue;const s=Ve(n,this._config);if(!s)continue;const o=this._hass.states[s];if(!o)continue;const l=parseFloat(o.state);if(isNaN(l))continue;const c=_t(i),d=ft(c),h=mt(c),p=e-c,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time0&&this._topology)for(const{key:e,devId:t}of xt(this._topology))n.has(t)&&s.add(e);const o=new Map;try{await St(this._hass,this._topology,this._config,o,t,n);for(const e of t.keys()){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}for(const e of s){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}this.updateDOM(e)}catch(e){console.warn("SPAN Panel: history refresh failed",e),this._errorStore?.add({key:"fetch:history",level:"warning",message:i("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(function(e,t,n,s,o,r){if(!e||!n||!t)return;const a=gt(s);let l=0;for(const[,e]of Object.entries(n.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==d&&(l+=Math.abs(s))}Et(e,t,n,s,l);const h=Ge(s),p="current"===h.entityRole;for(const[s,l]of Object.entries(n.circuits)){const n=e.querySelector(`.circuit-slot[data-uuid="${$t(s)}"]`);if(!n)continue;const u=l.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,f=l.device_type===d||_<0,v=l.entities?.switch,b=v?t.states[v]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||l.relay_state)===c,w=n.querySelector(".power-value");if(w)if(p){const e=l.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${je(_)}${Re(_)}`;const x=n.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=i(y?"grid.on":"grid.off"))}let S;if(n.classList.toggle("circuit-off",!y),n.classList.toggle("circuit-producer",f),l.always_on)S="always_on";else{const e=l.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const C=m[S]??m.unknown,$=n.querySelector(".shedding-icon");$&&($.setAttribute("icon",C.icon),$.style.color=C.color,$.title=C.label());const E=n.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 k=n.querySelector(".shedding-label");k&&(C.textLabel?(k.textContent=C.textLabel,k.style.color=C.color,k.style.display=""):k.style.display="none");const z=n.querySelector(".chart-container");if(z){const e=o.get(s)||[],i=n.classList.contains("circuit-col-span")?200:100,c=r?.has(s)?_t(r.get(s)):a,p=l.device_type===d;Ct(z,t,e,c,h,f,i,l.breaker_rating_a??void 0,p)}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(e,t,n,i,s,o){if(!n.sub_devices)return;const r=gt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${$t(i)}"]`);if(!n)continue;const l=lt(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${je(i)} ${Re(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey;if(!n)continue;const a=s.get(n)||[];let l=f.power;n.endsWith("_soc")?l=f.soc:n.endsWith("_soe")&&(l=f.soe);const c=!!e.closest(".bess-chart-col");Ct(e,t,a,o?.has(i)?_t(o.get(i)):r,l,!1,c?120:150,void 0,n.endsWith("_soc")||n.endsWith("_soe"))}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${$t(e)}"]`);if(!i)continue;const s=t.states[e];if(s){let e;if(t.formatEntityState)e=t.formatEntityState(s);else{e=s.state;const t=s.attributes.unit_of_measurement||"";t&&(e+=" "+t)}if("Wh"===(s.attributes.unit_of_measurement||"")){const t=parseFloat(s.state);isNaN(t)||(e=(t/1e3).toFixed(1)+" kWh")}i.textContent=e}}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(e){if(this._hass){this._favRefs?await this._buildFavoritesHorizonMaps():(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,s=n?.closest(".toggle-pill");if(!s)return;const o=t.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const r=s.closest("[data-uuid]");if(!r||!this._topology||!this._hass)return;const a=r.dataset.uuid;if(!a)return;const l=this._topology.circuits[a];if(!l)return;const c=l.entities?.switch;if(!c)return;const d=this._hass.states[c];if(!d)return void console.warn("SPAN Panel: switch entity not found:",c);const h="on"===d.state?"turn_off":"turn_on";this._hass.callService("switch",h,{},{entity_id:c}).catch(e=>{console.warn("SPAN Panel: switch service call failed",e),this._errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}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,s.errorStore=this.errorStore,i.classList.contains("panel-gear")){if(this._inFavoritesView){const e=await this._buildFavoritesSections();if(0===e.length)return;return void s.open({favoritesMode:!0,perPanelSections:e})}return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings,showFavorites:null!==this._panelFavorites,favoritePanelDeviceId:this._panelFavorites?.panelDeviceId,favoriteCircuitUuids:this._panelFavorites?.circuitUuids,favoriteSubDeviceIds:this._panelFavorites?.subDeviceIds,configEntryId:this._configEntryId})}const o=i.dataset.uuid;if(o&&this._topology){const e=this._topology.circuits[o];if(e){const t=this._favRefs?.[o]??null,n=t&&"circuit"===t.kind?t.targetId:o,i=t?.configEntryId??this._configEntryId;let a,l;t?[a,l]=await Promise.all([this._fetchGraphSettingsFresh(i),this._fetchMonitoringStatusFresh(i)]):(await Promise.all([this.graphSettingsCache.fetch(this._hass,i),this.monitoringCache.fetch(this._hass,i)]),a=this.graphSettingsCache.settings,l=this.monitoringCache.status);const c=e.entities?.current??e.entities?.power,d=c?l?.circuits?.[c]??null:null,h=a?.global_horizon??r,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,_=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),f=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:f,favoritePanelDeviceId:g,isFavorite:_,configEntryId:i})}}const a=i.dataset.subdevId;if(a&&this._topology?.sub_devices?.[a]){const e=this._topology.sub_devices[a],t=this._favRefs?.[a]??null,n=t&&"sub_device"===t.kind?t.targetId:a,i=t?.configEntryId??this._configEntryId;let o;t?o=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),o=this.graphSettingsCache.settings);const l=o?.global_horizon??r,c=o?.sub_devices?.[n],d=c?{...c,globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l},h=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,p=null!==t||(this._panelFavorites?.subDeviceIds.has(n)??!1),u=this._inFavoritesView||null!==this._panelFavorites;s.open({subDeviceMode:!0,subDeviceId:n,name:e.name??n,deviceType:e.type??"",entities:e.entities,graphHorizonInfo:d,showFavorites:u,favoritePanelDeviceId:h,isFavorite:p,configEntryId:i})}}async _buildFavoritesSections(){if(!this._hass||!this._favRefs)return[];const e=function(e,t){const n=new Map;for(const i of Object.values(e)){if("circuit"!==i.kind)continue;const e=t.get(i.panelDeviceId);if(void 0===e)continue;let s=n.get(i.panelDeviceId);void 0===s&&(s={panelDeviceId:i.panelDeviceId,panelName:e.panelName,topology:e.topology,configEntryId:e.configEntryId,favoriteCircuitUuids:new Set},n.set(i.panelDeviceId,s)),s.favoriteCircuitUuids.add(i.targetId)}return Array.from(n.values()).sort((e,t)=>e.panelName.localeCompare(t.panelName))}(this._favRefs,this._perPanelInfo);if(0===e.length)return[];return await Promise.all(e.map(async e=>({panelDeviceId:e.panelDeviceId,panelName:e.panelName,topology:e.topology,graphSettings:await this._fetchGraphSettingsFresh(e.configEntryId),favoriteCircuitUuids:e.favoriteCircuitUuids,configEntryId:e.configEntryId})))}async _fetchGraphSettingsFresh(e){if(!this._hass)return null;try{const t={};e&&(t.config_entry_id=e);const n={type:"call_service",domain:l,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new Ke(this._errorStore):null,o=s?await s.callWS(this._hass,n,{errorId:"fetch:graph_settings",errorMessage:i("error.graph_settings_failed")}):await this._hass.callWS(n);return o?.response??null}catch(e){return console.warn("SPAN Panel: fresh graph settings fetch failed",e),null}}async _fetchMonitoringStatusFresh(e){if(!this._hass)return null;try{const t={};e&&(t.config_entry_id=e);const n={type:"call_service",domain:l,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new Ke(this._errorStore):null,o=s?await s.callWS(this._hass,n,{errorId:"fetch:monitoring",errorMessage:i("error.monitoring_failed")}):await this._hass.callWS(n),r=o?.response;return r?{circuits:r.circuits,mains:r.mains}:null}catch(e){return console.warn("SPAN Panel: fresh monitoring status fetch failed",e),null}}bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n||!i)return;let s=!1,o=0,r=0;const a=t=>{e.classList.contains("confirmed")||(s=!0,o=t-n.offsetLeft,r=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!s)return;const t=Math.max(2,Math.min(e-o,r));n.style.left=t+"px"},c=()=>{if(!s)return;s=!1;(n.offsetLeft-2)/r>=.9?(n.style.left=r+"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(),a(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),a(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"))})}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.monitoringMultiCache.clear(),this.graphSettingsCache.clear()}}function Nt(e=""){const t=e?` value="${Ie(e)}"`:"",n=e?"":"display:none;";return`\n
\n \n \n
\n `}function Mt(e,t,n,s,o,r,a){const l=t.entities?.power,d=l?n.states[l]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?n.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===c,_=t.breaker_rating_a,f=_?`${Math.round(_)}A`:"",b=Ie(t.name||i("grid.unknown")),y=Ge(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,i=e?n.states[e]:null,s=i&&parseFloat(i.state)||0;x=`${y.format(s)}A`}else x=`${je(h)}${Re(h)}`;else x="";const S=r||"unknown";let C="";if("unknown"!==S){const e=m[S]??m.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};C=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let $="",E=o?.utilization_pct??null;if(null==E&&t.breaker_rating_a){const e=t.entities?.current,i=e?n.states[e]:null,s=i?Math.abs(parseFloat(i.state)||0):0;E=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=E){$=`=80?"utilization-warning":"utilization-normal"}">${Math.round(E)}%`}const k=!!o&&Ye(o)?v:"#555",z=``,P=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${i(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${f?`${f}`:""}\n ${$}\n ${b}\n ${C}\n ${P}\n \n ${x}\n \n ${z}\n \n
\n `}function Lt(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,l=t.device_type===d||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=et(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===c,l),g=Ie(e);return`\n
\n
\n
\n
\n
\n `}function It(e){return`
${Ie(e)}
`}function Dt(e,t,n){const i=e.entities?.switch,s=i?t.states[i]:null,o=e.entities?.power,r=o?t.states[o]:null,a=s?"on"===s.state:(r?.attributes?.relay_state||e.relay_state)===c;let l;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;l=i?Math.abs(parseFloat(i.state)||0):0}else l=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:l}}function Tt(e,t){if(e.always_on)return"always_on";const n=e.entities?.select,i=n?t.states[n]:null;return i?i.state:"unknown"}function Ht(e,t,n,i){const s=Dt(e,n,i),o=Dt(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function Ot(e,t,n){return e.sort((e,i)=>Ht(e[1],i[1],t,n))}function Ft(e){return e.entities?.current??e.entities?.power??""}class Rt{constructor(e){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._viewName=null,this._columns=1,this._ctrl=e}setColumns(e){const t=Math.max(1,Math.min(3,Math.floor(e)));this._columns=t}setInitialExpansion(e){this._expandedUuids=new Set(e)}setInitialSearchQuery(e){this._searchQuery=e}setViewName(e){this._viewName=e}renderActivityView(e,t,n,i,s,o){this._unbindEvents(),this._hass=t,this._topology=n,this._config=i,this._monitoringStatus=s;const r=Ot(Object.entries(n.circuits),t,i);let a=o+Nt(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=Ze(s,Ft(n)),r=Tt(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=Mt(e,n,t,i,o,r,l),l&&(a+=Lt(e,n,t,0,o)),a+="
"}a+="
",a+="",e.innerHTML=a;const l=e.querySelector("span-side-panel");l&&(l.hass=t,l.errorStore=this._ctrl.errorStore),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}renderAreaView(e,t,n,s,o,r){this._unbindEvents(),this._hass=t,this._topology=n,this._config=s,this._monitoringStatus=o;const a=i("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(n.circuits)){const n=t.area??a,i=l.get(n);i?i.push([e,t]):l.set(n,[[e,t]])}const c=[...l.keys()].sort((e,t)=>e===a?1:t===a?-1:e.localeCompare(t));let d=r+Nt(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=Ot(n,t,s);d+=It(e);for(const[e,n]of i){const i=Ze(o,Ft(n)),r=Tt(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=Mt(e,n,t,s,i,r,a),a&&(d+=Lt(e,n,t,0,i)),d+="
"}}d+="
",d+="",e.innerHTML=d;const h=e.querySelector("span-side-panel");h&&(h.hass=t,h.errorStore=this._ctrl.errorStore),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}updateCollapsedRows(e,t,n,s){const o=Ge(s),r="current"===o.entityRole,a=e.querySelectorAll(".list-row[data-row-uuid]");for(const e of a){const a=e.dataset.rowUuid;if(!a)continue;const l=n.circuits[a];if(!l)continue;const{isOn:c,value:d}=Dt(l,t,s),h=e.querySelector(".list-power-value");if(h)if(c)if(r)h.innerHTML=`${o.format(d)}A`;else{const e=l.entities?.power,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;h.innerHTML=`${je(i)}${Re(i)}`}else h.innerHTML="";const p=e.querySelector(".toggle-pill");if(p){p.classList.toggle("toggle-on",c),p.classList.toggle("toggle-off",!c);const e=p.querySelector(".toggle-label");e&&(e.textContent=i(c?"grid.on":"grid.off"))}const u=e.querySelector(".list-status-badge");u&&(u.textContent=c?"ON":"OFF",u.classList.toggle("list-status-on",c),u.classList.toggle("list-status-off",!c)),e.classList.toggle("circuit-off",!c)}!function(e,t,n,i){const s=e.querySelector(".list-view");if(s)for(const e of function(e,t){let n={anchor:null,units:[]};const i=[n];for(const s of[...e.children])if(s.classList.contains("area-header"))n={anchor:s,units:[]},i.push(n);else if(s.classList.contains("list-cell")){const e=s.dataset.cellUuid,i=e?t.circuits[e]:void 0;e&&i&&n.units.push({cell:s,uuid:e,circuit:i})}return i}(s,n)){if(e.units.length<2)continue;const n=[...e.units].sort((e,n)=>Ht(e.circuit,n.circuit,t,i));if(!n.some((t,n)=>t.uuid!==e.units[n].uuid))continue;let o=e.anchor;for(const e of n)o?o.after(e.cell):s.prepend(e.cell),o=e.cell}}(e,t,n,s)}stop(){this._unbindEvents(),null===this._viewName&&(this._expandedUuids.clear(),this._searchQuery=""),this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_dispatchFavoritesViewState(){if(!this._viewName||!this._container)return;const e={view:this._viewName,expanded:[...this._expandedUuids],searchQuery:this._searchQuery};this._container.dispatchEvent(new CustomEvent("favorites-view-state-changed",{detail:e,bubbles:!0,composed:!0}))}_bindEvents(e){this._container=e,this._clickHandler=t=>{const n=t.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const e=i.dataset.expandUuid;return void(e&&this._toggleExpand(e))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(t,e);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(t,e);if(n.closest(".list-search-clear")){const t=e.querySelector(".list-search");return void(t&&(t.value="",t.dispatchEvent(new Event("input",{bubbles:!0}))))}const s=n.closest(".unit-btn");if(s){const t=s.dataset.unit;t&&e.dispatchEvent(new CustomEvent("unit-changed",{detail:t,bubbles:!0,composed:!0}))}},this._inputHandler=t=>{const n=t.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(e),this._dispatchFavoritesViewState())},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(e).then(()=>{this._ctrl.updateDOM(e)}).catch(()=>{})},e.addEventListener("click",this._clickHandler),e.addEventListener("input",this._inputHandler),e.addEventListener("graph-settings-changed",this._graphSettingsHandler);const t=e.querySelector(".slide-confirm");t&&(this._ctrl.bindSlideConfirm(t,e),e.classList.add("switches-disabled"))}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_applyFilter(e){const t=e.querySelector(".list-search-clear");t&&(t.style.display=this._searchQuery?"":"none");const n=e.querySelectorAll(".list-cell[data-cell-uuid]");for(const e of n){const t=e.querySelector(".list-circuit-name"),n=(t?.textContent?.toLowerCase()??"").includes(this._searchQuery);e.style.display=n?"":"none"}const i=e.querySelectorAll(".area-header");for(const e of i){let t=!1,n=e.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-cell")&&"none"!==n.style.display){t=!0;break}n=n.nextElementSibling}e.style.display=t?"":"none"}}_toggleExpand(e){if(!(this._container&&this._hass&&this._topology&&this._config))return;const t=$t(e),n=this._container.querySelector(`.list-cell[data-cell-uuid="${t}"]`);if(!n)return;const i=n.querySelector(`.list-row[data-row-uuid="${t}"]`),s=n.querySelector(`.list-expand-toggle[data-expand-uuid="${t}"]`);if(i){if(this._expandedUuids.has(e)){this._expandedUuids.delete(e);const o=n.querySelector(`.list-expanded-content[data-expanded-uuid="${t}"]`);o&&o.remove(),s&&s.classList.remove("expanded"),i.classList.remove("list-row-expanded")}else{this._expandedUuids.add(e);const t=this._topology.circuits[e];if(!t)return;const n=Ze(this._monitoringStatus,Ft(t)),o=Lt(e,t,this._hass,this._config,n);i.insertAdjacentHTML("afterend",o),s&&s.classList.add("expanded"),i.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}this._dispatchFavoritesViewState()}}}async function jt(e,t){const[n,i,s]=await Promise.all([e.callWS({type:"config/area_registry/list"}),e.callWS({type:"config/entity_registry/list"}),e.callWS({type:"config/device_registry/list"})]),o=new Map;for(const e of n)o.set(e.area_id,e.name);const r=new Map;for(const e of i)e.area_id&&r.set(e.entity_id,e.area_id);const a=new Map;for(const e of s)a.set(e.id,e.area_id);let l;if(t.device_id){const e=a.get(t.device_id);e&&(l=o.get(e))}for(const e of Object.values(t.circuits)){let t;for(const n of Object.values(e.entities)){if(!n)continue;const e=r.get(n);if(e){t=o.get(e);break}}t||(t=l),e.area=t}}class Ut{constructor(){this._persistent=new Map,this._transient=null,this._transientTimer=null,this._subscribers=new Set,this._watchedPanels=new Map}add(e){const t={...e,timestamp:Date.now()};if(t.persistent)this._persistent.set(t.key,t);else{this._clearTransient(),this._transient=t;const e=t.ttl??5e3;this._transientTimer=setTimeout(()=>{this._transient=null,this._transientTimer=null,this._notify()},e)}this._notify()}remove(e){if(this._persistent.has(e))return this._persistent.delete(e),void this._notify();this._transient?.key===e&&(this._clearTransient(),this._notify())}clear(e){void 0===e?(this._persistent.clear(),this._clearTransient(),this._watchedPanels.clear()):!0===e.persistent?this._persistent.clear():!1===e.persistent&&this._clearTransient(),this._notify()}get active(){const e=[...this._persistent.values()];return null!==this._transient&&e.push(this._transient),e}hasPersistent(e){return this._persistent.has(e)}hasAnyPanelOffline(){for(const e of this._persistent.keys())if("panel-offline"===e||e.startsWith("panel-offline:"))return!0;return!1}subscribe(e){return this._subscribers.add(e),()=>{this._subscribers.delete(e)}}watchPanelStatus(e){this.watchPanelStatuses([{entityId:e,panelName:null}])}watchPanelStatuses(e){const t=this._watchedPanels,n=new Map;for(const i of e){const e=t.get(i.entityId);n.set(i.entityId,{panelName:i.panelName??null,wasOffline:e?.wasOffline??!1})}const i=this._isSingleUnnamed(t),s=this._isSingleUnnamed(n);for(const e of t.keys()){n.has(e)&&i===s||this._persistent.delete(this._offlineKey(e,i))}this._watchedPanels=n,this._notify()}clearPanelStatusWatch(){if(0===this._watchedPanels.size)return;const e=this._isSingleUnnamed(this._watchedPanels);for(const t of this._watchedPanels.keys())this._persistent.delete(this._offlineKey(t,e));this._watchedPanels.clear(),this._notify()}updateHass(e){if(0===this._watchedPanels.size)return;const t=this._isSingleUnnamed(this._watchedPanels);for(const[n,o]of this._watchedPanels){const r=e.states[n]?.state,a="on"===r,l=this._offlineKey(n,t),c=this._reconnectKey(n,t);if(a){const e=o.wasOffline;o.wasOffline=!1,this.remove(l),e&&this.add({key:c,level:"info",message:null===o.panelName?i("error.panel_reconnected"):s("error.panel_reconnected_named",{name:o.panelName}),persistent:!1})}else o.wasOffline=!0,this.hasPersistent(l)||this.add({key:l,level:"error",message:null===o.panelName?i("error.panel_offline"):s("error.panel_offline_named",{name:o.panelName}),persistent:!0})}}dispose(){this._clearTransient(),this._persistent.clear(),this._subscribers.clear(),this._watchedPanels.clear()}_isSingleUnnamed(e){if(1!==e.size)return!1;for(const t of e.values())return null===t.panelName;return!1}_offlineKey(e,t){return t?"panel-offline":`panel-offline:${e}`}_reconnectKey(e,t){return t?"panel-reconnected":`panel-reconnected:${e}`}_clearTransient(){null!==this._transientTimer&&(clearTimeout(this._transientTimer),this._transientTimer=null),this._transient=null}_notify(){for(const e of this._subscribers)try{e()}catch(e){console.warn("SPAN Panel: error-store subscriber threw",e)}}}function qt(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 Wt(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 Bt="favorites-changed";async function Gt(e,t,n={}){const i=await e.callWS({type:"call_service",domain:l,service:t,service_data:n,return_response:!0});return i?.response??null}const Vt=Object.keys(m).filter(e=>"unknown"!==e&&"always_on"!==e);class Qt extends HTMLElement{constructor(){super(),this.errorStore=null,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}disconnectedCallback(){this._clearDebounceTimers(),this._config=null}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open",""),this.setAttribute("data-mode",this._modeFor(e))}close(){this._clearDebounceTimers(),this.removeAttribute("open"),this.removeAttribute("data-mode"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_clearDebounceTimers(){for(const e of Object.keys(this._debounceTimers))clearTimeout(this._debounceTimers[e]);this._debounceTimers={}}_modeFor(e){return e.favoritesMode?"favorites":e.panelMode?"panel":e.subDeviceMode?"subDevice":"circuit"}_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 .unit-toggle {\n display: inline-flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .unit-btn:last-child {\n border-right: none;\n }\n .unit-btn:hover:not(.unit-active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .fav-heart {\n background: none;\n border: 1px solid var(--divider-color, #e0e0e0);\n color: var(--secondary-text-color, #727272);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n font-size: 0.9em;\n margin-right: 6px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n }\n .fav-heart.active {\n color: var(--primary-color, #03a9f4);\n border-color: var(--primary-color, #03a9f4);\n }\n .fav-heart:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .fav-heart ha-icon {\n --mdc-icon-size: 16px;\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',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const s=document.createElement("div");s.className="panel",t.appendChild(s),e.favoritesMode?this._renderFavoritesMode(s):e.panelMode?this._renderPanelMode(s):e.subDeviceMode?this._renderSubDeviceMode(s,e):this._renderCircuitMode(s,e)}_renderPanelMode(e){const t=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.global_defaults"));e.appendChild(n);const s=document.createElement("div");s.className="panel-body";const o=t.graphSettings,l=t.topology,c=o?.global_horizon??r,d=o?.circuits??{};s.appendChild(this._buildListColumnsSection());const h=document.createElement("div");h.className="section";const p=document.createElement("div");p.className="section-label",p.textContent=i("sidepanel.graph_horizon"),h.appendChild(p);const u=document.createElement("div");u.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=i("sidepanel.global_default"),u.appendChild(_);const f=document.createElement("select");for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===c&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{const e={horizon:f.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_graph_time_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),u.appendChild(f),h.appendChild(u),s.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=i("sidepanel.circuit_scales"),e.appendChild(n);const o=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,i]of o){const s=this._buildPanelModeCircuitRow(n,i,d[n],c,t.configEntryId??null,t.showFavorites??!1,t.favoritePanelDeviceId,t.favoriteCircuitUuids);e.appendChild(s)}s.appendChild(e)}const m=o?.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=i("sidepanel.subdevice_scales"),e.appendChild(n);const o=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,s]of o){const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");if(r.className="field-label",r.textContent=s.name||n,r.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",o.appendChild(r),t.showFavorites&&t.favoritePanelDeviceId){const e=this._buildSubDeviceFavoriteHeart(s.entities,t.favoriteSubDeviceIds?.has(n)??!1);e&&o.appendChild(e)}const l=m[n]||{horizon:c,has_override:!1},d=l.has_override?l.horizon:c,h=document.createElement("select");h.dataset.subdevId=n;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===d&&(t.selected=!0),h.appendChild(t)}if(h.addEventListener("change",()=>{this._debounce(`subdev-${n}`,g,()=>{const e={subdevice_id:n,horizon:h.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_subdevice_graph_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),o.appendChild(h),l.has_override){const e=document.createElement("button");e.textContent="↺",e.title=i("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",()=>{const s={subdevice_id:n};t.configEntryId&&(s.config_entry_id=t.configEntryId),this._callDomainService("clear_subdevice_graph_horizon",s).then(()=>{h.value=c,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),o.appendChild(e)}e.appendChild(o)}s.appendChild(e)}e.appendChild(s)}_buildPanelModeCircuitRow(e,t,n,s,o,r,l,c){const d=document.createElement("div");d.className="field-row";const h=document.createElement("span");if(h.className="field-label",h.textContent=t.name||e,h.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",d.appendChild(h),r&&l){const n=this._buildFavoriteHeart(t.entities,c?.has(e)??!1);n&&d.appendChild(n)}const p=n||{horizon:s,has_override:!1},u=p.has_override?p.horizon:s,_=document.createElement("select");_.dataset.uuid=e;for(const e of Object.keys(a)){const t=document.createElement("option");t.value=e;const n=`horizon.${e}`,s=i(n);t.textContent=s!==n?s:e,e===u&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._debounce(`circuit-${e}`,g,()=>{const t={circuit_id:e,horizon:_.value};o&&(t.config_entry_id=o),this._callDomainService("set_circuit_graph_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})})}),d.appendChild(_),p.has_override){const t=document.createElement("button");t.textContent="↺",t.title=i("sidepanel.reset_to_global"),Object.assign(t.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"}),t.addEventListener("click",()=>{const n={circuit_id:e};o&&(n.config_entry_id=o),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{_.value=s,t.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})}),d.appendChild(t)}return d}_renderFavoritesMode(e){const t=this._config,n=this._createHeader(i("sidepanel.graph_settings"),i("sidepanel.favorites_subtitle"));e.appendChild(n);const s=document.createElement("div");s.className="panel-body",s.appendChild(this._buildListColumnsSection());for(const e of t.perPanelSections)s.appendChild(this._buildFavoritesPanelSection(e));e.appendChild(s)}_buildFavoritesPanelSection(e){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=e.panelName,t.appendChild(n);const i=e.graphSettings?.global_horizon??r,s=e.graphSettings?.circuits??{},o=function(e){const t=e.circuits??{};return Object.entries(t).map(([e,t])=>({uuid:e,circuit:t})).sort((e,t)=>(e.circuit.name||"").localeCompare(t.circuit.name||""))}(e.topology);for(const{uuid:n,circuit:r}of o){const o=this._buildPanelModeCircuitRow(n,r,s[n],i,e.configEntryId,!0,e.panelDeviceId,e.favoriteCircuitUuids);t.appendChild(o)}return t}_renderCircuitMode(e,t){const n=`${Ie(String(t.breaker_rating_a))}A · ${Ie(String(t.voltage))}V · Tabs [${Ie(String(t.tabs))}]`,i=this._createHeader(Ie(t.name),n);e.appendChild(i);const s=document.createElement("div");s.className="panel-body",e.appendChild(s),this._renderRelaySection(s,t),t.showFavorites&&this._renderFavoriteSection(s,t),this._renderSheddingSection(s,t),this._renderGraphHorizonSection(s,t),t.showMonitoring&&this._renderMonitoringSection(s,t)}_favoriteEntityId(e){return e?.current??e?.power??null}_subDeviceFavoriteEntityId(e){if(!e)return null;let t=null;for(const[n,i]of Object.entries(e)){if("sensor"===i.domain)return n;t||(t=n)}return t}_buildSubDeviceFavoriteHeart(e,t){const n=this._subDeviceFavoriteEntityId(e);return n?this._buildHeartButton(n,t):null}_buildListColumnsSection(){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=i("sidepanel.list_view_columns"),e.appendChild(t);const n=document.createElement("div");n.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=i("sidepanel.columns"),n.appendChild(s);const o=Te(),r=document.createElement("div");r.className="unit-toggle";for(const e of[1,2,3]){const t=document.createElement("button");t.type="button",t.className="unit-btn"+(e===o?" unit-active":""),t.dataset.columns=String(e),t.textContent=String(e),t.addEventListener("click",()=>{He(e);for(const e of r.querySelectorAll(".unit-btn"))e.classList.toggle("unit-active",e===t);this.dispatchEvent(new CustomEvent("list-columns-changed",{detail:e,bubbles:!0,composed:!0}))}),r.appendChild(t)}return n.appendChild(r),e.appendChild(n),e}_buildFavoriteHeart(e,t){const n=this._favoriteEntityId(e);return n?this._buildHeartButton(n,t):(console.warn("SPAN Panel: circuit has no current/power sensor; favorite heart suppressed"),null)}_buildHeartButton(e,t){const n=document.createElement("button");n.type="button",n.className=t?"fav-heart active":"fav-heart",n.dataset.role="fav-heart",n.title=i("sidepanel.save_to_favorites"),n.setAttribute("role","switch"),n.setAttribute("aria-checked",String(t)),n.setAttribute("aria-label",i("sidepanel.save_to_favorites"));const s=document.createElement("ha-icon");return s.setAttribute("icon",t?"mdi:heart":"mdi:heart-outline"),n.appendChild(s),n.addEventListener("click",t=>{t.stopPropagation(),this._toggleFavoriteEntity(n,s,e).catch(()=>{})}),n}async _toggleFavoriteEntity(e,t,n){if(!this._hass)return;const s=e.classList.contains("active"),o=!s;e.classList.toggle("active",o),t.setAttribute("icon",o?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(o));try{o?await async function(e,t){const n=await Gt(e,"add_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Bt)),n?.favorites??{}}(this._hass,n):await async function(e,t){const n=await Gt(e,"remove_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Bt)),n?.favorites??{}}(this._hass,n)}catch(n){throw e.classList.toggle("active",s),t.setAttribute("icon",s?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(s)),console.warn("SPAN Panel: favorite toggle failed",n),this.errorStore?.add({key:"service:favorites",level:"error",message:i("error.favorites_toggle_failed"),persistent:!1}),n}}_renderFavoriteSection(e,t){const n=this._favoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_appendFavoriteHeartSection(e,t,n){const s=document.createElement("div");s.className="section",s.innerHTML=``;const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=i("sidepanel.save_to_favorites"),o.appendChild(r),o.appendChild(this._buildHeartButton(t,n)),s.appendChild(o),e.appendChild(s)}_renderSubDeviceMode(e,t){const n=this._createHeader(Ie(t.name),Ie(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i),t.showFavorites&&this._renderSubDeviceFavoriteSection(i,t),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceFavoriteSection(e,t){const n=this._subDeviceFavoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_renderSubDeviceHorizonSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=i("sidepanel.graph_horizon"),n.appendChild(s);const o=t.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||r,d=o?.globalHorizon||r,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:i("sidepanel.global")}];for(const e of Object.keys(a))p.push({key:e,label:e});const u=l?c:"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:n}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=n,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const n={subdevice_id:t.subDeviceId};t.configEntryId&&(n.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_subdevice_graph_horizon",{...n,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}n.appendChild(h),e.appendChild(n)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),s=Ie(e),o=Ie(t);i.innerHTML=`
${s}
`+(o?`
${o}
`:"");const r=document.createElement("button");return r.className="close-btn",r.innerHTML="✕",r.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(r),n}_renderRelaySection(e,t){if(!1===t.is_user_controllable||!t.entities?.switch)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.breaker");const r=document.createElement("ha-switch");r.dataset.role="relay-toggle";const a=t.entities.switch,l=this._hass?.states?.[a]?.state;"on"===l&&r.setAttribute("checked",""),r.addEventListener("change",()=>{const e=r.hasAttribute("checked")||r.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:a}).catch(e=>{console.warn("SPAN Panel: relay toggle failed",e),this.errorStore?.add({key:"service:relay",level:"error",message:i("error.relay_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),n.appendChild(s),e.appendChild(n)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const n=document.createElement("div");n.className="section",n.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=i("sidepanel.priority_label");const r=document.createElement("select");r.dataset.role="shedding-select";const a=t.entities.select,l=this._hass?.states?.[a]?.state||"";for(const e of Vt){const t=m[e];if(!t)continue;const n=document.createElement("option");n.value=e,n.textContent=i(`shedding.select.${e}`)||t.label(),e===l&&(n.selected=!0),r.appendChild(n)}r.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:r.value}).catch(e=>{console.warn("SPAN Panel: shedding update failed",e),this.errorStore?.add({key:"service:shedding",level:"error",message:i("error.shedding_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),n.appendChild(s),e.appendChild(n)}_renderGraphHorizonSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="section-label",s.textContent=i("sidepanel.graph_horizon"),n.appendChild(s);const o=t.graphHorizonInfo,l=!0===o?.has_override,c=o?.horizon||r,d=o?.globalHorizon||r,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:i("sidepanel.global")}];for(const e of Object.keys(a))p.push({key:e,label:e});const u=l?c:"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:n}of p){const s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=n,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const n={circuit_id:t.uuid};t.configEntryId&&(n.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",n).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_circuit_graph_horizon",{...n,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:i("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}n.appendChild(h),e.appendChild(n)}_renderMonitoringSection(e,t){const n=document.createElement("div");n.className="section";const s=document.createElement("div");s.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=i("sidepanel.monitoring"),o.style.margin="0";const r=document.createElement("ha-switch");r.dataset.role="monitoring-toggle";const a=t.monitoringInfo,l=null!=a&&!1!==a.monitoring_enabled;l&&r.setAttribute("checked",""),s.appendChild(o),s.appendChild(r),n.appendChild(s);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",n.appendChild(c);const d=!0===a?.has_override,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,c.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=a?.continuous_threshold_pct??80,g=a?.spike_threshold_pct??100,_=a?.window_duration_m??15,f=a?.cooldown_duration_m??15;p.appendChild(this._createThresholdRow(i("sidepanel.continuous_pct"),"continuous",u,t)),p.appendChild(this._createThresholdRow(i("sidepanel.spike_pct"),"spike",g,t)),p.appendChild(this._createDurationRow(i("sidepanel.window_duration"),"window-m",_,1,180,"m",t)),p.appendChild(this._createDurationRow(i("sidepanel.cooldown"),"cooldown-m",f,1,180,"m",t)),c.appendChild(p),r.addEventListener("change",()=>{const e=r.checked;c.style.display=e?"block":"none";const n={circuit_id:t.entities?.power||t.uuid,monitoring_enabled:e};t.configEntryId&&(n.config_entry_id=t.configEntryId),this._callDomainService("set_circuit_threshold",n).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})});const m=h.querySelectorAll('input[type="radio"]');for(const e of m)e.addEventListener("change",()=>{const n="custom"===e.value&&e.checked;if(p.style.display=n?"block":"none",!n&&e.checked){const e={circuit_id:t.entities?.power||t.uuid};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("clear_circuit_threshold",e).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})}});e.appendChild(n)}_createThresholdRow(e,t,n,s){const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=e;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(n),a.dataset.role=`threshold-${t}`,a.addEventListener("input",()=>{this._debounce(`threshold-${t}`,g,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),n=e.querySelector('[data-role="threshold-spike"]'),o=e.querySelector('[data-role="threshold-window-m"]'),r=e.querySelector('[data-role="threshold-cooldown-m"]'),a={circuit_id:s.entities?.power||s.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:o?Number(o.value):void 0,cooldown_duration_m:r?Number(r.value):void 0};s.configEntryId&&(a.config_entry_id=s.configEntryId),this._callDomainService("set_circuit_threshold",a).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),o.appendChild(r),o.appendChild(a),o}_createDurationRow(e,t,n,s,o,r,a,l=!1){const c=document.createElement("div");c.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(s),p.max=String(o),p.value=String(n),p.dataset.role=`threshold-${t}`,l&&(p.disabled=!0);const u=document.createElement("span");return u.textContent=r,h.appendChild(p),h.appendChild(u),l||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,g,()=>{const e=this.shadowRoot;if(!e)return;const t=e.querySelector('[data-role="threshold-continuous"]'),n=e.querySelector('[data-role="threshold-spike"]'),s=e.querySelector('[data-role="threshold-window-m"]'),o={circuit_id:a.uuid,continuous_threshold_pct:t?Number(t.value):void 0,spike_threshold_pct:n?Number(n.value):void 0,window_duration_m:s?Number(s.value):void 0};a.configEntryId&&(o.config_entry_id=a.configEntryId),this._callDomainService("set_circuit_threshold",o).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:i("error.threshold_failed"),persistent:!1})})})}),c.appendChild(d),c.appendChild(h),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(!e.subDeviceMode&&!e.favoritesMode){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:l,service:e,service_data:t}):Promise.resolve()}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",Qt)}catch{}let Kt=class extends Ee{constructor(){super(...arguments),this._store=null,this._unsub=null,this._errors=[]}set store(e){if(this._store===e)return;this._unsub?.(),this._unsub=null,this._store=e,this._errors=e.active;const t=e;this._unsub=e.subscribe(()=>{this._errors=t.active})}connectedCallback(){if(super.connectedCallback(),this._store&&!this._unsub){const e=this._store;this._errors=e.active,this._unsub=e.subscribe(()=>{this._errors=e.active})}}disconnectedCallback(){super.disconnectedCallback(),this._unsub?.(),this._unsub=null}render(){return 0===this._errors.length?de:le`${this._errors.map(e=>le` + + `)}`}_iconForLevel(e){switch(e){case"error":return"mdi:alert-circle";case"warning":return"mdi:alert";default:return"mdi:information"}}};Kt.styles=((e,...t)=>{const n=1===e.length?e[0]:t.reduce((t,n,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+e[i+1],e[0]);return new C(n,e,x)})` + :host { + display: block; + } + .banner-row { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + font-size: 13px; + line-height: 1.4; + } + .banner-row + .banner-row { + border-top: 1px solid rgba(128, 128, 128, 0.2); + } + .banner-row.level-error { + background: color-mix(in srgb, var(--error-color, #db4437) 15%, transparent); + color: var(--error-color, #db4437); + } + .banner-row.level-warning { + background: color-mix(in srgb, var(--warning-color, #ff9800) 15%, transparent); + color: var(--warning-color, #ff9800); + } + .banner-row.level-info { + background: color-mix(in srgb, var(--info-color, #4285f4) 15%, transparent); + color: var(--info-color, #4285f4); + } + .icon { + flex-shrink: 0; + width: 18px; + height: 18px; + --mdc-icon-size: 18px; + } + .message { + flex: 1; + min-width: 0; + } + .retry-btn { + flex-shrink: 0; + background: none; + border: 1px solid currentColor; + border-radius: 4px; + color: inherit; + cursor: pointer; + font-size: 12px; + padding: 2px 8px; + } + .retry-btn:hover { + opacity: 0.8; + } + `,b([Me()],Kt.prototype,"_errors",void 0),Kt=b([ze("span-error-banner")],Kt);const Jt=[{name:"Kitchen",watts:"120",path:"M0,28 L8,26 L16,24 L24,22 L32,25 L40,20 L48,18 L56,22 L64,19 L72,16 L80,18 L88,15 L96,17 L104,14 L112,16 L120,13"},{name:"Living Room",watts:"85",path:"M0,22 L8,24 L16,20 L24,26 L32,18 L40,22 L48,16 L56,20 L64,24 L72,18 L80,22 L88,20 L96,16 L104,22 L112,18 L120,20"},{name:"Master Bed",watts:"193",path:"M0,8 L8,10 L16,8 L24,12 L32,10 L40,8 L48,10 L56,8 L64,10 L72,8 L80,12 L88,10 L96,8 L104,10 L112,8 L120,10"},{name:"HVAC",watts:"64",path:"M0,30 L8,28 L16,26 L24,22 L32,18 L40,14 L48,18 L56,22 L64,26 L72,22 L80,18 L88,22 L96,26 L104,22 L112,18 L120,22"}];let Xt=class extends Ee{constructor(){super(...arguments),this._config={},this._discovered=!1,this._discovering=!1,this._topology=null,this._activeTab="panel",this._panelDevice=null,this._panelSize=0,this._historyLoaded=!1,this._ctrl=new At,this._listCtrl=new Rt(this._ctrl),this._errorStore=new Ut,this._areaUnsub=null,this._areaSubscribing=!1,this._tabBarCleanup=null,this._onVisibilityChange=null}get _configEntryId(){return this._panelDevice?.config_entries?.[0]??null}get _root(){const e=this.shadowRoot;if(!e)throw new Error("span-panel-card: shadow root is not available");return e}connectedCallback(){super.connectedCallback(),this._ctrl.startIntervals(this._root),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&(this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root))},document.addEventListener("visibilitychange",this._onVisibilityChange)}disconnectedCallback(){this._ctrl.stopIntervals(),this._listCtrl.stop(),this._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._tabBarCleanup&&(this._tabBarCleanup(),this._tabBarCleanup=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._errorStore.dispose(),super.disconnectedCallback()}setConfig(e){this._errorStore.clear(),this._config=e,this._discovered=!1,this._discovering=!1,this._historyLoaded=!1,this._topology=null,this._panelDevice=null,this._panelSize=0,this._activeTab="panel",this._ctrl.reset(),this._ctrl.setConfig(e),this._ctrl.errorStore=this._errorStore}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:o,show_panel:!0,show_battery:!0,show_evse:!0}}render(){if(n(this.hass?.language),!this._config.device_id)return this._renderPreview();if(!this._discovered){const e=this._errorStore.hasPersistent("discovery-failed");return le` + + + ${e?de:le`
${Ie(i("card.connecting"))}
`} +
+ `}return le` +
- `:this._discoveryError?re` - -
${Le(this._discoveryError)}
-
- `:re` - -
${Le(i("card.loading"))}
-
- `:this._renderPreview()}updated(e){if(e.has("hass")&&this.hass&&(n(this.hass.language),this._ctrl.hass=this.hass,this._config.device_id))if(this._discovered||this._discovering){if(this._discovered){this._ctrl.recordSamples(),this._ctrl.updateDOM(this.shadowRoot);const e=this.shadowRoot.querySelector("span-side-panel");e&&(e.hass=this.hass)}this._discovered&&"panel"!==this._activeTab&&this._topology&&this._listCtrl.updateCollapsedRows(this.shadowRoot,this.hass,this._topology,this._config)}else this._startDiscovery()}async _startDiscovery(){this._discovering=!0,await this._discoverTopology(),this._discoveryError?this._discovering=!1:(this._discovered=!0,this._discovering=!1,this._ctrl.init(this._topology,this._config,this.hass,this._configEntryId),this._topology&&async function(e,t,n){if(!e.connection)return()=>{};const i=async()=>{try{const i=new Map;for(const[e,n]of Object.entries(t.circuits))i.set(e,n.area);await Mt(e,t);for(const[e,s]of Object.entries(t.circuits))if(s.area!==i.get(e))return void n()}catch(e){console.warn("[span-panel] area registry update failed:",e)}},[s,o]=await Promise.all([e.connection.subscribeEvents(i,"entity_registry_updated"),e.connection.subscribeEvents(i,"area_registry_updated")]);return()=>{s(),o()}}(this.hass,this._topology,()=>{"area"===this._activeTab&&this._discovered&&this._populateCardContent()}).then(e=>{this._areaUnsub=e}).catch(()=>{}),await this.updateComplete,this._populateCardContent(),this._loadHistory(),this._ctrl.monitoringCache.fetch(this.hass,this._configEntryId).then(()=>{this._discovered&&this._ctrl.updateDOM(this.shadowRoot)}))}async _discoverTopology(){if(this.hass)try{const e=await async function(e,t){if(!t)throw new Error(i("card.device_not_found"));const n=await e.callWS({type:`${r}/panel_topology`,device_id:t}),s=n.panel_size??Pt(n.circuits);if(!s)throw new Error(i("card.topology_error"));const o=Lt((await e.callWS({type:"config/device_registry/list"})).find(e=>e.id===t));return await Mt(e,n),{topology:n,panelDevice:o,panelSize:s}}(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[n,s]=await Promise.all([e.callWS({type:"config/device_registry/list"}),e.callWS({type:"config/entity_registry/list"})]),o=Lt(n.find(e=>e.id===t));if(!o)return{topology:null,panelDevice:null,panelSize:0};const a=s.filter(e=>e.device_id===t),c=n.filter(e=>e.via_device_id===t),l=new Set(c.map(e=>e.id)),d=s.filter(e=>void 0!==e.device_id&&l.has(e.device_id)),h={},p=o.name_by_user??o.name??"";for(const t of[...a,...d]){const n=e.states[t.entity_id];if(!n)continue;const i=n.attributes,s=i.tabs;if("string"!=typeof s||!s.startsWith("tabs ["))continue;const o=s.slice(6,-1);let a;if(a=o.includes(":")?o.split(":").map(Number):[Number(o)],!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(o.identifiers)for(const e of o.identifiers)e[0]===r&&(u=e[1]);let g=0;for(const t of a){const n=e.states[t.entity_id];if(n&&"number"==typeof n.attributes.panel_size){g=n.attributes.panel_size;break}}if(g||(g=Pt(h)),!g)throw new Error(i("card.panel_size_error"));const _={};for(const t of c){const n=s.filter(e=>e.device_id===t.id),i=(t.model??"").toLowerCase(),o=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],s=i?.attributes?.friendly_name;r[t.entity_id]={domain:n??"",original_name:"string"==typeof s?s:t.entity_id}}_[t.id]={name:t.name_by_user??t.name??"",type:o?"bess":a?"evse":"unknown",entities:r}}const f={serial:u,firmware:o.sw_version??"",panel_size:g,device_id:t,device_name:o.name_by_user??o.name??i("header.default_name"),circuits:h,sub_devices:_};return await Mt(e,f),{topology:f,panelDevice:o,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)}}}_populateCardContent(){const e=this.shadowRoot.querySelector("#card-content");if(!(e&&this.hass&&this._topology&&this._panelSize))return;const t=this.shadowRoot.querySelector("#card-tabs");if(t){const e=[{id:"panel",label:i("tab.by_panel"),icon:"mdi:view-dashboard"},{id:"activity",label:i("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:i("tab.by_area"),icon:"mdi:home-group"}];t.innerHTML=(n=e,s=this._activeTab,o=this._config.tab_style??"text",`
${n.map(e=>{const t=e.id===s?" active":"",n=Le(e.id);return"icon"===o?``:``}).join("")}
`),this._tabBarCleanup&&(this._tabBarCleanup(),this._tabBarCleanup=null),this._tabBarCleanup=function(e,t){const n=e=>{const n=e.target.closest(".shared-tab");if(n){const e=n.dataset.tab;e&&t(e)}};return e.addEventListener("click",n),()=>{e.removeEventListener("click",n)}}(t,e=>{["panel","activity","area"].includes(e)&&(this._activeTab=e,this._listCtrl.stop(),this._populateCardContent())})}var n,s,o;if("panel"===this._activeTab){const t=Math.ceil(this._panelSize/2),n=function(e,t){const n=Le(e.device_name||i("header.default_name")),s=Le(e.serial||""),o=Le(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

${n}

\n ${s}\n \n
\n ${i("header.enable_switches")}\n
\n \n
\n
\n
\n
\n ${r?`\n
\n ${i("header.site")}\n
\n 0\n ${a?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${i("header.grid")}\n
\n --\n
\n
`:""}\n ${l?`\n
\n ${i("header.upstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${i("header.downstream")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${h?`\n
\n ${i("header.solar")}\n
\n --\n ${a?"A":"kW"}\n
\n
`:""}\n ${p?`\n
\n ${i("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n
\n
\n
\n ${o}\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 `}(this._topology,this._config),s=this._ctrl.monitoringCache.status,o=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),n=Object.values(e.mains??{}),s=[...t,...n],o=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=80&&e.utilization_pct<100).length,a=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=100).length,r=s.filter(e=>e.has_override).length;return`\n
\n ✓ ${i("status.monitoring")} · ${t.length} ${i("status.circuits")} · ${n.length} ${i("status.mains")}\n \n ${o>0?`${o} ${i(o>1?"status.warnings":"status.warning")}`:""}\n ${a>0?`${a} ${i(a>1?"status.alerts":"status.alert")}`:""}\n ${r>0?`${r} ${i(r>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),a=function(e,t,n,i,s){const o=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),s=1===e.length?"single":Ie(e)??"single";o.set(i,{uuid:t,circuit:n,layout:s});for(const t of e)a.add(t)}const r=new Set,c=new Set;for(const[e,t]of o)if("col-span"===t.layout){const n=t.circuit.tabs,i=Oe(Math.max(...n));0===Re(e)?r.add(i):c.add(i)}function l(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?We(s,t??""):null;let o;if(e.circuit.always_on)o="always_on";else{const t=e.circuit.entities?.select;o=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:o}}let d="";for(let e=1;e<=t;e++){const t=2*e-1,s=2*e,h=o.get(t),p=o.get(s);if(d+=`
${t}
`,h&&"row-span"===h.layout){const{monInfo:t,sheddingPriority:o}=l(h);d+=Be(h.uuid,h.circuit,e,"2 / 4","row-span",n,i,t,o),d+=`
${s}
`;continue}if(!r.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)a.has(t)||(d+=Ge(e,"2"));else{const{monInfo:t,sheddingPriority:s}=l(h);d+=Be(h.uuid,h.circuit,e,"2",h.layout,n,i,t,s)}if(!c.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)a.has(s)||(d+=Ge(e,"3"));else{const{monInfo:t,sheddingPriority:s}=l(p);d+=Be(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(this._topology,t,this.hass,this._config,s),r=function(e,t,n){const s=!1!==n.show_battery,o=!1!==n.show_evse;if(!e.sub_devices)return"";const a=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===d&&!s||e.type===h&&!o));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===h).length;let c=0,l="";for(const[e,s]of a){const o=s.type===h?i("subdevice.ev_charger"):s.type===d?i("subdevice.battery"):i("subdevice.fallback"),a=Ze(s),p=a?t.states[a]:void 0,u=p&&parseFloat(p.state)||0,g=s.type===d,_=s.type===h,f=g?Ye(s):null,m=g?et(s):null,v=g?tt(s):null,b=nt(s,t,n,new Set([a,f,m,v].filter(e=>null!==e))),y=it(e,0,g,a,f,m);let w="";g?w="sub-device-bess":_&&(c++,c===r&&r%2==1&&(w="sub-device-full")),l+=`\n
\n
\n ${Le(o)}\n ${Le(s.name||"")}\n ${a?`${De(u)} ${Te(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return l}(this._topology,this.hass,this._config);e.innerHTML=`\n ${n}\n ${o}\n ${r?`
${r}
`:""}\n ${!1!==this._config.show_panel?`
${a}
`:""}\n `;const c=e.querySelector(".slide-confirm");if(c){const e=this.shadowRoot.querySelector("ha-card");this._ctrl.bindSlideConfirm(c,e),e&&e.classList.add("switches-disabled")}const l=this.shadowRoot.querySelector("span-side-panel");l&&(l.hass=this.hass),this._ctrl.recordSamples(),this._ctrl.updateDOM(this.shadowRoot),this._ctrl.setupResizeObserver(this.shadowRoot,this.shadowRoot.querySelector("ha-card"))}else"activity"===this._activeTab?(e.innerHTML="",this._listCtrl.renderActivityView(e,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status),this._ctrl.updateDOM(this.shadowRoot)):"area"===this._activeTab&&(e.innerHTML="",this._listCtrl.renderAreaView(e,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status),this._ctrl.updateDOM(this.shadowRoot))}_onCardClick(e){if("panel"!==this._activeTab)return;const t=e.target;if(!t)return;const n=t.closest(".unit-btn");if(n)return void this._onUnitToggle(n);if(t.closest(".toggle-pill"))return void this._ctrl.onToggleClick(e,this.shadowRoot);t.closest(".gear-icon")&&this._ctrl.onGearClick(e,this.shadowRoot)}async _onUnitToggle(e){const t=e.dataset.unit;t&&t!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:t},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._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this.shadowRoot))}async _onListUnitChanged(e){const t=e.detail;t&&t!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:t},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._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this.shadowRoot))}_onGraphSettingsChanged(){this._ctrl.onGraphSettingsChanged(this.shadowRoot)}_onSidePanelClosed(){this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()}_renderPreview(){const e=Dt.map(e=>re` + `}updated(e){if(e.has("hass")&&this.hass&&(n(this.hass.language),this._ctrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._config.device_id))if(this._discovered||this._discovering){if(this._discovered){this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root);const e=this._root.querySelector("span-side-panel");e&&(e.hass=this.hass,e.errorStore=this._errorStore)}this._discovered&&"panel"!==this._activeTab&&this._topology&&this._listCtrl.updateCollapsedRows(this._root,this.hass,this._topology,this._config)}else this._startDiscovery()}async _startDiscovery(){this._discovering||(this._discovering=!0,await this._discoverTopology(),this._errorStore.hasPersistent("discovery-failed")?this._discovering=!1:(this._discovered=!0,this._discovering=!1,this._ctrl.init(this._topology,this._config,this.hass,this._configEntryId),this._topology?.panel_entities?.panel_status&&(this._errorStore.watchPanelStatus(this._topology.panel_entities.panel_status),this._errorStore.updateHass(this.hass)),this._topology&&(this._areaSubscribing=!0,async function(e,t,n,s){if(!e.connection)return()=>{};const o=async()=>{try{const i=new Map;for(const[e,n]of Object.entries(t.circuits))i.set(e,n.area);await jt(e,t);for(const[e,s]of Object.entries(t.circuits))if(s.area!==i.get(e))return void n()}catch(e){console.warn("[span-panel] area registry update failed:",e),s?.add({key:"fetch:areas",level:"warning",message:i("error.areas_failed"),persistent:!1})}},[r,a]=await Promise.all([e.connection.subscribeEvents(o,"entity_registry_updated"),e.connection.subscribeEvents(o,"area_registry_updated")]);return()=>{r(),a()}}(this.hass,this._topology,()=>{"area"===this._activeTab&&this._discovered&&this._populateCardContent()},this._errorStore).then(e=>{this._areaSubscribing?this._areaUnsub=e:e()}).catch(e=>{this._areaSubscribing=!1,console.warn("SPAN Panel: area subscription failed",e),this._errorStore.add({key:"subscribe:area",level:"warning",message:i("error.areas_failed"),persistent:!1})})),await this.updateComplete,this._populateCardContent(),this._loadHistory(),this._ctrl.monitoringCache.fetch(this.hass,this._configEntryId).then(()=>{this._discovered&&this._ctrl.updateDOM(this._root)})))}async _discoverTopology(){if(!this.hass)return;const e=new Ke(this._errorStore);try{const t=await async function(e,t,n){if(!t)throw new Error(i("card.device_not_found"));const s={type:`${l}/panel_topology`,device_id:t},o=n?await n.callWS(e,s,{errorId:"fetch:topology"}):await e.callWS(s),r=o.panel_size??qt(o.circuits);if(!r)throw new Error(i("card.topology_error"));const a={type:"config/device_registry/list"},c=Wt((n?await n.callWS(e,a,{errorId:"fetch:topology"}):await e.callWS(a)).find(e=>e.id===t));return await jt(e,o),{topology:o,panelDevice:c,panelSize:r}}(this.hass,this._config.device_id,e);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(e,t,n){const s={type:"config/device_registry/list"},o={type:"config/entity_registry/list"},[r,a]=await Promise.all([n?n.callWS(e,s,{errorId:"fetch:topology"}):e.callWS(s),n?n.callWS(e,o,{errorId:"fetch:topology"}):e.callWS(o)]),c=Wt(r.find(e=>e.id===t));if(!c)return{topology:null,panelDevice:null,panelSize:0};const d=a.filter(e=>e.device_id===t),h=r.filter(e=>e.via_device_id===t),p=new Set(h.map(e=>e.id)),u=a.filter(e=>void 0!==e.device_id&&p.has(e.device_id)),g={},_=c.name_by_user??c.name??"";for(const t of[...d,...u]){const n=e.states[t.entity_id];if(!n)continue;const i=n.attributes,s=i.tabs;if("string"!=typeof s||!s.startsWith("tabs ["))continue;const o=s.slice(6,-1);let r;if(r=o.includes(":")?o.split(":").map(Number):[Number(o)],!r.every(Number.isFinite))continue;const a=t.unique_id.split("_");let l=null;for(let e=2;e=16&&/^[a-f0-9]+$/i.test(t)){l=t;break}}if(!l)continue;let c=("string"==typeof i.friendly_name?i.friendly_name:void 0)??t.entity_id;for(const e of[" Power"," Consumed Energy"," Produced Energy"])if(c.endsWith(e)){c=c.slice(0,-e.length);break}_&&c.startsWith(_+" ")&&(c=c.slice(_.length+1));const d=t.entity_id.replace(/^sensor\./,"").replace(/_power$/,""),h="number"==typeof i.voltage?i.voltage:2===r.length?240:120,p={power:t.entity_id,switch:`switch.${d}_breaker`,breaker_rating:`sensor.${d}_breaker_rating`};g[l]={tabs:r,name:c,voltage:h,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:p}}let f="";if(c.identifiers)for(const e of c.identifiers){if(!Array.isArray(e)||e.length<2)continue;const[t,n]=e;t===l&&"string"==typeof n&&(f=n)}let m=0;for(const t of d){const n=e.states[t.entity_id];if(n&&"number"==typeof n.attributes.panel_size){m=n.attributes.panel_size;break}}if(m||(m=qt(g)),!m)throw new Error(i("card.panel_size_error"));const v={};for(const t of h){const n=a.filter(e=>e.device_id===t.id),i=(t.model??"").toLowerCase(),s=i.includes("battery")||(t.identifiers??[]).some(e=>e[1].toLowerCase().includes("bess")),o=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],s=i?.attributes?.friendly_name;r[t.entity_id]={domain:n??"",original_name:"string"==typeof s?s:t.entity_id}}v[t.id]={name:t.name_by_user??t.name??"",type:s?"bess":o?"evse":"unknown",entities:r}}const b={serial:f,firmware:c.sw_version??"",panel_size:m,device_id:t,device_name:c.name_by_user??c.name??i("header.default_name"),circuits:g,sub_devices:v};return await jt(e,b),{topology:b,panelDevice:c,panelSize:m}}(this.hass,this._config.device_id,e);this._topology=t.topology,this._panelDevice=t.panelDevice,this._panelSize=t.panelSize}catch(e){console.error("SPAN Panel: fallback discovery also failed",e),this._errorStore.add({key:"discovery-failed",level:"error",message:i("error.discovery_failed"),persistent:!0,retryFn:()=>{this._errorStore.remove("discovery-failed"),this._startDiscovery()}})}}}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._root)}catch(e){console.warn("SPAN Panel: history fetch failed, charts will populate live",e)}}}_populateCardContent(){const e=this._root.querySelector("#card-content");if(!(e&&this.hass&&this._topology&&this._panelSize))return;const t=this._root.querySelector("#card-tabs");if(t){const e=[{id:"panel",label:i("tab.by_panel"),icon:"mdi:view-dashboard"},{id:"activity",label:i("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:i("tab.by_area"),icon:"mdi:home-group"}];t.innerHTML=(n=e,s=this._activeTab,o=this._config.tab_style??"text",`
${n.map(e=>{const t=e.id===s?" active":"",n=Ie(e.id);return"icon"===o?``:``}).join("")}
`),this._tabBarCleanup&&(this._tabBarCleanup(),this._tabBarCleanup=null),this._tabBarCleanup=function(e,t){const n=e=>{const n=e.target.closest(".shared-tab");if(n){const e=n.dataset.tab;e&&t(e)}};return e.addEventListener("click",n),()=>{e.removeEventListener("click",n)}}(t,e=>{["panel","activity","area"].includes(e)&&(this._activeTab=e,this._listCtrl.stop(),this._populateCardContent())})}var n,s,o;if("panel"===this._activeTab){const t=Math.ceil(this._panelSize/2),n=Oe(this._topology,this._config),s=this._ctrl.monitoringCache.status,o=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),n=Object.values(e.mains??{}),s=[...t,...n],o=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=80&&e.utilization_pct<100).length,r=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=100).length,a=s.filter(e=>e.has_override).length;return`\n
\n ✓ ${i("status.monitoring")} · ${t.length} ${i("status.circuits")} · ${n.length} ${i("status.mains")}\n \n ${o>0?`${o} ${i(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${i(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${i(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(s),r=function(e,t,n,i,s){const o=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),s=1===e.length?"single":Be(e)??"single";o.set(i,{uuid:t,circuit:n,layout:s});for(const t of e)r.add(t)}const a=new Set,l=new Set;for(const[e,t]of o)if("col-span"===t.layout){const n=t.circuit.tabs,i=qe(Math.max(...n));0===We(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?Ze(s,t??""):null;let o;if(e.circuit.always_on)o="always_on";else{const t=e.circuit.entities?.select;o=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:o}}let d="";for(let e=1;e<=t;e++){const t=2*e-1,s=2*e,h=o.get(t),p=o.get(s);if(d+=`
${t}
`,h&&"row-span"===h.layout){const{monInfo:t,sheddingPriority:o}=c(h);d+=tt(h.uuid,h.circuit,e,"2 / 4","row-span",n,i,t,o),d+=`
${s}
`;continue}if(!a.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(t)||(d+=nt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=tt(h.uuid,h.circuit,e,"2",h.layout,n,i,t,s)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(s)||(d+=nt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=tt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(this._topology,t,this.hass,this._config,s),a=function(e,t,n){const s=!1!==n.show_battery,o=!1!==n.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===h&&!s||e.type===p&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===p).length;let l=0,c="";for(const[e,s]of r){const o=s.type===p?i("subdevice.ev_charger"):s.type===h?i("subdevice.battery"):i("subdevice.fallback"),r=lt(s),d=r?t.states[r]:void 0,u=d&&parseFloat(d.state)||0,g=s.type===h,_=s.type===p,f=g?ct(s):null,m=g?dt(s):null,v=g?ht(s):null,b=pt(s,t,n,new Set([r,f,m,v].filter(e=>null!==e))),y=ut(e,0,g,r,f,m);let w="";g?w="sub-device-bess":_&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Ie(o)}\n ${Ie(s.name||"")}\n ${r?`${je(u)} ${Re(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}(this._topology,this.hass,this._config);e.innerHTML=`\n ${n}\n ${o}\n ${a?`
${a}
`:""}\n ${!1!==this._config.show_panel?`
${r}
`:""}\n `;const l=e.querySelector(".slide-confirm");if(l){const e=this._root.querySelector("ha-card");this._ctrl.bindSlideConfirm(l,e),e&&e.classList.add("switches-disabled")}const c=this._root.querySelector("span-side-panel");c&&(c.hass=this.hass,c.errorStore=this._errorStore),this._ctrl.recordSamples(),this._ctrl.updateDOM(this._root),this._ctrl.setupResizeObserver(this._root,this._root.querySelector("ha-card"))}else if("activity"===this._activeTab){e.innerHTML="";const t=Oe(this._topology,this._config);this._listCtrl.setColumns(Te()),this._listCtrl.renderActivityView(e,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status,t),this._ctrl.updateDOM(this._root)}else if("area"===this._activeTab){e.innerHTML="";const t=Oe(this._topology,this._config);this._listCtrl.setColumns(Te()),this._listCtrl.renderAreaView(e,this.hass,this._topology,this._config,this._ctrl.monitoringCache.status,t),this._ctrl.updateDOM(this._root)}}_onCardClick(e){if("panel"!==this._activeTab)return;const t=e.target;if(!t)return;const n=t.closest(".unit-btn");if(n)return void this._onUnitToggle(n);if(t.closest(".toggle-pill"))return void this._ctrl.onToggleClick(e,this._root);t.closest(".gear-icon")&&this._ctrl.onGearClick(e,this._root)}async _onUnitToggle(e){const t=e.dataset.unit;t&&t!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:t},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._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this._root))}async _onListUnitChanged(e){const t=e.detail;t&&t!==(this._config.chart_metric??"power")&&(this._config={...this._config,chart_metric:t},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._populateCardContent(),await this._loadHistory(),this._ctrl.updateDOM(this._root))}_onGraphSettingsChanged(){this._ctrl.onGraphSettingsChanged(this._root)}_onListColumnsChanged(e){const t=e.detail;"number"!=typeof t||1!==t&&2!==t&&3!==t||"activity"!==this._activeTab&&"area"!==this._activeTab||this._populateCardContent()}_onSidePanelClosed(){this._ctrl.monitoringCache.invalidate(),this._ctrl.graphSettingsCache.invalidate()}_renderPreview(){const e=Jt.map(e=>le`
${e.name} @@ -67,7 +122,7 @@ const ze={attribute:!0,type:String,converter:O,reflect:!1,hasChanged:R},ke=(e=ze
- `);return re` + `);return le`
@@ -78,4 +133,4 @@ const ze={attribute:!0,type:String,converter:O,reflect:!1,hasChanged:R},ke=(e=ze
${i("card.no_device")}
- `}};Ht.styles=C("\n :host {\n --span-accent: var(--primary-color, #4dd9af);\n }\n\n ha-card {\n padding: 24px;\n background: var(--card-background-color, #1c1c1c);\n color: var(--primary-text-color, #e0e0e0);\n border-radius: var(--ha-card-border-radius, 12px);\n border: var(--ha-card-border-width, 1px) solid var(--ha-card-border-color, var(--divider-color, #333));\n box-shadow: var(--ha-card-box-shadow, none);\n }\n\n .panel-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: flex-start;\n gap: 8px 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .header-left { flex: 1 1 300px; min-width: 0; }\n .header-center { flex: 0 0 auto; }\n .header-right { flex: 0 1 auto; min-width: 0; }\n\n .panel-identity {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-bottom: 12px;\n }\n\n .panel-title {\n font-size: 1.8em;\n font-weight: 700;\n margin: 0;\n color: var(--primary-text-color, #fff);\n }\n\n .panel-serial {\n font-size: 0.85em;\n color: var(--secondary-text-color, #999);\n font-family: monospace;\n }\n\n .panel-stats {\n display: flex;\n flex-wrap: wrap;\n gap: 16px 32px;\n }\n\n .stat { display: flex; flex-direction: column; }\n .stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }\n .stat-row { display: flex; align-items: baseline; gap: 2px; }\n .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); }\n .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); }\n\n .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; }\n .header-right-top { display: flex; gap: 20px; align-items: center; }\n .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); }\n\n .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }\n .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; }\n .shedding-legend-item ha-icon { --mdc-icon-size: 16px; }\n .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; }\n .shedding-legend-text { font-size: 9px; font-weight: 600; }\n .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); }\n\n .panel-gear {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color);\n opacity: 0.6;\n padding: 4px;\n margin-left: 8px;\n vertical-align: middle;\n }\n .panel-gear:hover { opacity: 1; }\n .header-center {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 8px;\n }\n .panel-identity .panel-gear {\n margin-left: 0;\n }\n .slide-confirm {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 160px;\n height: 28px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333));\n vertical-align: middle;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n }\n .slide-confirm-text {\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 0.65em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n pointer-events: none;\n z-index: 0;\n }\n .slide-confirm-knob {\n position: absolute;\n left: 2px;\n top: 2px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--secondary-text-color, #666);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n z-index: 1;\n transition: none;\n }\n .slide-confirm-knob ha-icon {\n --mdc-icon-size: 14px;\n color: var(--card-background-color, #1c1c1c);\n }\n .slide-confirm-knob.snapping {\n transition: left 0.25s ease;\n }\n .slide-confirm.confirmed {\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n }\n .slide-confirm.confirmed .slide-confirm-text {\n color: var(--state-active-color, var(--span-accent));\n }\n .slide-confirm.confirmed .slide-confirm-knob {\n background: var(--state-active-color, var(--span-accent));\n }\n .switches-disabled .toggle-pill {\n opacity: 0.3;\n pointer-events: none;\n }\n .unit-toggle {\n display: inline-flex;\n background: var(--secondary-background-color, #333);\n border-radius: 6px;\n overflow: hidden;\n margin-left: 8px;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n background: none;\n color: var(--secondary-text-color);\n font-size: 0.75em;\n font-weight: 600;\n cursor: pointer;\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #4dd9af);\n color: var(--text-primary-color, #000);\n }\n\n .monitoring-summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 16px;\n font-size: 0.8em;\n background: rgba(76, 175, 80, 0.1);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n }\n .monitoring-active { color: #4caf50; }\n .monitoring-counts { display: flex; gap: 12px; }\n .count-warning { color: #ff9800; }\n .count-alert { color: #f44336; }\n .count-overrides { color: var(--secondary-text-color); }\n\n .panel-grid {\n display: grid;\n grid-template-columns: 28px 1fr 1fr 28px;\n gap: 8px;\n align-items: stretch;\n }\n\n .tab-label {\n display: flex;\n align-items: center;\n font-size: 0.85em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n user-select: none;\n }\n .tab-left { justify-content: flex-start; }\n .tab-right { justify-content: flex-end; }\n\n .circuit-slot {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px 20px;\n min-height: 140px;\n transition: opacity 0.3s;\n position: relative;\n overflow: hidden;\n }\n\n .circuit-col-span { min-height: 280px; }\n .circuit-row-span { border-left: 3px solid var(--span-accent); }\n .circuit-off .circuit-name,\n .circuit-off .breaker-badge,\n .circuit-off .power-value,\n .circuit-off .chart-container { opacity: 0.35; }\n .circuit-off .toggle-pill,\n .circuit-off .gear-icon { opacity: 1; }\n\n .circuit-empty {\n opacity: 0.2;\n min-height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-style: dashed;\n }\n .empty-label { color: var(--secondary-text-color, #999); font-size: 0.85em; }\n\n .circuit-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 6px;\n gap: 8px;\n }\n\n .circuit-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n\n .breaker-badge {\n background: color-mix(in srgb, var(--span-accent) 15%, transparent);\n color: var(--span-accent);\n font-size: 0.7em;\n font-weight: 700;\n padding: 2px 7px;\n border-radius: 4px;\n white-space: nowrap;\n border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent);\n flex-shrink: 0;\n }\n\n .circuit-name {\n font-size: 0.9em;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--primary-text-color, #e0e0e0);\n }\n\n .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }\n\n .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .power-value strong { font-weight: 700; font-size: 1.1em; }\n .power-unit { font-size: 0.8em; font-weight: 400; color: var(--secondary-text-color, #999); margin-left: 1px; }\n .circuit-producer .power-value strong { color: var(--info-color, #4fc3f7); }\n\n .toggle-pill {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 4px;\n border-radius: 10px;\n cursor: pointer;\n font-size: 0.65em;\n font-weight: 600;\n transition: background 0.2s;\n user-select: none;\n min-width: 40px;\n }\n .toggle-on {\n padding-left: 6px;\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n color: var(--state-active-color, var(--span-accent));\n }\n .toggle-off {\n padding-right: 6px;\n background: color-mix(in srgb, var(--secondary-text-color) 15%, transparent);\n color: var(--secondary-text-color, #999);\n }\n .toggle-knob {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n transition: background 0.2s, margin 0.2s;\n }\n .toggle-on .toggle-knob {\n background: var(--state-active-color, var(--span-accent));\n margin-left: auto;\n }\n .toggle-off .toggle-knob {\n background: var(--secondary-text-color, #999);\n margin-right: auto;\n order: -1;\n }\n\n .circuit-status {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 4px;\n padding: 0 4px;\n }\n .shedding-icon { opacity: 0.8; cursor: default; }\n .shedding-composite {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .shedding-icon-secondary { opacity: 0.8; }\n .shedding-label {\n font-size: 10px;\n font-weight: 600;\n opacity: 0.8;\n }\n .gear-icon {\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px;\n opacity: 0.6;\n transition: opacity 0.2s;\n margin-left: auto;\n }\n .gear-icon:hover { opacity: 1; }\n .utilization {\n font-size: 0.75em;\n font-weight: 600;\n }\n .utilization-normal { color: #4caf50; }\n .utilization-warning { color: #ff9800; }\n .utilization-alert { color: #f44336; }\n .circuit-alert {\n border-color: #f44336 !important;\n box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);\n }\n .circuit-custom-monitoring {\n border-left: 3px solid #ff9800;\n }\n\n .chart-container {\n width: 100%;\n aspect-ratio: 4 / 1;\n margin-top: 4px;\n overflow: hidden;\n min-width: 0;\n }\n\n .sub-devices {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .sub-device {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px;\n }\n .sub-device-bess,\n .sub-device-full {\n grid-column: 1 / -1;\n }\n\n .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; }\n .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); }\n .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; }\n .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .sub-power-value strong { font-weight: 700; font-size: 1.1em; }\n .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; }\n\n .bess-charts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 10px;\n }\n .bess-chart-col { min-width: 0; }\n .bess-chart-title {\n font-size: 0.75em;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--secondary-text-color, #999);\n margin-bottom: 4px;\n }\n .bess-chart-col .chart-container { aspect-ratio: auto; }\n .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; }\n .sub-entity-name { color: var(--secondary-text-color, #999); }\n .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); }\n\n /* ── Shared tab bar ────────────────────────────────────── */\n\n .shared-tab-bar {\n display: flex;\n gap: 0;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .shared-tab {\n padding: 8px 16px;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 500;\n color: var(--primary-text-color);\n opacity: 0.6;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n transition: opacity 0.15s;\n }\n\n .shared-tab:hover {\n opacity: 0.85;\n }\n\n .shared-tab.active {\n opacity: 1;\n border-bottom-color: var(--span-accent);\n }\n\n /* ── List view search ──────────────────────────────────── */\n\n .list-search-container {\n margin-bottom: 12px;\n position: relative;\n }\n\n .list-search {\n width: 100%;\n padding: 8px 36px 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--secondary-background-color, #2a2a2a);\n color: var(--primary-text-color);\n font-size: 0.9em;\n box-sizing: border-box;\n outline: none;\n }\n\n .list-search:focus {\n border-color: var(--span-accent);\n }\n\n .list-search-clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 2px;\n display: flex;\n align-items: center;\n opacity: 0.7;\n }\n\n .list-search-clear:hover {\n opacity: 1;\n }\n\n .list-unit-toggle {\n display: inline-flex;\n margin-bottom: 12px;\n }\n\n /* ── List rows ─────────────────────────────────────────── */\n\n .list-view {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .list-row {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 10px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n }\n\n .list-row:hover {\n background: var(--secondary-background-color, #2a2a2a);\n }\n\n .list-row.circuit-off {\n opacity: 0.5;\n }\n\n .list-row.list-row-expanded {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n .list-circuit-name {\n flex: 1;\n color: var(--primary-text-color);\n font-size: 0.9em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .list-status-badge {\n font-size: 0.75em;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .list-status-on {\n color: #4dd9af;\n }\n\n .list-status-off {\n color: #f44336;\n }\n\n .list-power-value {\n font-size: 0.9em;\n font-weight: 600;\n min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n }\n\n .list-expand-toggle {\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 4px;\n transition: transform 0.2s;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-expand-toggle.expanded {\n transform: rotate(180deg);\n }\n\n /* ── Expanded circuit content ──────────────────────────── */\n\n .list-expanded-content {\n padding: 12px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n border-radius: 0 0 8px 8px;\n margin-top: -6px;\n margin-bottom: 2px;\n }\n\n .list-expanded-content .circuit-slot {\n border: none;\n margin: 0;\n background: none;\n }\n\n /* ── Area headers ──────────────────────────────────────── */\n\n .area-header {\n padding: 16px 12px 6px;\n font-weight: 600;\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n /* ── No results ────────────────────────────────────────── */\n\n .list-no-results {\n padding: 24px;\n text-align: center;\n color: var(--secondary-text-color);\n }\n\n"),v([Ae({attribute:!1})],Ht.prototype,"hass",void 0),v([Me()],Ht.prototype,"_config",void 0),v([Me()],Ht.prototype,"_discovered",void 0),v([Me()],Ht.prototype,"_discovering",void 0),v([Me()],Ht.prototype,"_discoveryError",void 0),v([Me()],Ht.prototype,"_topology",void 0),v([Me()],Ht.prototype,"_activeTab",void 0),Ht=v([(e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)})("span-panel-card")],Ht);class Ot 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={},this._tabStyleSelect=null}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]===r)&&!e.via_device_id).map(e=>{const t=(e.identifiers??[]).find(e=>e[0]===r)?.[1]??"",n=e.name_by_user??e.name??i("editor.panel_label");return{device_id:e.id,label:`${n} (${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._buildTabStyleSelector(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,n,s){const o=document.createElement("div");o.style.cssText=s;const a=document.createElement("label");a.textContent=i("editor.panel_label"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=t;const c=document.createElement("option");if(c.value="",c.textContent=i("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)}),o.appendChild(a),o.appendChild(r),e.appendChild(o),this._panelSelect=r}_buildTimeWindow(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const a=document.createElement("label");a.textContent=i("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=t+"width: 70px; cursor: text;",l=(e,t,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=t,o.max=n,o.value=String(e),o.style.cssText=c;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}},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",i("editor.days")),g=l(h,"0","23",i("editor.hours")),_=l(p,"0","59",i("editor.minutes")),f=()=>{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",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),r.appendChild(u.wrap),r.appendChild(g.wrap),r.appendChild(_.wrap),o.appendChild(a),o.appendChild(r),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const a=document.createElement("label");a.textContent=i("editor.chart_metric"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=t,r.addEventListener("change",()=>{this._config={...this._config,chart_metric:r.value},this._fireConfigChanged()}),o.appendChild(a),o.appendChild(r),e.appendChild(o),this._metricSelect=r}_buildTabStyleSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const a=document.createElement("label");a.textContent=i("editor.tab_style"),a.style.cssText=n;const r=document.createElement("select");r.style.cssText=t;const c=[{value:"text",text:i("editor.tab_style_text")},{value:"icon",text:i("editor.tab_style_icon")}];for(const e of c){const t=document.createElement("option");t.value=e.value,t.textContent=e.text,e.value===(this._config.tab_style??"text")&&(t.selected=!0),r.appendChild(t)}r.addEventListener("change",()=>{this._config={...this._config,tab_style:r.value},this._fireConfigChanged()}),o.appendChild(a),o.appendChild(r),e.appendChild(o),this._tabStyleSelect=r}_buildSectionCheckboxes(e,t,n){const s=document.createElement("div");s.style.cssText=n;const o=document.createElement("label");o.textContent=i("editor.visible_sections"),o.style.cssText=t,s.appendChild(o);const a=[{key:"show_panel",label:i("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:i("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:i("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),s.appendChild(t),this._checkboxes[e.key]=n;let o=null;e.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",s.appendChild(o),this._entityContainers[e.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(s)}_isChartEntity(e,t,n){const i=(t.original_name??"").toLowerCase(),s=t.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(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,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===t[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),e.appendChild(o),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:`${r}/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??s;e.innerHTML="";for(const[n,i]of Object.entries(g)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=n,s.textContent=i.label(),n===t&&(s.selected=!0),e.appendChild(s)}}_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??s),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e];this._tabStyleSelect&&(this._tabStyleSelect.value=this._config.tab_style??"text")}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}try{customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",Ot)}catch{}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.2 ","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;"); + `}};Xt.styles=$('\n :host {\n --span-accent: var(--primary-color, #4dd9af);\n }\n\n ha-card {\n padding: 24px;\n background: var(--card-background-color, #1c1c1c);\n color: var(--primary-text-color, #e0e0e0);\n border-radius: var(--ha-card-border-radius, 12px);\n border: var(--ha-card-border-width, 1px) solid var(--ha-card-border-color, var(--divider-color, #333));\n box-shadow: var(--ha-card-box-shadow, none);\n }\n\n .panel-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: flex-start;\n gap: 8px 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .header-left { flex: 1 1 300px; min-width: 0; }\n .header-center { flex: 0 0 auto; }\n .header-right { flex: 0 1 auto; min-width: 0; }\n\n .panel-identity {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-bottom: 12px;\n }\n\n .panel-title {\n font-size: 1.8em;\n font-weight: 700;\n margin: 0;\n color: var(--primary-text-color, #fff);\n }\n\n .panel-serial {\n font-size: 0.85em;\n color: var(--secondary-text-color, #999);\n font-family: monospace;\n }\n\n .panel-stats {\n display: flex;\n flex-wrap: wrap;\n gap: 16px 32px;\n }\n\n /* Favorites view header: gear + slide-to-arm + right-anchored legend/W-A cluster. */\n .favorites-summary {\n padding: 8px 24px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n align-items: center;\n gap: 12px;\n }\n /* Override the generic .gear-icon { margin-left: auto } rule so the\n favorites gear stays flush-left instead of floating to the right of\n the flex row (same idea as .panel-identity .panel-gear does for\n real-panel headers). */\n .favorites-summary .favorites-gear {\n margin-left: 0;\n }\n /* Right-anchored cluster wrapping the shedding legend + W/A unit toggle.\n margin-left:auto moved here from .favorites-summary-unit-toggle so the\n legend and toggle cluster together, matching the real-panel header\n layout. */\n .favorites-summary-right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .favorites-subdevices-section {\n padding: 8px 16px 0;\n }\n\n /* Favorites view: responsive grid of per-contributing-panel status cards. */\n .favorites-panel-stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n padding: 12px 24px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .favorites-panel-card {\n background: var(--secondary-background-color, rgba(255, 255, 255, 0.04));\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n padding: 10px 14px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n .favorites-panel-card-title {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--primary-text-color);\n opacity: 0.85;\n }\n .favorites-panel-card .panel-stats {\n gap: 10px 20px;\n }\n .favorites-panel-card .stat-value {\n font-size: 1.15em;\n }\n\n .stat { display: flex; flex-direction: column; }\n .stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }\n .stat-row { display: flex; align-items: baseline; gap: 2px; }\n .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); }\n .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); }\n\n .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; }\n .header-right-top { display: flex; gap: 20px; align-items: center; }\n .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); }\n\n .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }\n .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; }\n .shedding-legend-item ha-icon { --mdc-icon-size: 16px; }\n .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; }\n .shedding-legend-text { font-size: 9px; font-weight: 600; }\n .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); }\n\n .panel-gear {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color);\n opacity: 0.6;\n padding: 4px;\n margin-left: 8px;\n vertical-align: middle;\n }\n .panel-gear:hover { opacity: 1; }\n .header-center {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 8px;\n }\n .panel-identity .panel-gear {\n margin-left: 0;\n }\n .slide-confirm {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 160px;\n height: 28px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333));\n vertical-align: middle;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n }\n .slide-confirm-text {\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 0.65em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n pointer-events: none;\n z-index: 0;\n }\n .slide-confirm-knob {\n position: absolute;\n left: 2px;\n top: 2px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--secondary-text-color, #666);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n z-index: 1;\n transition: none;\n }\n .slide-confirm-knob ha-icon {\n --mdc-icon-size: 14px;\n color: var(--card-background-color, #1c1c1c);\n }\n .slide-confirm-knob.snapping {\n transition: left 0.25s ease;\n }\n .slide-confirm.confirmed {\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n }\n .slide-confirm.confirmed .slide-confirm-text {\n color: var(--state-active-color, var(--span-accent));\n }\n .slide-confirm.confirmed .slide-confirm-knob {\n background: var(--state-active-color, var(--span-accent));\n }\n .switches-disabled .toggle-pill {\n opacity: 0.3;\n pointer-events: none;\n }\n .unit-toggle {\n display: inline-flex;\n background: var(--secondary-background-color, #333);\n border-radius: 6px;\n overflow: hidden;\n margin-left: 8px;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n background: none;\n color: var(--secondary-text-color);\n font-size: 0.75em;\n font-weight: 600;\n cursor: pointer;\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #4dd9af);\n color: var(--text-primary-color, #000);\n }\n\n .monitoring-summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 16px;\n font-size: 0.8em;\n background: rgba(76, 175, 80, 0.1);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n }\n .monitoring-active { color: #4caf50; }\n .monitoring-counts { display: flex; gap: 12px; }\n .count-warning { color: #ff9800; }\n .count-alert { color: #f44336; }\n .count-overrides { color: var(--secondary-text-color); }\n\n .panel-grid {\n display: grid;\n grid-template-columns: 28px 1fr 1fr 28px;\n gap: 8px;\n align-items: stretch;\n }\n\n .tab-label {\n display: flex;\n align-items: center;\n font-size: 0.85em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n user-select: none;\n }\n .tab-left { justify-content: flex-start; }\n .tab-right { justify-content: flex-end; }\n\n .circuit-slot {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px 20px;\n min-height: 140px;\n transition: opacity 0.3s;\n position: relative;\n overflow: hidden;\n }\n\n .circuit-col-span { min-height: 280px; }\n .circuit-row-span { border-left: 3px solid var(--span-accent); }\n .circuit-off .circuit-name,\n .circuit-off .breaker-badge,\n .circuit-off .power-value,\n .circuit-off .chart-container { opacity: 0.35; }\n .circuit-off .toggle-pill,\n .circuit-off .gear-icon { opacity: 1; }\n\n .circuit-empty {\n opacity: 0.2;\n min-height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-style: dashed;\n }\n .empty-label { color: var(--secondary-text-color, #999); font-size: 0.85em; }\n\n .circuit-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 6px;\n gap: 8px;\n }\n\n .circuit-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n\n .breaker-badge {\n background: color-mix(in srgb, var(--span-accent) 15%, transparent);\n color: var(--span-accent);\n font-size: 0.7em;\n font-weight: 700;\n padding: 2px 7px;\n border-radius: 4px;\n white-space: nowrap;\n border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent);\n flex-shrink: 0;\n }\n\n .circuit-name {\n font-size: 0.9em;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--primary-text-color, #e0e0e0);\n }\n\n .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }\n\n .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .power-value strong { font-weight: 700; font-size: 1.1em; }\n .power-unit { font-size: 0.8em; font-weight: 400; color: var(--secondary-text-color, #999); margin-left: 1px; }\n .circuit-producer .power-value strong { color: var(--info-color, #4fc3f7); }\n\n .toggle-pill {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 4px;\n border-radius: 10px;\n cursor: pointer;\n font-size: 0.65em;\n font-weight: 600;\n transition: background 0.2s;\n user-select: none;\n min-width: 40px;\n }\n .toggle-on {\n padding-left: 6px;\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n color: var(--state-active-color, var(--span-accent));\n }\n .toggle-off {\n padding-right: 6px;\n background: color-mix(in srgb, var(--secondary-text-color) 15%, transparent);\n color: var(--secondary-text-color, #999);\n }\n .toggle-knob {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n transition: background 0.2s, margin 0.2s;\n }\n .toggle-on .toggle-knob {\n background: var(--state-active-color, var(--span-accent));\n margin-left: auto;\n }\n .toggle-off .toggle-knob {\n background: var(--secondary-text-color, #999);\n margin-right: auto;\n order: -1;\n }\n\n .circuit-status {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 4px;\n padding: 0 4px;\n }\n .shedding-icon { opacity: 0.8; cursor: default; }\n .shedding-composite {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .shedding-icon-secondary { opacity: 0.8; }\n .shedding-label {\n font-size: 10px;\n font-weight: 600;\n opacity: 0.8;\n }\n .gear-icon {\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px;\n opacity: 0.6;\n transition: opacity 0.2s;\n margin-left: auto;\n }\n .gear-icon:hover { opacity: 1; }\n .utilization {\n font-size: 0.75em;\n font-weight: 600;\n }\n .utilization-normal { color: #4caf50; }\n .utilization-warning { color: #ff9800; }\n .utilization-alert { color: #f44336; }\n .circuit-alert {\n border-color: #f44336 !important;\n box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);\n }\n .circuit-custom-monitoring {\n border-left: 3px solid #ff9800;\n }\n\n .chart-container {\n width: 100%;\n aspect-ratio: 4 / 1;\n margin-top: 4px;\n overflow: hidden;\n min-width: 0;\n }\n\n .sub-devices {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .sub-device {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px;\n }\n .sub-device-bess,\n .sub-device-full {\n grid-column: 1 / -1;\n }\n\n .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; }\n .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); }\n .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; }\n .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .sub-power-value strong { font-weight: 700; font-size: 1.1em; }\n .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; }\n\n .bess-charts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 10px;\n }\n .bess-chart-col { min-width: 0; }\n .bess-chart-title {\n font-size: 0.75em;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--secondary-text-color, #999);\n margin-bottom: 4px;\n }\n .bess-chart-col .chart-container { aspect-ratio: auto; }\n .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; }\n .sub-entity-name { color: var(--secondary-text-color, #999); }\n .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); }\n\n /* ── Shared tab bar ────────────────────────────────────── */\n\n .shared-tab-bar {\n display: flex;\n gap: 0;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .shared-tab {\n padding: 8px 16px;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 500;\n color: var(--primary-text-color);\n opacity: 0.6;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n transition: opacity 0.15s;\n }\n\n .shared-tab:hover {\n opacity: 0.85;\n }\n\n .shared-tab.active {\n opacity: 1;\n border-bottom-color: var(--span-accent);\n }\n\n /* ── List view search ──────────────────────────────────── */\n\n .list-search-container {\n margin-bottom: 12px;\n position: relative;\n }\n\n .list-search {\n width: 100%;\n padding: 8px 36px 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--secondary-background-color, #2a2a2a);\n color: var(--primary-text-color);\n font-size: 0.9em;\n box-sizing: border-box;\n outline: none;\n }\n\n .list-search:focus {\n border-color: var(--span-accent);\n }\n\n .list-search-clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 2px;\n display: flex;\n align-items: center;\n opacity: 0.7;\n }\n\n .list-search-clear:hover {\n opacity: 1;\n }\n\n .list-unit-toggle {\n display: inline-flex;\n margin-bottom: 12px;\n }\n\n /* ── List rows ─────────────────────────────────────────── */\n\n .list-view {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n /* Each circuit is wrapped in a .list-cell so the row + its optional\n expanded chart stay together. In single-column flex mode the cell\n just stacks naturally. In multi-column grid mode the cell becomes\n one grid item, so the chart is always in the same column as its\n row. Area headers (rendered as siblings, not inside a cell) span\n all columns via their inline "grid-column: 1 / -1". */\n .list-cell {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: grid;\n grid-template-columns: repeat(var(--list-cols), minmax(0, 1fr));\n gap: 6px 8px;\n flex-direction: initial;\n }\n /* On narrow viewports a 2/3-column list would squeeze rows into an\n unreadable shape, so force stacking regardless of user preference. */\n @media (max-width: 599px) {\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: flex;\n flex-direction: column;\n }\n }\n\n .list-row {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 10px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n }\n\n .list-row:hover {\n background: var(--secondary-background-color, #2a2a2a);\n }\n\n .list-row.circuit-off {\n opacity: 0.5;\n }\n\n .list-row.list-row-expanded {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n .list-circuit-name {\n flex: 1;\n color: var(--primary-text-color);\n font-size: 0.9em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .list-status-badge {\n font-size: 0.75em;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .list-status-on {\n color: #4dd9af;\n }\n\n .list-status-off {\n color: #f44336;\n }\n\n .list-power-value {\n font-size: 0.9em;\n font-weight: 600;\n min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n }\n\n .list-expand-toggle {\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 4px;\n transition: transform 0.2s;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-expand-toggle.expanded {\n transform: rotate(180deg);\n }\n\n .list-row .gear-icon {\n background: transparent;\n border: none;\n padding: 2px;\n cursor: pointer;\n color: #555;\n display: inline-flex;\n align-items: center;\n }\n .list-row .gear-icon:hover {\n color: var(--primary-text-color);\n }\n\n /* ── Expanded circuit content ──────────────────────────── */\n\n .list-expanded-content {\n padding: 0;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n border-radius: 0 0 8px 8px;\n margin-top: -6px;\n margin-bottom: 2px;\n }\n\n .circuit-slot.circuit-chart-only {\n border: none;\n margin: 0;\n background: none;\n padding: 8px 12px;\n min-height: 0;\n }\n\n /* ── Area headers ──────────────────────────────────────── */\n\n .area-header {\n padding: 16px 12px 6px;\n font-weight: 600;\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n /* ── No results ────────────────────────────────────────── */\n\n .list-no-results {\n padding: 24px;\n text-align: center;\n color: var(--secondary-text-color);\n }\n\n'),b([Ne({attribute:!1})],Xt.prototype,"hass",void 0),b([Me()],Xt.prototype,"_config",void 0),b([Me()],Xt.prototype,"_discovered",void 0),b([Me()],Xt.prototype,"_discovering",void 0),b([Me()],Xt.prototype,"_topology",void 0),b([Me()],Xt.prototype,"_activeTab",void 0),Xt=b([ze("span-panel-card")],Xt);class Zt 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={},this._tabStyleSelect=null}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]===l)&&!e.via_device_id).map(e=>{const t=(e.identifiers??[]).find(e=>e[0]===l)?.[1]??"",n=e.name_by_user??e.name??i("editor.panel_label");return{device_id:e.id,label:`${n} (${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._buildTabStyleSelector(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,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.panel_label"),r.style.cssText=n;const a=document.createElement("select");a.style.cssText=t;const l=document.createElement("option");if(l.value="",l.textContent=i("editor.select_panel"),a.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),a.appendChild(t)}a.addEventListener("change",()=>{this._config={...this._config,device_id:a.value},this._fireConfigChanged(),this._discoverAvailableRoles(a.value)}),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._panelSelect=a}_buildTimeWindow(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.chart_window"),r.style.cssText=n;const a=document.createElement("div");a.style.cssText="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;";const l=t+"width: 70px; cursor: text;",c=(e,t,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=t,o.max=n,o.value=String(e),o.style.cssText=l;const r=document.createElement("span");return r.textContent=i,r.style.cssText="font-size: 0.9em; color: var(--secondary-text-color);",s.appendChild(o),s.appendChild(r),{wrap:s,input:o}},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=c(d,"0","30",i("editor.days")),g=c(h,"0","23",i("editor.hours")),_=c(p,"0","59",i("editor.minutes")),f=()=>{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",f),g.input.addEventListener("change",f),_.input.addEventListener("change",f),a.appendChild(u.wrap),a.appendChild(g.wrap),a.appendChild(_.wrap),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._daysInput=u.input,this._hoursInput=g.input,this._minsInput=_.input}_buildMetricSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.chart_metric"),r.style.cssText=n;const a=document.createElement("select");a.style.cssText=t,a.addEventListener("change",()=>{this._config={...this._config,chart_metric:a.value},this._fireConfigChanged()}),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._metricSelect=a}_buildTabStyleSelector(e,t,n,s){const o=document.createElement("div");o.style.cssText=s;const r=document.createElement("label");r.textContent=i("editor.tab_style"),r.style.cssText=n;const a=document.createElement("select");a.style.cssText=t;const l=[{value:"text",text:i("editor.tab_style_text")},{value:"icon",text:i("editor.tab_style_icon")}];for(const e of l){const t=document.createElement("option");t.value=e.value,t.textContent=e.text,e.value===(this._config.tab_style??"text")&&(t.selected=!0),a.appendChild(t)}a.addEventListener("change",()=>{this._config={...this._config,tab_style:a.value},this._fireConfigChanged()}),o.appendChild(r),o.appendChild(a),e.appendChild(o),this._tabStyleSelect=a}_buildSectionCheckboxes(e,t,n){const s=document.createElement("div");s.style.cssText=n;const o=document.createElement("label");o.textContent=i("editor.visible_sections"),o.style.cssText=t,s.appendChild(o);const r=[{key:"show_panel",label:i("editor.panel_circuits"),subDeviceType:null},{key:"show_battery",label:i("editor.battery_bess"),subDeviceType:"bess"},{key:"show_evse",label:i("editor.ev_charger_evse"),subDeviceType:"evse"}];this._checkboxes={},this._entityContainers={};for(const e of r){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),s.appendChild(t),this._checkboxes[e.key]=n;let o=null;e.subDeviceType&&(o=document.createElement("div"),o.style.cssText="padding-left: 26px;",o.style.display=n.checked?"block":"none",s.appendChild(o),this._entityContainers[e.subDeviceType]=o),n.addEventListener("change",()=>{this._config={...this._config,[e.key]:n.checked},o&&(o.style.display=n.checked?"block":"none"),this._fireConfigChanged()})}e.appendChild(s)}_isChartEntity(e,t,n){const i=(t.original_name??"").toLowerCase(),s=t.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(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,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 r=document.createElement("input");r.type="checkbox",r.checked=!0===t[i],r.style.cssText="width: 16px; height: 16px; cursor: pointer; accent-color: var(--primary-color);";const a=document.createElement("span");let l=s.original_name??i;const c=n.name??"";l.startsWith(c+" ")&&(l=l.slice(c.length+1)),a.textContent=l,a.style.cssText="font-size: 0.85em; color: var(--primary-text-color); cursor: pointer;",o.appendChild(r),o.appendChild(a),e.appendChild(o),r.addEventListener("change",()=>{const e={...this._config.visible_sub_entities??{}};r.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:`${l}/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??o;e.innerHTML="";for(const[n,i]of Object.entries(_)){if(this._availableRoles&&!this._availableRoles.has(i.entityRole))continue;const s=document.createElement("option");s.value=n,s.textContent=i.label(),n===t&&(s.selected=!0),e.appendChild(s)}}_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??o),this._checkboxes)for(const[e,t]of Object.entries(this._checkboxes))t.checked=!1!==this._config[e];this._tabStyleSelect&&(this._tabStyleSelect.value=this._config.tab_style??"text")}_fireConfigChanged(){this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:this._config}}))}}try{customElements.get("span-panel-card-editor")||customElements.define("span-panel-card-editor",Zt)}catch{}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.4 ","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/custom_components/span_panel/frontend/dist/span-panel.js b/custom_components/span_panel/frontend/dist/span-panel.js index 2717bb6c..c5e5b8bd 100644 --- a/custom_components/span_panel/frontend/dist/span-panel.js +++ b/custom_components/span_panel/frontend/dist/span-panel.js @@ -1,70 +1,144 @@ -let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","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.all_targets":"All","notification.event_bus_target":"Event Bus (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:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","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.configure_subdevice":"Configure device","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)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","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.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (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:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","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.configure_subdevice":"Configurar dispositivo","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)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","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.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (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 :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","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.configure_subdevice":"Configurer l'appareil","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)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","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.all_targets":"すべて","notification.event_bus_target":"イベントバス (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":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","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.configure_subdevice":"デバイスを設定","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)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","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.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (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:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","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.configure_subdevice":"Configurar dispositivo","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)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};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",l="pv",c="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},_={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")}},f="#ff9800";function m(e,t,n,i){var o,s=arguments.length,a=s<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)a=Reflect.decorate(e,t,n,i);else for(var r=e.length-1;r>=0;r--)(o=e[r])&&(a=(s<3?o(a):s>3?o(t,n,a):o(t,n))||a);return s>3&&a&&Object.defineProperty(t,n,a),a}"function"==typeof SuppressedError&&SuppressedError; +let e="en";const t={en:{"tab.panel":"Panel","tab.by_panel":"By Panel","tab.by_activity":"By Activity","tab.by_area":"By Area","tab.monitoring":"Monitoring","tab.settings":"Settings","list.search_placeholder":"Search circuits...","list.unassigned_area":"Unassigned","list.no_results":"No circuits found","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.all_targets":"All","notification.event_bus_target":"Event Bus (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:","notification.event_bus_help":"Event Bus fires event type","notification.event_bus_payload":"with payload:","notification.test_label":"Test Notification","notification.test_button":"Send Test","notification.test_sending":"Sending...","notification.test_sent":"Test notification sent","error.prefix":"Error:","error.failed_save":"Failed to save","error.failed":"Failed","error.panel_offline":"SPAN Panel unreachable","error.panel_reconnected":"SPAN Panel reconnected","error.panel_offline_named":"{name} unreachable","error.panel_reconnected_named":"{name} reconnected","error.discovery_failed":"Unable to connect to SPAN Panel","error.relay_failed":"Unable to toggle relay","error.shedding_failed":"Unable to update shedding priority","error.threshold_failed":"Unable to save threshold","error.graph_horizon_failed":"Unable to update graph time horizon","error.favorites_fetch_failed":"Unable to load favorites","error.favorites_toggle_failed":"Unable to update favorite","error.history_failed":"Unable to load historical data","error.monitoring_failed":"Unable to load monitoring status","error.graph_settings_failed":"Unable to load graph settings","error.areas_failed":"Area assignments may be out of sync","error.retry":"Retry","card.connecting":"Connecting to SPAN Panel...","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","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.configure_subdevice":"Configure device","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.favorites_subtitle":"Favorites","sidepanel.global_default":"Global Default","sidepanel.list_view_columns":"List View Columns","sidepanel.columns":"Columns","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.shedding_priority":"Shedding Priority","sidepanel.priority_label":"Priority","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.favorite":"Favorite","sidepanel.save_to_favorites":"Save to favorites","panel.favorites":"Favorites","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.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)","editor.tab_style":"Tab Style","editor.tab_style_text":"Text","editor.tab_style_icon":"Icon","metric.power":"Power","metric.current":"Current","metric.soc":"State of Charge","metric.soe":"State of Energy","shedding.always_on":"Critical","shedding.never":"Non-sheddable","shedding.soc_threshold":"SoC Threshold","shedding.off_grid":"Sheddable","shedding.unknown":"Unknown","shedding.select.never":"Stays on in an outage","shedding.select.soc_threshold":"Stays on until battery threshold","shedding.select.off_grid":"Turns off in an outage"},es:{"tab.panel":"Panel","tab.by_panel":"Por Panel","tab.by_activity":"Por Actividad","tab.by_area":"Por Área","tab.monitoring":"Monitoreo","tab.settings":"Configuración","list.search_placeholder":"Buscar circuitos...","list.unassigned_area":"Sin asignar","list.no_results":"No se encontraron circuitos","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.all_targets":"Todos","notification.event_bus_target":"Bus de Eventos (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:","notification.event_bus_help":"El Bus de Eventos dispara el tipo de evento","notification.event_bus_payload":"con datos:","notification.test_label":"Notificación de prueba","notification.test_button":"Enviar prueba","notification.test_sending":"Enviando...","notification.test_sent":"Notificación de prueba enviada","error.prefix":"Error:","error.failed_save":"Error al guardar","error.failed":"Falló","error.panel_offline":"SPAN Panel inaccesible","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inaccesible","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"No se puede conectar al SPAN Panel","error.relay_failed":"No se pudo cambiar el relé","error.shedding_failed":"No se pudo actualizar la prioridad de desconexión","error.threshold_failed":"No se pudo guardar el umbral","error.graph_horizon_failed":"No se pudo actualizar el horizonte temporal del gráfico","error.favorites_fetch_failed":"No se pudieron cargar los favoritos","error.favorites_toggle_failed":"No se pudo actualizar el favorito","error.history_failed":"No se pudieron cargar los datos históricos","error.monitoring_failed":"No se pudo cargar el estado de monitoreo","error.graph_settings_failed":"No se pudo cargar la configuración del gráfico","error.areas_failed":"Las asignaciones de áreas pueden estar desincronizadas","error.retry":"Reintentar","card.connecting":"Conectando al SPAN Panel...","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","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.configure_subdevice":"Configurar dispositivo","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.favorites_subtitle":"Favoritos","sidepanel.global_default":"Predeterminado Global","sidepanel.list_view_columns":"Columnas de la lista","sidepanel.columns":"Columnas","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.shedding_priority":"Prioridad de Desconexción","sidepanel.priority_label":"Prioridad","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.favorite":"Favorito","sidepanel.save_to_favorites":"Guardar en favoritos","panel.favorites":"Favoritos","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.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)","editor.tab_style":"Estilo de pestañas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícono","metric.power":"Potencia","metric.current":"Corriente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energía","shedding.always_on":"Crítico","shedding.never":"No desconectable","shedding.soc_threshold":"Umbral SoC","shedding.off_grid":"Desconectable","shedding.unknown":"Desconocido","shedding.select.never":"Permanece encendido en un corte","shedding.select.soc_threshold":"Encendido hasta umbral de batería","shedding.select.off_grid":"Se apaga en un corte"},fr:{"tab.panel":"Panneau","tab.by_panel":"Par Panneau","tab.by_activity":"Par Activité","tab.by_area":"Par Zone","tab.monitoring":"Surveillance","tab.settings":"Paramètres","list.search_placeholder":"Rechercher des circuits...","list.unassigned_area":"Non attribué","list.no_results":"Aucun circuit trouvé","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.all_targets":"Tous","notification.event_bus_target":"Bus d'événements (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 :","notification.event_bus_help":"Le Bus d'événements déclenche le type d'événement","notification.event_bus_payload":"avec les données :","notification.test_label":"Notification de test","notification.test_button":"Envoyer un test","notification.test_sending":"Envoi...","notification.test_sent":"Notification de test envoyée","error.prefix":"Erreur :","error.failed_save":"Échec de la sauvegarde","error.failed":"Échoué","error.panel_offline":"SPAN Panel inaccessible","error.panel_reconnected":"SPAN Panel reconnecté","error.panel_offline_named":"{name} inaccessible","error.panel_reconnected_named":"{name} reconnecté","error.discovery_failed":"Impossible de se connecter au SPAN Panel","error.relay_failed":"Impossible de basculer le relais","error.shedding_failed":"Impossible de mettre à jour la priorité de délestage","error.threshold_failed":"Impossible d'enregistrer le seuil","error.graph_horizon_failed":"Impossible de mettre à jour l'horizon temporel du graphique","error.favorites_fetch_failed":"Impossible de charger les favoris","error.favorites_toggle_failed":"Impossible de mettre à jour le favori","error.history_failed":"Impossible de charger les données historiques","error.monitoring_failed":"Impossible de charger l'état de surveillance","error.graph_settings_failed":"Impossible de charger les paramètres du graphique","error.areas_failed":"Les affectations de zones peuvent être désynchronisées","error.retry":"Réessayer","card.connecting":"Connexion au SPAN Panel...","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","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.configure_subdevice":"Configurer l'appareil","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.favorites_subtitle":"Favoris","sidepanel.global_default":"Défaut Global","sidepanel.list_view_columns":"Colonnes de la liste","sidepanel.columns":"Colonnes","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.shedding_priority":"Priorité de Délestage","sidepanel.priority_label":"Priorité","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.favorite":"Favori","sidepanel.save_to_favorites":"Enregistrer dans les favoris","panel.favorites":"Favoris","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.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)","editor.tab_style":"Style des onglets","editor.tab_style_text":"Texte","editor.tab_style_icon":"Icône","metric.power":"Puissance","metric.current":"Courant","metric.soc":"État de Charge","metric.soe":"État d'Énergie","shedding.always_on":"Critique","shedding.never":"Non délestable","shedding.soc_threshold":"Seuil SoC","shedding.off_grid":"Délestable","shedding.unknown":"Inconnu","shedding.select.never":"Reste allumé en cas de coupure","shedding.select.soc_threshold":"Allumé jusqu'au seuil batterie","shedding.select.off_grid":"S'éteint en cas de coupure"},ja:{"tab.panel":"パネル","tab.by_panel":"パネル別","tab.by_activity":"活動別","tab.by_area":"エリア別","tab.monitoring":"モニタリング","tab.settings":"設定","list.search_placeholder":"回路を検索...","list.unassigned_area":"未割り当て","list.no_results":"回路が見つかりません","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.all_targets":"すべて","notification.event_bus_target":"イベントバス (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":"プレースホルダー:","notification.event_bus_help":"イベントバスが発行するイベントタイプ","notification.event_bus_payload":"ペイロード:","notification.test_label":"テスト通知","notification.test_button":"テスト送信","notification.test_sending":"送信中...","notification.test_sent":"テスト通知を送信しました","error.prefix":"エラー:","error.failed_save":"保存に失敗","error.failed":"失敗","error.panel_offline":"SPANパネルに接続できません","error.panel_reconnected":"SPANパネルが再接続されました","error.panel_offline_named":"{name}に接続できません","error.panel_reconnected_named":"{name}が再接続されました","error.discovery_failed":"SPANパネルへの接続に失敗しました","error.relay_failed":"リレーの切り替えに失敗しました","error.shedding_failed":"シェディング優先度の更新に失敗しました","error.threshold_failed":"しきい値の保存に失敗しました","error.graph_horizon_failed":"グラフの時間範囲の更新に失敗しました","error.favorites_fetch_failed":"お気に入りの読み込みに失敗しました","error.favorites_toggle_failed":"お気に入りの更新に失敗しました","error.history_failed":"履歴データの読み込みに失敗しました","error.monitoring_failed":"監視ステータスの読み込みに失敗しました","error.graph_settings_failed":"グラフ設定の読み込みに失敗しました","error.areas_failed":"エリア割り当てが同期されていない可能性があります","error.retry":"再試行","card.connecting":"SPANパネルに接続中...","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","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.configure_subdevice":"デバイスを設定","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.favorites_subtitle":"お気に入り","sidepanel.global_default":"グローバルデフォルト","sidepanel.list_view_columns":"リスト表示の列数","sidepanel.columns":"列","sidepanel.circuit_scales":"回路グラフスケール","sidepanel.subdevice_scales":"サブデバイスグラフスケール","sidepanel.reset_to_global":"グローバルにリセット","sidepanel.relay":"リレー","sidepanel.breaker":"ブレーカー","sidepanel.shedding_priority":"シェディング優先度","sidepanel.priority_label":"優先度","sidepanel.monitoring":"モニタリング","sidepanel.global":"グローバル","sidepanel.custom":"カスタム","sidepanel.continuous_pct":"継続 %","sidepanel.spike_pct":"スパイク %","sidepanel.window_duration":"ウィンドウ時間","sidepanel.cooldown":"クールダウン","sidepanel.favorite":"お気に入り","sidepanel.save_to_favorites":"お気に入りに保存","panel.favorites":"お気に入り","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.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)","editor.tab_style":"タブスタイル","editor.tab_style_text":"テキスト","editor.tab_style_icon":"アイコン","metric.power":"電力","metric.current":"電流","metric.soc":"充電状態","metric.soe":"エネルギー状態","shedding.always_on":"重要","shedding.never":"切断不可","shedding.soc_threshold":"SoCしきい値","shedding.off_grid":"切断可能","shedding.unknown":"不明","shedding.select.never":"停電時もオンを維持","shedding.select.soc_threshold":"バッテリーしきい値までオン","shedding.select.off_grid":"停電時にオフ"},pt:{"tab.panel":"Painel","tab.by_panel":"Por Painel","tab.by_activity":"Por Atividade","tab.by_area":"Por Área","tab.monitoring":"Monitoramento","tab.settings":"Configurações","list.search_placeholder":"Pesquisar circuitos...","list.unassigned_area":"Não atribuído","list.no_results":"Nenhum circuito encontrado","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.all_targets":"Todos","notification.event_bus_target":"Barramento de Eventos (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:","notification.event_bus_help":"O Barramento de Eventos dispara o tipo de evento","notification.event_bus_payload":"com dados:","notification.test_label":"Notificação de teste","notification.test_button":"Enviar teste","notification.test_sending":"Enviando...","notification.test_sent":"Notificação de teste enviada","error.prefix":"Erro:","error.failed_save":"Falha ao salvar","error.failed":"Falhou","error.panel_offline":"SPAN Panel inacessível","error.panel_reconnected":"SPAN Panel reconectado","error.panel_offline_named":"{name} inacessível","error.panel_reconnected_named":"{name} reconectado","error.discovery_failed":"Não foi possível conectar ao SPAN Panel","error.relay_failed":"Não foi possível alternar o relé","error.shedding_failed":"Não foi possível atualizar a prioridade de desligamento","error.threshold_failed":"Não foi possível salvar o limite","error.graph_horizon_failed":"Não foi possível atualizar o horizonte temporal do gráfico","error.favorites_fetch_failed":"Não foi possível carregar os favoritos","error.favorites_toggle_failed":"Não foi possível atualizar o favorito","error.history_failed":"Não foi possível carregar os dados históricos","error.monitoring_failed":"Não foi possível carregar o status de monitoramento","error.graph_settings_failed":"Não foi possível carregar as configurações do gráfico","error.areas_failed":"As atribuições de áreas podem estar fora de sincronização","error.retry":"Tentar novamente","card.connecting":"Conectando ao SPAN Panel...","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","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.configure_subdevice":"Configurar dispositivo","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.favorites_subtitle":"Favoritos","sidepanel.global_default":"Padrão Global","sidepanel.list_view_columns":"Colunas da Lista","sidepanel.columns":"Colunas","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.shedding_priority":"Prioridade de Desligamento","sidepanel.priority_label":"Prioridade","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.favorite":"Favorito","sidepanel.save_to_favorites":"Salvar nos favoritos","panel.favorites":"Favoritos","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.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)","editor.tab_style":"Estilo das abas","editor.tab_style_text":"Texto","editor.tab_style_icon":"Ícone","metric.power":"Potência","metric.current":"Corrente","metric.soc":"Estado de Carga","metric.soe":"Estado de Energia","shedding.always_on":"Crítico","shedding.never":"Não desligável","shedding.soc_threshold":"Limite SoC","shedding.off_grid":"Desligável","shedding.unknown":"Desconhecido","shedding.select.never":"Permanece ligado em uma queda","shedding.select.soc_threshold":"Ligado até limite da bateria","shedding.select.off_grid":"Desliga em uma queda"}};function n(n){return t[e]?.[n]??t.en?.[n]??n}function i(n,i){return(t[e]?.[n]??t.en?.[n]??n).replace(/\{(\w+)\}/g,(e,t)=>Object.prototype.hasOwnProperty.call(i,t)?i[t]:`{${t}}`)}const s="power",o="5m",r={"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",l="CLOSED",c="pv",d="bess",h="evse",p="sub_",u=500,g={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)}},_={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:g.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")}},v="#ff9800";function m(e,t,n,i){var s,o=arguments.length,r=o<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,n):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)r=Reflect.decorate(e,t,n,i);else for(var a=e.length-1;a>=0;a--)(s=e[a])&&(r=(o<3?s(r):o>3?s(t,n,r):s(t,n))||r);return o>3&&r&&Object.defineProperty(t,n,r),r}"function"==typeof SuppressedError&&SuppressedError; /** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const b=globalThis,v=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,y=Symbol(),w=new WeakMap;let x=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==y)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(v&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=w.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&w.set(t,e))}return e}toString(){return this.cssText}};const $=v?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return(e=>new x("string"==typeof e?e:e+"",void 0,y))(t)})(e):e,{is:S,defineProperty:C,getOwnPropertyDescriptor:E,getOwnPropertyNames:k,getOwnPropertySymbols:z,getPrototypeOf:A}=Object,P=globalThis,M=P.trustedTypes,T=M?M.emptyScript:"",N=P.reactiveElementPolyfillSupport,D=(e,t)=>e,L={toAttribute(e,t){switch(t){case Boolean:e=e?T:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},H=(e,t)=>!S(e,t),I={attribute:!0,type:String,converter:L,reflect:!1,useDefault:!1,hasChanged:H}; +const b=globalThis,y=b.ShadowRoot&&(void 0===b.ShadyCSS||b.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,w=Symbol(),x=new WeakMap;let S=class{constructor(e,t,n){if(this._$cssResult$=!0,n!==w)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o;const t=this.t;if(y&&void 0===e){const n=void 0!==t&&1===t.length;n&&(e=x.get(t)),void 0===e&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),n&&x.set(t,e))}return e}toString(){return this.cssText}};const $=e=>new S("string"==typeof e?e:e+"",void 0,w),C=(e,...t)=>{const n=1===e.length?e[0]:t.reduce((t,n,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+e[i+1],e[0]);return new S(n,e,w)},P=y?e=>e:e=>e instanceof CSSStyleSheet?(e=>{let t="";for(const n of e.cssRules)t+=n.cssText;return $(t)})(e):e,{is:k,defineProperty:E,getOwnPropertyDescriptor:z,getOwnPropertyNames:A,getOwnPropertySymbols:N,getPrototypeOf:M}=Object,I=globalThis,T=I.trustedTypes,D=T?T.emptyScript:"",F=I.reactiveElementPolyfillSupport,L=(e,t)=>e,H={toAttribute(e,t){switch(t){case Boolean:e=e?D:null;break;case Object:case Array:e=null==e?e:JSON.stringify(e)}return e},fromAttribute(e,t){let n=e;switch(t){case Boolean:n=null!==e;break;case Number:n=null===e?null:Number(e);break;case Object:case Array:try{n=JSON.parse(e)}catch(e){n=null}}return n}},O=(e,t)=>!k(e,t),R={attribute:!0,type:String,converter:H,reflect:!1,useDefault:!1,hasChanged:O}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */Symbol.metadata??=Symbol("metadata"),P.litPropertyMetadata??=new WeakMap;let O=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=I){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&C(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:o}=E(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const s=i?.call(this);o?.call(this,t),this.requestUpdate(e,s,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??I}static _$Ei(){if(this.hasOwnProperty(D("elementProperties")))return;const e=A(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(D("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(D("properties"))){const e=this.properties,t=[...k(e),...z(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift($(e))}else void 0!==e&&t.push($(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(v)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=b.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const o=(void 0!==n.converter?.toAttribute?n.converter:L).toAttribute(t,n.type);this._$Em=e,null==o?this.removeAttribute(i):this.setAttribute(i,o),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),o="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:L;this._$Em=i;const s=o.fromAttribute(t,e.type);this[i]=s??this._$Ej?.get(i)??s,this._$Em=null}}requestUpdate(e,t,n,i=!1,o){if(void 0!==e){const s=this.constructor;if(!1===i&&(o=this[e]),n??=s.getPropertyOptions(e),!((n.hasChanged??H)(o,t)||n.useDefault&&n.reflect&&o===this._$Ej?.get(e)&&!this.hasAttribute(s._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:o},s){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,s??t??this[e]),!0!==o||void 0!==s)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};O.elementStyles=[],O.shadowRootOptions={mode:"open"},O[D("elementProperties")]=new Map,O[D("finalized")]=new Map,N?.({ReactiveElement:O}),(P.reactiveElementVersions??=[]).push("2.1.2"); + */Symbol.metadata??=Symbol("metadata"),I.litPropertyMetadata??=new WeakMap;let q=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=R){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){const n=Symbol(),i=this.getPropertyDescriptor(e,n,t);void 0!==i&&E(this.prototype,e,i)}}static getPropertyDescriptor(e,t,n){const{get:i,set:s}=z(this.prototype,e)??{get(){return this[t]},set(e){this[t]=e}};return{get:i,set(t){const o=i?.call(this);s?.call(this,t),this.requestUpdate(e,o,n)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??R}static _$Ei(){if(this.hasOwnProperty(L("elementProperties")))return;const e=M(this);e.finalize(),void 0!==e.l&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(L("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(L("properties"))){const e=this.properties,t=[...A(e),...N(e)];for(const n of t)this.createProperty(n,e[n])}const e=this[Symbol.metadata];if(null!==e){const t=litPropertyMetadata.get(e);if(void 0!==t)for(const[e,n]of t)this.elementProperties.set(e,n)}this._$Eh=new Map;for(const[e,t]of this.elementProperties){const n=this._$Eu(e,t);void 0!==n&&this._$Eh.set(n,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){const t=[];if(Array.isArray(e)){const n=new Set(e.flat(1/0).reverse());for(const e of n)t.unshift(P(e))}else void 0!==e&&t.push(P(e));return t}static _$Eu(e,t){const n=t.attribute;return!1===n?void 0:"string"==typeof n?n:"string"==typeof e?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),void 0!==this.renderRoot&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){const e=new Map,t=this.constructor.elementProperties;for(const n of t.keys())this.hasOwnProperty(n)&&(e.set(n,this[n]),delete this[n]);e.size>0&&(this._$Ep=e)}createRenderRoot(){const e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return((e,t)=>{if(y)e.adoptedStyleSheets=t.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(const n of t){const t=document.createElement("style"),i=b.litNonce;void 0!==i&&t.setAttribute("nonce",i),t.textContent=n.cssText,e.appendChild(t)}})(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,n){this._$AK(e,n)}_$ET(e,t){const n=this.constructor.elementProperties.get(e),i=this.constructor._$Eu(e,n);if(void 0!==i&&!0===n.reflect){const s=(void 0!==n.converter?.toAttribute?n.converter:H).toAttribute(t,n.type);this._$Em=e,null==s?this.removeAttribute(i):this.setAttribute(i,s),this._$Em=null}}_$AK(e,t){const n=this.constructor,i=n._$Eh.get(e);if(void 0!==i&&this._$Em!==i){const e=n.getPropertyOptions(i),s="function"==typeof e.converter?{fromAttribute:e.converter}:void 0!==e.converter?.fromAttribute?e.converter:H;this._$Em=i;const o=s.fromAttribute(t,e.type);this[i]=o??this._$Ej?.get(i)??o,this._$Em=null}}requestUpdate(e,t,n,i=!1,s){if(void 0!==e){const o=this.constructor;if(!1===i&&(s=this[e]),n??=o.getPropertyOptions(e),!((n.hasChanged??O)(s,t)||n.useDefault&&n.reflect&&s===this._$Ej?.get(e)&&!this.hasAttribute(o._$Eu(e,n))))return;this.C(e,t,n)}!1===this.isUpdatePending&&(this._$ES=this._$EP())}C(e,t,{useDefault:n,reflect:i,wrapped:s},o){n&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,o??t??this[e]),!0!==s||void 0!==o)||(this._$AL.has(e)||(this.hasUpdated||n||(t=void 0),this._$AL.set(e,t)),!0===i&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}const e=this.scheduleUpdate();return null!=e&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[e,t]of this._$Ep)this[e]=t;this._$Ep=void 0}const e=this.constructor.elementProperties;if(e.size>0)for(const[t,n]of e){const{wrapped:e}=n,i=this[t];!0!==e||this._$AL.has(t)||void 0===i||this.C(t,void 0,n,i)}}let e=!1;const t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(e=>e.hostUpdate?.()),this.update(t)):this._$EM()}catch(t){throw e=!1,this._$EM(),t}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(e){}firstUpdated(e){}};q.elementStyles=[],q.shadowRootOptions={mode:"open"},q[L("elementProperties")]=new Map,q[L("finalized")]=new Map,F?.({ReactiveElement:q}),(I.reactiveElementVersions??=[]).push("2.1.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const R=globalThis,q=e=>e,j=R.trustedTypes,U=j?j.createPolicy("lit-html",{createHTML:e=>e}):void 0,G="$lit$",F=`lit$${Math.random().toFixed(9).slice(2)}$`,W="?"+F,B=`<${W}>`,V=document,Q=()=>V.createComment(""),J=e=>null===e||"object"!=typeof e&&"function"!=typeof e,X=Array.isArray,K="[ \t\n\f\r]",Z=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Y=/-->/g,ee=/>/g,te=RegExp(`>|${K}(?:([^\\s"'>=/]+)(${K}*=${K}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),ne=/'/g,ie=/"/g,oe=/^(?:script|style|textarea|title)$/i,se=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ae=Symbol.for("lit-noChange"),re=Symbol.for("lit-nothing"),le=new WeakMap,ce=V.createTreeWalker(V,129);function de(e,t){if(!X(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==U?U.createHTML(t):t}const he=(e,t)=>{const n=e.length-1,i=[];let o,s=2===t?"":3===t?"":"",a=Z;for(let t=0;t"===l[0]?(a=o??Z,c=-1):void 0===l[1]?c=-2:(c=a.lastIndex-l[2].length,r=l[1],a=void 0===l[3]?te:'"'===l[3]?ie:ne):a===ie||a===ne?a=te:a===Y||a===ee?a=Z:(a=te,o=void 0);const h=a===te&&e[t+1].startsWith("/>")?" ":"";s+=a===Z?n+B:c>=0?(i.push(r),n.slice(0,c)+G+n.slice(c)+F+h):n+F+(-2===c?t:h)}return[de(e,s+(e[n]||"")+(2===t?"":3===t?"":"")),i]};class pe{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let o=0,s=0;const a=e.length-1,r=this.parts,[l,c]=he(e,t);if(this.el=pe.createElement(l,n),ce.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=ce.nextNode())&&r.length0){i.textContent=j?j.emptyScript:"";for(let n=0;nX(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==re&&J(this._$AH)?this._$AA.nextSibling.data=e:this.T(V.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=pe.createElement(de(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new ge(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=le.get(e.strings);return void 0===t&&le.set(e.strings,t=new pe(e)),t}k(e){X(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const o of e)i===t.length?t.push(n=new _e(this.O(Q()),this.O(Q()),this,this.options)):n=t[i],n._$AI(o),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=re}_$AI(e,t=this,n,i){const o=this.strings;let s=!1;if(void 0===o)e=ue(this,e,t,0),s=!J(e)||e!==this._$AH&&e!==ae,s&&(this._$AH=e);else{const i=e;let a,r;for(e=o[0],a=0;ae,W=j.trustedTypes,G=W?W.createPolicy("lit-html",{createHTML:e=>e}):void 0,V="$lit$",B=`lit$${Math.random().toFixed(9).slice(2)}$`,Q="?"+B,K=`<${Q}>`,J=document,X=()=>J.createComment(""),Z=e=>null===e||"object"!=typeof e&&"function"!=typeof e,Y=Array.isArray,ee="[ \t\n\f\r]",te=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,ne=/-->/g,ie=/>/g,se=RegExp(`>|${ee}(?:([^\\s"'>=/]+)(${ee}*=${ee}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),oe=/'/g,re=/"/g,ae=/^(?:script|style|textarea|title)$/i,le=(e=>(t,...n)=>({_$litType$:e,strings:t,values:n}))(1),ce=Symbol.for("lit-noChange"),de=Symbol.for("lit-nothing"),he=new WeakMap,pe=J.createTreeWalker(J,129);function ue(e,t){if(!Y(e)||!e.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==G?G.createHTML(t):t}const ge=(e,t)=>{const n=e.length-1,i=[];let s,o=2===t?"":3===t?"":"",r=te;for(let t=0;t"===l[0]?(r=s??te,c=-1):void 0===l[1]?c=-2:(c=r.lastIndex-l[2].length,a=l[1],r=void 0===l[3]?se:'"'===l[3]?re:oe):r===re||r===oe?r=se:r===ne||r===ie?r=te:(r=se,s=void 0);const h=r===se&&e[t+1].startsWith("/>")?" ":"";o+=r===te?n+K:c>=0?(i.push(a),n.slice(0,c)+V+n.slice(c)+B+h):n+B+(-2===c?t:h)}return[ue(e,o+(e[n]||"")+(2===t?"":3===t?"":"")),i]};class _e{constructor({strings:e,_$litType$:t},n){let i;this.parts=[];let s=0,o=0;const r=e.length-1,a=this.parts,[l,c]=ge(e,t);if(this.el=_e.createElement(l,n),pe.currentNode=this.el.content,2===t||3===t){const e=this.el.content.firstChild;e.replaceWith(...e.childNodes)}for(;null!==(i=pe.nextNode())&&a.length0){i.textContent=W?W.emptyScript:"";for(let n=0;nY(e)||"function"==typeof e?.[Symbol.iterator])(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==de&&Z(this._$AH)?this._$AA.nextSibling.data=e:this.T(J.createTextNode(e)),this._$AH=e}$(e){const{values:t,_$litType$:n}=e,i="number"==typeof n?this._$AC(e):(void 0===n.el&&(n.el=_e.createElement(ue(n.h,n.h[0]),this.options)),n);if(this._$AH?._$AD===i)this._$AH.p(t);else{const e=new ve(i,this),n=e.u(this.options);e.p(t),this.T(n),this._$AH=e}}_$AC(e){let t=he.get(e.strings);return void 0===t&&he.set(e.strings,t=new _e(e)),t}k(e){Y(this._$AH)||(this._$AH=[],this._$AR());const t=this._$AH;let n,i=0;for(const s of e)i===t.length?t.push(n=new me(this.O(X()),this.O(X()),this,this.options)):n=t[i],n._$AI(s),i++;i2||""!==n[0]||""!==n[1]?(this._$AH=Array(n.length-1).fill(new String),this.strings=n):this._$AH=de}_$AI(e,t=this,n,i){const s=this.strings;let o=!1;if(void 0===s)e=fe(this,e,t,0),o=!Z(e)||e!==this._$AH&&e!==ce,o&&(this._$AH=e);else{const i=e;let r,a;for(e=s[0],r=0;r{const i=n?.renderBefore??t;let o=i._$litPart$;if(void 0===o){const e=n?.renderBefore??null;i._$litPart$=o=new _e(t.insertBefore(Q(),e),e,void 0,n??{})}return o._$AI(e),o})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ae}};$e._$litElement$=!0,$e.finalized=!0,xe.litElementHydrateSupport?.({LitElement:$e});const Se=xe.litElementPolyfillSupport;Se?.({LitElement:$e}),(xe.litElementVersions??=[]).push("4.2.2"); + */let Pe=class extends q{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){const e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){const t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=((e,t,n)=>{const i=n?.renderBefore??t;let s=i._$litPart$;if(void 0===s){const e=n?.renderBefore??null;i._$litPart$=s=new me(t.insertBefore(X(),e),e,void 0,n??{})}return s._$AI(e),s})(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ce}};Pe._$litElement$=!0,Pe.finalized=!0,Ce.litElementHydrateSupport?.({LitElement:Pe});const ke=Ce.litElementPolyfillSupport;ke?.({LitElement:Pe}),(Ce.litElementVersions??=[]).push("4.2.2"); /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause */ -const Ce={attribute:!0,type:String,converter:L,reflect:!1,hasChanged:H},Ee=(e=Ce,t,n)=>{const{kind:i,metadata:o}=n;let s=globalThis.litPropertyMetadata.get(o);if(void 0===s&&globalThis.litPropertyMetadata.set(o,s=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),s.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const o=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,o,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const o=this[i];t.call(this,n),this.requestUpdate(i,o,e,!0,n)}}throw Error("Unsupported decorator location: "+i)}; +const Ee=e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)},ze={attribute:!0,type:String,converter:H,reflect:!1,hasChanged:O},Ae=(e=ze,t,n)=>{const{kind:i,metadata:s}=n;let o=globalThis.litPropertyMetadata.get(s);if(void 0===o&&globalThis.litPropertyMetadata.set(s,o=new Map),"setter"===i&&((e=Object.create(e)).wrapped=!0),o.set(n.name,e),"accessor"===i){const{name:i}=n;return{set(n){const s=t.get.call(this);t.set.call(this,n),this.requestUpdate(i,s,e,!0,n)},init(t){return void 0!==t&&this.C(i,void 0,e,t),t}}}if("setter"===i){const{name:i}=n;return function(n){const s=this[i];t.call(this,n),this.requestUpdate(i,s,e,!0,n)}}throw Error("Unsupported decorator location: "+i)}; /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function ke(e){return(t,n)=>"object"==typeof n?Ee(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)} + */function Ne(e){return(t,n)=>"object"==typeof n?Ae(e,t,n):((e,t,n)=>{const i=t.hasOwnProperty(n);return t.constructor.createProperty(n,e),i?Object.getOwnPropertyDescriptor(t,n):void 0})(e,t,n)} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */function ze(e){return ke({...e,state:!0,attribute:!1})} + */function Me(e){return Ne({...e,state:!0,attribute:!1})} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */const Ae=2;class Pe{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,n){this._$Ct=e,this._$AM=t,this._$Ci=n}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}} + */const Ie=2;class Te{constructor(e){}get _$AU(){return this._$AM._$AU}_$AT(e,t,n){this._$Ct=e,this._$AM=t,this._$Ci=n}_$AS(e,t){return this.update(e,t)}update(e,t){return this.render(...t)}} /** * @license * Copyright 2017 Google LLC * SPDX-License-Identifier: BSD-3-Clause - */class Me extends Pe{constructor(e){if(super(e),this.it=re,e.type!==Ae)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(e){if(e===re||null==e)return this._t=void 0,this.it=e;if(e===ae)return e;if("string"!=typeof e)throw Error(this.constructor.directiveName+"() called with a non-string value");if(e===this.it)return this._t;this.it=e;const t=[e];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}}Me.directiveName="unsafeHTML",Me.resultType=1;const Te=(e=>(...t)=>({_$litDirective$:e,values:t}))(Me),Ne={"&":"&","<":"<",">":">",'"':""","'":"'"};function De(e){return String(e).replace(/[&<>"']/g,e=>Ne[e]??e)}const Le=Object.keys(_).filter(e=>"unknown"!==e&&"always_on"!==e);class He 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 l=t.graphSettings,c=t.topology,d=l?.global_horizon??o,h=l?.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 _=document.createElement("div");_.className="field-row";const f=document.createElement("span");f.className="field-label",f.textContent=n("sidepanel.global_default"),_.appendChild(f);const m=document.createElement("select");for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,o=n(i);t.textContent=o!==i?o: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}`))}),_.appendChild(m),u.appendChild(_),a.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 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},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.uuid=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,o=n(i);t.textContent=o!==i?o:e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`circuit-${t}`,p,()=>{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)}a.appendChild(e)}const b=l?.sub_devices??{};if(c?.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(c.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=b[t]||{horizon:d,has_override:!1},l=r.has_override?r.horizon:d,c=document.createElement("select");c.dataset.subdevId=t;for(const e of Object.keys(s)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,o=n(i);t.textContent=o!==i?o:e,e===l&&(t.selected=!0),c.appendChild(t)}if(c.addEventListener("change",()=>{this._debounce(`subdev-${t}`,p,()=>{this._callDomainService("set_subdevice_graph_horizon",{subdevice_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_subdevice_graph_horizon",{subdevice_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)}a.appendChild(e)}e.appendChild(a)}_renderCircuitMode(e,t){const n=`${De(String(t.breaker_rating_a))}A · ${De(String(t.voltage))}V · Tabs [${De(String(t.tabs))}]`,i=this._createHeader(De(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),t.showMonitoring&&this._renderMonitoringSection(o,t)}_renderSubDeviceMode(e,t){const n=this._createHeader(De(t.name),De(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,l=!0===r?.has_override,c=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=l?c:"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=De(e),s=De(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,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(`${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,l=this._hass?.states?.[r]?.state||"";for(const e of Le){const t=_[e];if(!t)continue;const i=document.createElement("option");i.value=e,i.textContent=n(`shedding.select.${e}`)||t.label(),e===l&&(i.selected=!0),a.appendChild(i)}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,l=!0===r?.has_override,c=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=l?c:"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,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=!0===r?.has_override,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,c.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,f=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",f,1,180,"m",t)),c.appendChild(p),a.addEventListener("change",()=>{const e=a.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 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,l=!1){const c=document.createElement("div");c.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}`,l&&(u.disabled=!0);const g=document.createElement("span");return g.textContent=a,h.appendChild(u),h.appendChild(g),l||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}`))})}),c.appendChild(d),c.appendChild(h),c}_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}):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)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",He)}catch{}async function Ie(e,t){const[n,i,o]=await Promise.all([e.callWS({type:"config/area_registry/list"}),e.callWS({type:"config/entity_registry/list"}),e.callWS({type:"config/device_registry/list"})]),s=new Map;for(const e of n)s.set(e.area_id,e.name);const a=new Map;for(const e of i)e.area_id&&a.set(e.entity_id,e.area_id);const r=new Map;for(const e of o)r.set(e.id,e.area_id);let l;if(t.device_id){const e=r.get(t.device_id);e&&(l=s.get(e))}for(const e of Object.values(t.circuits)){let t;for(const n of Object.values(e.entities)){if(!n)continue;const e=a.get(n);if(e){t=s.get(e);break}}t||(t=l),e.area=t}}async function Oe(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"}),r=(l=s.find(e=>e.id===t),l?{id:l.id,name:l.name,name_by_user:l.name_by_user,config_entries:l.config_entries,identifiers:l.identifiers,via_device_id:l.via_device_id,sw_version:l.sw_version,model:l.model}:null);var l;return await Ie(e,i),{topology:i,panelDevice:r,panelSize:o}}const Re=u.power;function qe(e){return Re.unit(e)}function je(e){return(e<0?"-":"")+Re.format(e)}function Ue(e){return(Math.abs(e)/1e3).toFixed(1)}function Ge(e){return Math.ceil(e/2)}function Fe(e){return e%2==0?1:0}function We(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return Ge(t)===Ge(n)?"row-span":Fe(t)===Fe(n)?"col-span":"row-span"}function Be(e){const t=e.chart_metric??i;return u[t]??u[i]}function Ve(e,t){const n=function(e){return Be(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class Qe{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 Je(e,t){return e?.circuits?e.circuits[t]??null:null}function Xe(e){if(!e?.utilization_pct)return"";const t=e.utilization_pct;return t>=100?"utilization-alert":t>=80?"utilization-warning":"utilization-normal"}function Ke(e,t,i,o,s,a,c,d,h,p=!1){const u=t.entities?.power,g=u?a.states[u]:null,m=g&&parseFloat(g.state)||0,b=t.device_type===l||m<0,v=t.entities?.switch,y=v?a.states[v]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===r,x=t.breaker_rating_a,$=x?`${Math.round(x)}A`:"",S=De(t.name||n("grid.unknown")),C=Be(c);let E;if("current"===C.entityRole){const e=t.entities?.current,n=e?a.states[e]:null,i=n&&parseFloat(n.state)||0;E=`${C.format(i)}A`}else E=`${je(m)}${qe(m)}`;const k=h||"unknown";let z="";if("unknown"!==k){const e=_[k]??_.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};z=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}const A=d&&function(e){return!!e&&void 0!==e.continuous_threshold_pct}(d),P=A?f:"#555",M=``;let T="";if(null!=d?.utilization_pct){const e=d.utilization_pct;T=`${Math.round(e)}%`}const N=function(e){return!!e&&null!=e.over_threshold_since}(d);return`\n
\n
\n
\n ${$?`${$}`:""}\n ${S}\n
\n
\n \n ${E}\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 ${z}\n ${T}\n ${M}\n
\n
\n
\n `}function Ze(e,t){return`\n
\n \n
\n `}const Ye={names:["power","battery power"],suffixes:["_power"]},et={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},tt={names:["state of energy"],suffixes:["_soe_kwh"]},nt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function it(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 ot(e){return it(e,Ye)}function st(e){return it(e,et)}function at(e){return it(e,tt)}function rt(e){return it(e,nt)}function lt(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 ${De(l)}:\n ${De(d)}\n
\n `}return s}function ct(e,t,i,o,s,a){if(i){const t=[{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);return`\n
\n ${t.map(e=>`\n
\n
${De(e.title)}
\n
\n
\n `).join("")}\n
\n `}return o?`
`:""}function dt(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 ht(e){const t=s[e];return t?t.ms:s[o].ms}function pt(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function ut(e){return Math.max(500,Math.floor(e/5e3))}function gt(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 _t(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 ft(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 mt(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=pt(i),l=ut(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,_t(t,r,l))}}}function bt(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:ot(i)};i.type===c&&(e.soc=st(i),e.soe=at(i));for(const[i,o]of Object.entries(e))o&&t.push({entityId:o,key:`${h}${n}_${i}`,devId:n})}return t}async function vt(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=Ve(i,n);if(!t)continue;let s;s=o&&o.has(e)?ht(o.get(e)):dt(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 bt(t)){let t;t=s&&s.has(o)?ht(s.get(o)):dt(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(ft(e,n.entityIds,n.uuidByEntity,t,i)):r.push(mt(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(r)}function yt(e,t,n,o,s,a,r,l,c){const{options:d,series:h}=function(e,t,n,o,s,a=!1){n||(n=u[i]);const r=o?"140, 160, 220":"77, 217, 175",l=`rgb(${r})`,c=Date.now(),d=c-t,h=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=(e??[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),g=[{type:"line",data:p,showSymbol:!1,smooth:!1,...a?{}:{step:"end"},lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${r}, 0.35)`},{offset:1,color:`rgba(${r}, 0.02)`}]}},itemStyle:{color:l}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):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}}};h?(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:[[d,.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:[[d,s],[c,s]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const m={xAxis:{type:"time",min:d,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||0===e.length)return"";const t=e[0],i=new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),o=parseFloat(t.value[1].toFixed(2));return`
${i}
${n.format(o)} ${n.unit(o)}
`}},animation:!1};return{options:m,series:g}}(n,o,s,a,l,c),p=r??120;e.style.minHeight=p+"px";let g=e.querySelector("ha-chart-base");g||(g=document.createElement("ha-chart-base"),g.style.display="block",g.style.width="100%",g.hass=t,e.innerHTML="",e.appendChild(g));const _=e.clientHeight;g.height=(_>0?_:p)+"px",g.hass=t,g.options=d,g.data=h}function wt(e,t,i,o,s,a){if(!e||!i||!t)return;const c=dt(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 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=Ue(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=Ue(e),c&&(c.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=Ue(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=Ue(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=Be(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,f=g&&parseFloat(g.state)||0,m=d.device_type===l||f<0,b=d.entities?.switch,v=b?t.states[b]:null,y=v?"on"===v.state:(g?.attributes?.relay_state||d.relay_state)===r,w=i.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${je(f)}${qe(f)}`;const x=i.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let $;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",m),d.always_on)$="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;$=n?n.state:"unknown"}const S=_[$]??_.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",S.icon),C.style.color=S.color,C.title=S.label());const E=i.querySelector(".shedding-icon-secondary");E&&(S.icon2?(E.setAttribute("icon",S.icon2),E.style.color=S.color,E.style.display=""):E.style.display="none");const k=i.querySelector(".shedding-label");k&&(S.textLabel?(k.textContent=S.textLabel,k.style.color=S.color,k.style.display=""):k.style.display="none");const z=i.querySelector(".chart-container");if(z){const e=s.get(o)||[],n=i.classList.contains("circuit-col-span")?200:100,r=a?.has(o)?ht(a.get(o)):c,p=d.device_type===l;yt(z,t,e,r,h,m,n,d.breaker_rating_a??void 0,p)}}}class xt{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 $t(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function St(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class Ct{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new Qe,this.graphSettingsCache=new xt,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._showMonitoring=!1,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}set showMonitoring(e){this._showMonitoring=e}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(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,$t(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,St(e,t))}async fetchAndBuildHorizonMaps(){try{await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings)}catch{}}async loadHistory(){await vt(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=Ve(n,this._config);if(!a)continue;const r=this._hass.states[a];if(!r)continue;const l=parseFloat(r.state);if(isNaN(l))continue;const c=ht(i),d=pt(c),h=ut(c),p=e-c,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time0&&this._topology)for(const{key:e,devId:t}of bt(this._topology))n.has(t)&&i.add(e);const o=new Map;try{await vt(this._hass,this._topology,this._config,o,t,n);for(const e of t.keys()){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}for(const e of i){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}this.updateDOM(e)}catch{}}updateDOM(e){this._hass&&this._topology&&this._config&&(wt(e,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(e,t,n,i,o,s){if(!n.sub_devices)return;const a=dt(i);for(const[i,r]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${i}"]`);if(!n)continue;const l=ot(r);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,o=n.querySelector(".sub-power-value");o&&(o.innerHTML=`${je(i)} ${qe(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey;if(!n)continue;const r=o.get(n)||[];let l=g.power;n.endsWith("_soc")?l=g.soc:n.endsWith("_soe")&&(l=g.soe);const c=!!e.closest(".bess-chart-col");yt(e,t,r,s?.has(i)?ht(s.get(i)):a,l,!1,c?120:150,void 0,n.endsWith("_soc")||n.endsWith("_soe"))}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){let e;if(t.formatEntityState)e=t.formatEntityState(o);else{e=o.state;const t=o.attributes.unit_of_measurement||"";t&&(e+=" "+t)}if("Wh"===(o.attributes.unit_of_measurement||"")){const t=parseFloat(o.state);isNaN(t)||(e=(t/1e3).toFixed(1)+" kWh")}i.textContent=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 l=r.entities?.switch;if(!l)return;const c=this._hass.states[l];if(!c)return void console.warn("SPAN Panel: switch entity not found:",l);const d="on"===c.state?"turn_off":"turn_on";this._hass.callService("switch",d,{},{entity_id:l}).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?.current??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,l=i?.circuits?.[a],c=l?{...l,globalHorizon:r}:{horizon:r,has_override:!1,globalHorizon:r};return void s.open({...e,uuid:a,monitoringInfo:n,showMonitoring:this._showMonitoring,graphHorizonInfo:c})}}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"))},l=e=>{if(!o)return;const t=Math.max(2,Math.min(e-s,a));n.style.left=t+"px"},c=()=>{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=>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"))})}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()}}const Et="\n :host {\n --span-accent: var(--primary-color, #4dd9af);\n }\n\n ha-card {\n padding: 24px;\n background: var(--card-background-color, #1c1c1c);\n color: var(--primary-text-color, #e0e0e0);\n border-radius: var(--ha-card-border-radius, 12px);\n border: var(--ha-card-border-width, 1px) solid var(--ha-card-border-color, var(--divider-color, #333));\n box-shadow: var(--ha-card-box-shadow, none);\n }\n\n .panel-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: flex-start;\n gap: 8px 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .header-left { flex: 1 1 300px; min-width: 0; }\n .header-center { flex: 0 0 auto; }\n .header-right { flex: 0 1 auto; min-width: 0; }\n\n .panel-identity {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-bottom: 12px;\n }\n\n .panel-title {\n font-size: 1.8em;\n font-weight: 700;\n margin: 0;\n color: var(--primary-text-color, #fff);\n }\n\n .panel-serial {\n font-size: 0.85em;\n color: var(--secondary-text-color, #999);\n font-family: monospace;\n }\n\n .panel-stats {\n display: flex;\n flex-wrap: wrap;\n gap: 16px 32px;\n }\n\n .stat { display: flex; flex-direction: column; }\n .stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }\n .stat-row { display: flex; align-items: baseline; gap: 2px; }\n .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); }\n .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); }\n\n .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; }\n .header-right-top { display: flex; gap: 20px; align-items: center; }\n .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); }\n\n .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }\n .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; }\n .shedding-legend-item ha-icon { --mdc-icon-size: 16px; }\n .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; }\n .shedding-legend-text { font-size: 9px; font-weight: 600; }\n .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); }\n\n .panel-gear {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color);\n opacity: 0.6;\n padding: 4px;\n margin-left: 8px;\n vertical-align: middle;\n }\n .panel-gear:hover { opacity: 1; }\n .header-center {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 8px;\n }\n .panel-identity .panel-gear {\n margin-left: 0;\n }\n .slide-confirm {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 160px;\n height: 28px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333));\n vertical-align: middle;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n }\n .slide-confirm-text {\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 0.65em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n pointer-events: none;\n z-index: 0;\n }\n .slide-confirm-knob {\n position: absolute;\n left: 2px;\n top: 2px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--secondary-text-color, #666);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n z-index: 1;\n transition: none;\n }\n .slide-confirm-knob ha-icon {\n --mdc-icon-size: 14px;\n color: var(--card-background-color, #1c1c1c);\n }\n .slide-confirm-knob.snapping {\n transition: left 0.25s ease;\n }\n .slide-confirm.confirmed {\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n }\n .slide-confirm.confirmed .slide-confirm-text {\n color: var(--state-active-color, var(--span-accent));\n }\n .slide-confirm.confirmed .slide-confirm-knob {\n background: var(--state-active-color, var(--span-accent));\n }\n .switches-disabled .toggle-pill {\n opacity: 0.3;\n pointer-events: none;\n }\n .unit-toggle {\n display: inline-flex;\n background: var(--secondary-background-color, #333);\n border-radius: 6px;\n overflow: hidden;\n margin-left: 8px;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n background: none;\n color: var(--secondary-text-color);\n font-size: 0.75em;\n font-weight: 600;\n cursor: pointer;\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #4dd9af);\n color: var(--text-primary-color, #000);\n }\n\n .monitoring-summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 16px;\n font-size: 0.8em;\n background: rgba(76, 175, 80, 0.1);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n }\n .monitoring-active { color: #4caf50; }\n .monitoring-counts { display: flex; gap: 12px; }\n .count-warning { color: #ff9800; }\n .count-alert { color: #f44336; }\n .count-overrides { color: var(--secondary-text-color); }\n\n .panel-grid {\n display: grid;\n grid-template-columns: 28px 1fr 1fr 28px;\n gap: 8px;\n align-items: stretch;\n }\n\n .tab-label {\n display: flex;\n align-items: center;\n font-size: 0.85em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n user-select: none;\n }\n .tab-left { justify-content: flex-start; }\n .tab-right { justify-content: flex-end; }\n\n .circuit-slot {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px 20px;\n min-height: 140px;\n transition: opacity 0.3s;\n position: relative;\n overflow: hidden;\n }\n\n .circuit-col-span { min-height: 280px; }\n .circuit-row-span { border-left: 3px solid var(--span-accent); }\n .circuit-off .circuit-name,\n .circuit-off .breaker-badge,\n .circuit-off .power-value,\n .circuit-off .chart-container { opacity: 0.35; }\n .circuit-off .toggle-pill,\n .circuit-off .gear-icon { opacity: 1; }\n\n .circuit-empty {\n opacity: 0.2;\n min-height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-style: dashed;\n }\n .empty-label { color: var(--secondary-text-color, #999); font-size: 0.85em; }\n\n .circuit-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 6px;\n gap: 8px;\n }\n\n .circuit-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n\n .breaker-badge {\n background: color-mix(in srgb, var(--span-accent) 15%, transparent);\n color: var(--span-accent);\n font-size: 0.7em;\n font-weight: 700;\n padding: 2px 7px;\n border-radius: 4px;\n white-space: nowrap;\n border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent);\n flex-shrink: 0;\n }\n\n .circuit-name {\n font-size: 0.9em;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--primary-text-color, #e0e0e0);\n }\n\n .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }\n\n .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .power-value strong { font-weight: 700; font-size: 1.1em; }\n .power-unit { font-size: 0.8em; font-weight: 400; color: var(--secondary-text-color, #999); margin-left: 1px; }\n .circuit-producer .power-value strong { color: var(--info-color, #4fc3f7); }\n\n .toggle-pill {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 4px;\n border-radius: 10px;\n cursor: pointer;\n font-size: 0.65em;\n font-weight: 600;\n transition: background 0.2s;\n user-select: none;\n min-width: 40px;\n }\n .toggle-on {\n padding-left: 6px;\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n color: var(--state-active-color, var(--span-accent));\n }\n .toggle-off {\n padding-right: 6px;\n background: color-mix(in srgb, var(--secondary-text-color) 15%, transparent);\n color: var(--secondary-text-color, #999);\n }\n .toggle-knob {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n transition: background 0.2s, margin 0.2s;\n }\n .toggle-on .toggle-knob {\n background: var(--state-active-color, var(--span-accent));\n margin-left: auto;\n }\n .toggle-off .toggle-knob {\n background: var(--secondary-text-color, #999);\n margin-right: auto;\n order: -1;\n }\n\n .circuit-status {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 4px;\n padding: 0 4px;\n }\n .shedding-icon { opacity: 0.8; cursor: default; }\n .shedding-composite {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .shedding-icon-secondary { opacity: 0.8; }\n .shedding-label {\n font-size: 10px;\n font-weight: 600;\n opacity: 0.8;\n }\n .gear-icon {\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px;\n opacity: 0.6;\n transition: opacity 0.2s;\n margin-left: auto;\n }\n .gear-icon:hover { opacity: 1; }\n .utilization {\n font-size: 0.75em;\n font-weight: 600;\n }\n .utilization-normal { color: #4caf50; }\n .utilization-warning { color: #ff9800; }\n .utilization-alert { color: #f44336; }\n .circuit-alert {\n border-color: #f44336 !important;\n box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);\n }\n .circuit-custom-monitoring {\n border-left: 3px solid #ff9800;\n }\n\n .chart-container {\n width: 100%;\n aspect-ratio: 4 / 1;\n margin-top: 4px;\n overflow: hidden;\n min-width: 0;\n }\n\n .sub-devices {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .sub-device {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px;\n }\n .sub-device-bess,\n .sub-device-full {\n grid-column: 1 / -1;\n }\n\n .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; }\n .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); }\n .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; }\n .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .sub-power-value strong { font-weight: 700; font-size: 1.1em; }\n .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; }\n\n .bess-charts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 10px;\n }\n .bess-chart-col { min-width: 0; }\n .bess-chart-title {\n font-size: 0.75em;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--secondary-text-color, #999);\n margin-bottom: 4px;\n }\n .bess-chart-col .chart-container { aspect-ratio: auto; }\n .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; }\n .sub-entity-name { color: var(--secondary-text-color, #999); }\n .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); }\n\n /* ── Shared tab bar ────────────────────────────────────── */\n\n .shared-tab-bar {\n display: flex;\n gap: 0;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .shared-tab {\n padding: 8px 16px;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 500;\n color: var(--primary-text-color);\n opacity: 0.6;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n transition: opacity 0.15s;\n }\n\n .shared-tab:hover {\n opacity: 0.85;\n }\n\n .shared-tab.active {\n opacity: 1;\n border-bottom-color: var(--span-accent);\n }\n\n /* ── List view search ──────────────────────────────────── */\n\n .list-search-container {\n margin-bottom: 12px;\n position: relative;\n }\n\n .list-search {\n width: 100%;\n padding: 8px 36px 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--secondary-background-color, #2a2a2a);\n color: var(--primary-text-color);\n font-size: 0.9em;\n box-sizing: border-box;\n outline: none;\n }\n\n .list-search:focus {\n border-color: var(--span-accent);\n }\n\n .list-search-clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 2px;\n display: flex;\n align-items: center;\n opacity: 0.7;\n }\n\n .list-search-clear:hover {\n opacity: 1;\n }\n\n .list-unit-toggle {\n display: inline-flex;\n margin-bottom: 12px;\n }\n\n /* ── List rows ─────────────────────────────────────────── */\n\n .list-view {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n\n .list-row {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 10px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n }\n\n .list-row:hover {\n background: var(--secondary-background-color, #2a2a2a);\n }\n\n .list-row.circuit-off {\n opacity: 0.5;\n }\n\n .list-row.list-row-expanded {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n .list-circuit-name {\n flex: 1;\n color: var(--primary-text-color);\n font-size: 0.9em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .list-status-badge {\n font-size: 0.75em;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .list-status-on {\n color: #4dd9af;\n }\n\n .list-status-off {\n color: #f44336;\n }\n\n .list-power-value {\n font-size: 0.9em;\n font-weight: 600;\n min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n }\n\n .list-expand-toggle {\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 4px;\n transition: transform 0.2s;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-expand-toggle.expanded {\n transform: rotate(180deg);\n }\n\n /* ── Expanded circuit content ──────────────────────────── */\n\n .list-expanded-content {\n padding: 12px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n border-radius: 0 0 8px 8px;\n margin-top: -6px;\n margin-bottom: 2px;\n }\n\n .list-expanded-content .circuit-slot {\n border: none;\n margin: 0;\n background: none;\n }\n\n /* ── Area headers ──────────────────────────────────────── */\n\n .area-header {\n padding: 16px 12px 6px;\n font-weight: 600;\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n /* ── No results ────────────────────────────────────────── */\n\n .list-no-results {\n padding: 24px;\n text-align: center;\n color: var(--secondary-text-color);\n }\n\n";class kt{constructor(){this._ctrl=new Ct,this._container=null,this._onGearClick=null,this._onToggleClick=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._ctrl.reset(),this._ctrl.showMonitoring=!0,this._container=e,this._ctrl.hass=t;try{const e=await Oe(t,i);a=e.topology,r=e.panelSize}catch(t){return void(e.innerHTML=`

${De(t.message)}

`)}this._ctrl.init(a,o,t,s??null),await this._ctrl.monitoringCache.fetch(t,s??null),await this._ctrl.fetchAndBuildHorizonMaps();const l=Math.ceil(r/2),h=this._ctrl.monitoringCache.status,p=function(e,t){const i=De(e.device_name||n("header.default_name")),o=De(e.serial||""),s=De(e.firmware||""),a="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,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 ${l?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${c?`\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(_).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 `}(h),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":We(e)??"single";s.set(i,{uuid:t,circuit:n,layout:o});for(const t of e)a.add(t)}const r=new Set,l=new Set;for(const[e,t]of s)if("col-span"===t.layout){const n=t.circuit.tabs,i=Ge(Math.max(...n));0===Fe(e)?r.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=o?Je(o,t??""):null;let s;if(e.circuit.always_on)s="always_on";else{const t=e.circuit.entities?.select;s=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:s}}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}=c(h);d+=Ke(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+=Ze(e,"2"));else{const{monInfo:t,sheddingPriority:o}=c(h);d+=Ke(h.uuid,h.circuit,e,"2",h.layout,n,i,t,o)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)a.has(o)||(d+=Ze(e,"3"));else{const{monInfo:t,sheddingPriority:o}=c(p);d+=Ke(p.uuid,p.circuit,e,"3",p.layout,n,i,t,o)}d+=`
${o}
`}return d}(a,l,t,o,h),f=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===c&&!o||e.type===d&&!s));if(0===a.length)return"";const r=a.filter(([,e])=>e.type===d).length;let l=0,h="";for(const[e,o]of a){const s=o.type===d?n("subdevice.ev_charger"):o.type===c?n("subdevice.battery"):n("subdevice.fallback"),a=ot(o),p=a?t.states[a]:void 0,u=p&&parseFloat(p.state)||0,g=o.type===c,_=o.type===d,f=g?st(o):null,m=g?at(o):null,b=g?rt(o):null,v=lt(o,t,i,new Set([a,f,m,b].filter(e=>null!==e))),y=ct(e,0,g,a,f,m);let w="";g?w="sub-device-bess":_&&(l++,l===r&&r%2==1&&(w="sub-device-full")),h+=`\n
\n
\n ${De(s)}\n ${De(o.name||"")}\n ${a?`${je(u)} ${qe(u)}`:""}\n \n
\n ${y}\n ${v}\n
\n `}return h}(a,t,o);e.innerHTML=`\n \n ${p}\n ${u}\n ${f?`
${f}
`:""}\n ${!1!==o.show_panel?`\n
\n ${g}\n
\n `:""}\n \n `,this._onGearClick=t=>{this._ctrl.onGearClick(t,e)},this._onToggleClick=t=>{this._ctrl.onToggleClick(t,e)},e.addEventListener("click",this._onGearClick),e.addEventListener("click",this._onToggleClick),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 m=e.querySelector(".slide-confirm");m&&(this._ctrl.bindSlideConfirm(m,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&(this._onGearClick&&(this._container.removeEventListener("click",this._onGearClick),this._onGearClick=null),this._onToggleClick&&(this._container.removeEventListener("click",this._onToggleClick),this._onToggleClick=null),this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null))}}const zt="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",At="\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",Pt="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",Mt="\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 Nt(e,t,n,i,o){return`\n ${i}\n `}class Dt{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,l=o?.circuits??{},c=o?.mains??{},d=new Set;for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&d.add(e);const h=new Set(["notify","send_message"]);for(const e of Object.keys(t.services?.notify||{}))h.has(e)||d.add(`notify.${e}`);d.add("event_bus");const p=[...d].sort(),u=s.notify_targets??"",g=("string"==typeof u?u.split(","):u).map(e=>e.trim()).filter(Boolean),_=p.length>0&&p.every(e=>g.includes(e)),f=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)",b=s.notification_priority??"default",v=Object.entries(l).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),y=Object.entries(c),w=[...v,...y],x=w.length>0&&w.every(([,e])=>!1!==e.monitoring_enabled),$=w.some(([,e])=>!1!==e.monitoring_enabled),S=v.map(([e,t])=>{const i=De(t.name??e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=De(e);return`\n \n \n \n \n ${Nt(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${Nt(r,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${Nt(r,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${Nt(r,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${s?``:""}\n \n \n `}).join(""),C=Object.entries(c).map(([e,t])=>{const i=De(t.name??e),o=!1!==t.monitoring_enabled,s=!0===t.has_override,a=o?"":"opacity:0.4;",r=De(e);return`\n \n \n \n \n ${Nt(r,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${Nt(r,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${Nt(r,"window_duration_m",t.window_duration_m,"m","mains")}\n ${Nt(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
\n ${0===p.length?`
${n("notification.no_targets")}
`:p.map(e=>{const i=g.includes(e),o="event_bus"===e,s=o?null:t.states[e],a=s?.attributes?.friendly_name,r=o?n("notification.event_bus_target"):a?`${De(a)} (${De(e)})`:De(e);return``}).join("")}\n
\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} {local_time}\n
\n
\n ${n("notification.event_bus_help")} span_panel_current_alert\n ${n("notification.event_bus_payload")} alert_source alert_id\n alert_name alert_type current_a\n breaker_rating_a threshold_pct utilization_pct\n panel_serial window_duration_s local_time\n
\n\n
\n ${n("notification.test_label")}\n \n \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 ${S}\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&&!x&&$&&(E.indeterminate=!0);const k=e.querySelector("#notify-all-targets");if(k&&p.length>0){const e=g.length>0;!_&&e&&(k.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: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)"}}},p)};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;const r=()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);if(s){const e=i.map(e=>"event_bus"===e?n("notification.event_bus_target"):e);s.textContent=e.length?e.join(", "):n("notification.none_selected")}const o=e.querySelector("#notify-all-targets");if(o){const t=[...e.querySelectorAll(".notify-target-cb")];o.checked=t.length>0&&t.every(e=>e.checked),o.indeterminate=!o.checked&&t.some(e=>e.checked)}this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch{}},p)},l=e.querySelector("#notify-all-targets");l&&l.addEventListener("change",()=>{for(const t of e.querySelectorAll(".notify-target-cb"))t.checked=l.checked;const t=e.querySelector("#notify-target-btn");t&&(t.style.opacity=l.checked?"0.4":"",t.style.pointerEvents=l.checked?"none":""),l.checked&&(o.style.display="none"),r()});for(const t of e.querySelectorAll(".notify-target-cb"))t.addEventListener("change",()=>{r()})}_bindNotificationSettings(e,t){const i=e.querySelector("#g-priority"),o=e.querySelector("#g-title-template"),s=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{}},p)};i&&i.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:i.value}),await this.render(e,t)}catch{}}),o&&o.addEventListener("input",()=>{r("notification_title_template",o.value)}),s&&s.addEventListener("input",()=>{r("notification_message_template",s.value)});const l=e.querySelector("#test-notification-btn"),c=e.querySelector("#test-notification-status");l&&l.addEventListener("click",async()=>{l.disabled=!0,c&&(c.textContent=n("notification.test_sending"),c.style.color="var(--secondary-text-color)");try{this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null);const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value).join(", ");await this._callSetGlobal(t,{notify_targets:i});const o={};this._configEntryId&&(o.config_entry_id=this._configEntryId),await t.callWS({type:"call_service",domain:a,service:"test_notification",service_data:o}),c&&(c.textContent=n("notification.test_sent"),c.style.color="var(--success-color, #4caf50)")}catch(e){if(c){const t=e instanceof Error?e.message:n("error.failed");c.textContent=`${n("error.prefix")} ${t}`,c.style.color="var(--error-color, #f44336)"}}finally{l.disabled=!1}})}_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 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,l="mains"===r?"set_mains_threshold":"set_circuit_threshold",c="mains"===r?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:l,service_data:this._serviceData({[c]: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;if(!i)return;const 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 Lt(e=""){const t=e?` value="${De(e)}"`:"",i=e?"":"display:none;";return`\n
\n \n \n
\n `}function Ht(e){const t="current"===(e.chart_metric||"power");return`\n
\n \n \n
\n `}function It(e,t,i,o,s,a,l){const c=t.entities?.power,d=c?i.states[c]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?i.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===r,f=t.breaker_rating_a,m=f?`${Math.round(f)}A`:"",b=De(t.name||n("grid.unknown")),v=Be(o),y="current"===v.entityRole;let w;if(g)if(y){const e=t.entities?.current,n=e?i.states[e]:null,o=n&&parseFloat(n.state)||0;w=`${v.format(o)}A`}else w=`${je(h)}${qe(h)}`;else w="";const x=a||"unknown";let $="";if("unknown"!==x){const e=_[x]??_.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};$=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let S="";if(null!=s?.utilization_pct){const e=s.utilization_pct;S=`${Math.round(e)}%`}const C=g?'ON':'OFF';return`\n
\n ${m?`${m}`:""}\n ${b}\n ${$}\n ${S}\n ${C}\n \n ${w}\n \n \n
\n `}function Ot(e,t,n,i,o,s){const a=Ke(e,t,0,"1","single",n,i,o,s,!0);return`
${a}
`}function Rt(e){return`
${De(e)}
`}function qt(e,t,n){const i=e.entities?.switch,o=i?t.states[i]:null,s=e.entities?.power,a=s?t.states[s]:null,l=o?"on"===o.state:(a?.attributes?.relay_state||e.relay_state)===r;let c;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;c=i?Math.abs(parseFloat(i.state)||0):0}else c=a?Math.abs(parseFloat(a.state)||0):0;return{isOn:l,value:c}}function jt(e,t){if(e.always_on)return"always_on";const n=e.entities?.select,i=n?t.states[n]:null;return i?i.state:"unknown"}function Ut(e,t,n){return e.sort((e,i)=>{const o=qt(e[1],t,n),s=qt(i[1],t,n);return o.isOn&&!s.isOn?-1:!o.isOn&&s.isOn?1:s.value-o.value})}function Gt(e){return e.entities?.current??e.entities?.power??""}class Ft{constructor(e){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._ctrl=e}renderActivityView(e,t,n,i,o){this._unbindEvents(),this._hass=t,this._topology=n,this._config=i,this._monitoringStatus=o;const s=Ut(Object.entries(n.circuits),t,i);let a=Lt(this._searchQuery)+Ht(i);a+='
';for(const[e,n]of s){const s=Je(o,Gt(n)),r=jt(n,t),l=this._expandedUuids.has(e);a+=It(e,n,t,i,s,r,l),l&&(a+=Ot(e,n,t,i,s,r))}a+="
",a+="",e.innerHTML=a;const r=e.querySelector("span-side-panel");r&&(r.hass=t),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}renderAreaView(e,t,i,o,s){this._unbindEvents(),this._hass=t,this._topology=i,this._config=o,this._monitoringStatus=s;const a=n("list.unassigned_area"),r=new Map;for(const[e,t]of Object.entries(i.circuits)){const n=t.area??a,i=r.get(n);i?i.push([e,t]):r.set(n,[[e,t]])}const l=[...r.keys()].sort((e,t)=>e===a?1:t===a?-1:e.localeCompare(t));let c=Lt(this._searchQuery)+Ht(o);c+='
';for(const e of l){const n=r.get(e);if(!n)continue;const i=Ut(n,t,o);c+=Rt(e);for(const[e,n]of i){const i=Je(s,Gt(n)),a=jt(n,t),r=this._expandedUuids.has(e);c+=It(e,n,t,o,i,a,r),r&&(c+=Ot(e,n,t,o,i,a))}}c+="
",c+="",e.innerHTML=c;const d=e.querySelector("span-side-panel");d&&(d.hass=t),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}updateCollapsedRows(e,t,n,i){const o=Be(i),s="current"===o.entityRole,a=e.querySelectorAll(".list-row[data-row-uuid]");for(const e of a){const a=e.dataset.rowUuid;if(!a)continue;const r=n.circuits[a];if(!r)continue;const{isOn:l,value:c}=qt(r,t,i),d=e.querySelector(".list-power-value");if(d)if(l)if(s)d.innerHTML=`${o.format(c)}A`;else{const e=r.entities?.power,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;d.innerHTML=`${je(i)}${qe(i)}`}else d.innerHTML="";const h=e.querySelector(".list-status-badge");h&&(h.textContent=l?"ON":"OFF",h.classList.toggle("list-status-on",l),h.classList.toggle("list-status-off",!l)),e.classList.toggle("circuit-off",!l)}}stop(){this._unbindEvents(),this._expandedUuids.clear(),this._searchQuery="",this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_bindEvents(e){this._container=e,this._clickHandler=t=>{const n=t.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const e=i.dataset.expandUuid;return void(e&&this._toggleExpand(e))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(t,e);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(t,e);if(n.closest(".list-search-clear")){const t=e.querySelector(".list-search");return void(t&&(t.value="",t.dispatchEvent(new Event("input",{bubbles:!0}))))}const o=n.closest(".unit-btn");if(o){const t=o.dataset.unit;t&&e.dispatchEvent(new CustomEvent("unit-changed",{detail:t,bubbles:!0,composed:!0}))}},this._inputHandler=t=>{const n=t.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(e))},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(e).then(()=>{this._ctrl.updateDOM(e)}).catch(()=>{})},e.addEventListener("click",this._clickHandler),e.addEventListener("input",this._inputHandler),e.addEventListener("graph-settings-changed",this._graphSettingsHandler)}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_applyFilter(e){const t=e.querySelector(".list-search-clear");t&&(t.style.display=this._searchQuery?"":"none");const n=e.querySelectorAll(".list-row[data-row-uuid]");for(const t of n){const n=t.querySelector(".list-circuit-name"),i=(n?.textContent?.toLowerCase()??"").includes(this._searchQuery);t.style.display=i?"":"none";const o=t.dataset.rowUuid;if(o){const t=e.querySelector(`.list-expanded-content[data-expanded-uuid="${o}"]`);t&&(t.style.display=i?"":"none")}}const i=e.querySelectorAll(".area-header");for(const e of i){let t=!1,n=e.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-row")&&"none"!==n.style.display){t=!0;break}n=n.nextElementSibling}e.style.display=t?"":"none"}}_toggleExpand(e){if(!(this._container&&this._hass&&this._topology&&this._config))return;const t=this._container.querySelector(`.list-row[data-row-uuid="${e}"]`),n=this._container.querySelector(`.list-expand-toggle[data-expand-uuid="${e}"]`);if(t)if(this._expandedUuids.has(e)){this._expandedUuids.delete(e);const i=this._container.querySelector(`.list-expanded-content[data-expanded-uuid="${e}"]`);i&&i.remove(),n&&n.classList.remove("expanded"),t.classList.remove("list-row-expanded")}else{this._expandedUuids.add(e);const i=this._topology.circuits[e];if(!i)return;const o=Je(this._monitoringStatus,Gt(i)),s=jt(i,this._hass),a=Ot(e,i,this._hass,this._config,o,s);t.insertAdjacentHTML("afterend",a),n&&n.classList.add("expanded"),t.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}}}let Wt=class extends $e{constructor(){super(...arguments),this.narrow=!1,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._discoveryError=null,this._dashboardTab=new kt,this._monitoringTab=new Dt,this._listDashCtrl=new Ct,this._listCtrl=new Ft(this._listDashCtrl),this._areaUnsub=null,this._onVisibilityChange=null,this._deviceRegistryUnsub=null}connectedCallback(){super.connectedCallback(),this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&this._scheduleTabRender()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._monitoringTab.stop(),this._listCtrl.stop(),this._listDashCtrl.stopIntervals(),this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._unsubscribeDeviceRegistry(),super.disconnectedCallback()}firstUpdated(){this.hass&&!this._discovered&&this._discoverPanels()}updated(e){if(e.has("hass")){const t=e.get("hass");this._dashboardTab.hass=this.hass,this._listDashCtrl.hass=this.hass;const n=this.renderRoot.querySelector("ha-menu-button");n&&(n.hass=this.hass,n.narrow=this.narrow),this._discovered?this.shadowRoot.getElementById("tab-content")||this._scheduleTabRender():this._discoverPanels(),!t&&this.hass&&this._subscribeDeviceRegistry()}if(e.has("narrow")){const e=this.renderRoot.querySelector("ha-menu-button");e&&(e.narrow=this.narrow)}if(this._discovered&&(e.has("_discovered")||e.has("_activeTab")||e.has("_selectedPanelId")||e.has("_chartMetric"))&&this._scheduleTabRender(),e.has("hass")&&this._discovered&&("activity"===this._activeTab||"area"===this._activeTab)){const e=this.shadowRoot.getElementById("tab-content"),t=this._listDashCtrl.topology;if(e&&t){this._listCtrl.updateCollapsedRows(e,this.hass,t,this._buildDashboardConfig());const n=e.querySelector("span-side-panel");n&&(n.hass=this.hass)}}}setConfig(e){}render(){var i,o,s;return i=this.hass?.language,e=i&&t[i]?i:"en",this._discovered?se` + */class De extends Te{constructor(e){if(super(e),this.it=de,e.type!==Ie)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(e){if(e===de||null==e)return this._t=void 0,this.it=e;if(e===ce)return e;if("string"!=typeof e)throw Error(this.constructor.directiveName+"() called with a non-string value");if(e===this.it)return this._t;this.it=e;const t=[e];return t.raw=t,this._t={_$litType$:this.constructor.resultType,strings:t,values:[]}}}De.directiveName="unsafeHTML",De.resultType=1;const Fe=(e=>(...t)=>({_$litDirective$:e,values:t}))(De);class Le{constructor(){this._persistent=new Map,this._transient=null,this._transientTimer=null,this._subscribers=new Set,this._watchedPanels=new Map}add(e){const t={...e,timestamp:Date.now()};if(t.persistent)this._persistent.set(t.key,t);else{this._clearTransient(),this._transient=t;const e=t.ttl??5e3;this._transientTimer=setTimeout(()=>{this._transient=null,this._transientTimer=null,this._notify()},e)}this._notify()}remove(e){if(this._persistent.has(e))return this._persistent.delete(e),void this._notify();this._transient?.key===e&&(this._clearTransient(),this._notify())}clear(e){void 0===e?(this._persistent.clear(),this._clearTransient(),this._watchedPanels.clear()):!0===e.persistent?this._persistent.clear():!1===e.persistent&&this._clearTransient(),this._notify()}get active(){const e=[...this._persistent.values()];return null!==this._transient&&e.push(this._transient),e}hasPersistent(e){return this._persistent.has(e)}hasAnyPanelOffline(){for(const e of this._persistent.keys())if("panel-offline"===e||e.startsWith("panel-offline:"))return!0;return!1}subscribe(e){return this._subscribers.add(e),()=>{this._subscribers.delete(e)}}watchPanelStatus(e){this.watchPanelStatuses([{entityId:e,panelName:null}])}watchPanelStatuses(e){const t=this._watchedPanels,n=new Map;for(const i of e){const e=t.get(i.entityId);n.set(i.entityId,{panelName:i.panelName??null,wasOffline:e?.wasOffline??!1})}const i=this._isSingleUnnamed(t),s=this._isSingleUnnamed(n);for(const e of t.keys()){n.has(e)&&i===s||this._persistent.delete(this._offlineKey(e,i))}this._watchedPanels=n,this._notify()}clearPanelStatusWatch(){if(0===this._watchedPanels.size)return;const e=this._isSingleUnnamed(this._watchedPanels);for(const t of this._watchedPanels.keys())this._persistent.delete(this._offlineKey(t,e));this._watchedPanels.clear(),this._notify()}updateHass(e){if(0===this._watchedPanels.size)return;const t=this._isSingleUnnamed(this._watchedPanels);for(const[s,o]of this._watchedPanels){const r=e.states[s]?.state,a="on"===r,l=this._offlineKey(s,t),c=this._reconnectKey(s,t);if(a){const e=o.wasOffline;o.wasOffline=!1,this.remove(l),e&&this.add({key:c,level:"info",message:null===o.panelName?n("error.panel_reconnected"):i("error.panel_reconnected_named",{name:o.panelName}),persistent:!1})}else o.wasOffline=!0,this.hasPersistent(l)||this.add({key:l,level:"error",message:null===o.panelName?n("error.panel_offline"):i("error.panel_offline_named",{name:o.panelName}),persistent:!0})}}dispose(){this._clearTransient(),this._persistent.clear(),this._subscribers.clear(),this._watchedPanels.clear()}_isSingleUnnamed(e){if(1!==e.size)return!1;for(const t of e.values())return null===t.panelName;return!1}_offlineKey(e,t){return t?"panel-offline":`panel-offline:${e}`}_reconnectKey(e,t){return t?"panel-reconnected":`panel-reconnected:${e}`}_clearTransient(){null!==this._transientTimer&&(clearTimeout(this._transientTimer),this._transientTimer=null),this._transient=null}_notify(){for(const e of this._subscribers)try{e()}catch(e){console.warn("SPAN Panel: error-store subscriber threw",e)}}}const He={"&":"&","<":"<",">":">",'"':""","'":"'"};function Oe(e){return String(e).replace(/[&<>"']/g,e=>He[e]??e)}const Re="span_panel_list_columns";function qe(){try{const e=localStorage.getItem(Re);if(!e)return 1;const t=parseInt(e,10);return 1===t||2===t||3===t?t:1}catch{return 1}}function je(e){try{localStorage.setItem(Re,String(e))}catch{}}function Ue(e){return new Promise(t=>setTimeout(t,e))}class We{constructor(e){this._store=e}async callWS(e,t,n){const i=n?.retries??3,s=n?.errorId??`ws:${String(t.type??"unknown")}`;return this._withRetry(()=>e.callWS(t),i,s,n?.errorMessage)}async callService(e,t,n,i,s,o){const r=o?.retries??3,a=o?.errorId??`svc:${t}.${n}`;return this._withRetry(()=>e.callService(t,n,i,s),r,a,o?.errorMessage)}async _withRetry(e,t,i,s){if(this._store.hasAnyPanelOffline())try{const t=await e();return this._store.remove(i),t}catch(e){const t=e instanceof Error?e:new Error(String(e));throw this._store.add({key:i,level:"error",message:s??n("error.panel_offline"),persistent:!1}),t}let o;for(let n=0;n<=t;n++)try{const t=await e();return this._store.remove(i),t}catch(e){if(o=e instanceof Error?e:new Error(String(e)),n{try{const t={type:"call_service",domain:a,service:"get_favorites",service_data:{},return_response:!0},s=this._retry?await this._retry.callWS(e,t,{errorId:"fetch:favorites",errorMessage:n("error.favorites_fetch_failed")}):await e.callWS(t),o=s?.response?.favorites??{};return i===this._generation&&(this._map=o,this._lastFetch=Date.now()),o}catch(e){return console.warn("SPAN Panel: favorites fetch failed",e),this._retry||this._errorStore?.add({key:"fetch:favorites",level:"warning",message:n("error.favorites_fetch_failed"),persistent:!1}),this._map??{}}finally{this._inflight?.gen===i&&(this._inflight=null)}})();return this._inflight={gen:i,promise:s},s}invalidate(){this._lastFetch=0,this._generation++}clear(){this._map=null,this._lastFetch=0,this._generation++}get map(){return this._map??{}}}function Qe(e){for(const t of Object.values(e)){if((t.circuits?.length??0)>0)return!0;if((t.sub_devices?.length??0)>0)return!0}return!1}const Ke=Object.keys(f).filter(e=>"unknown"!==e&&"always_on"!==e);class Je extends HTMLElement{constructor(){super(),this.errorStore=null,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}disconnectedCallback(){this._clearDebounceTimers(),this._config=null}open(e){this._config=e,this._render(),this.offsetHeight,this.setAttribute("open",""),this.setAttribute("data-mode",this._modeFor(e))}close(){this._clearDebounceTimers(),this.removeAttribute("open"),this.removeAttribute("data-mode"),this._config=null,this.dispatchEvent(new CustomEvent("side-panel-closed",{bubbles:!0,composed:!0}))}_clearDebounceTimers(){for(const e of Object.keys(this._debounceTimers))clearTimeout(this._debounceTimers[e]);this._debounceTimers={}}_modeFor(e){return e.favoritesMode?"favorites":e.panelMode?"panel":e.subDeviceMode?"subDevice":"circuit"}_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 .unit-toggle {\n display: inline-flex;\n border: 1px solid var(--divider-color, #e0e0e0);\n border-radius: 6px;\n overflow: hidden;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n border-right: 1px solid var(--divider-color, #e0e0e0);\n background: var(--card-background-color, #fff);\n color: var(--primary-text-color, #212121);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n }\n .unit-btn:last-child {\n border-right: none;\n }\n .unit-btn:hover:not(.unit-active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #03a9f4);\n color: #fff;\n font-weight: 600;\n }\n\n .monitoring-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .fav-heart {\n background: none;\n border: 1px solid var(--divider-color, #e0e0e0);\n color: var(--secondary-text-color, #727272);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n font-size: 0.9em;\n margin-right: 6px;\n line-height: 1;\n display: inline-flex;\n align-items: center;\n }\n .fav-heart.active {\n color: var(--primary-color, #03a9f4);\n border-color: var(--primary-color, #03a9f4);\n }\n .fav-heart:hover:not(.active) {\n background: var(--secondary-background-color, #f5f5f5);\n }\n .fav-heart ha-icon {\n --mdc-icon-size: 16px;\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',t.appendChild(n);const i=document.createElement("div");i.className="backdrop",i.addEventListener("click",()=>this.close()),t.appendChild(i);const s=document.createElement("div");s.className="panel",t.appendChild(s),e.favoritesMode?this._renderFavoritesMode(s):e.panelMode?this._renderPanelMode(s):e.subDeviceMode?this._renderSubDeviceMode(s,e):this._renderCircuitMode(s,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 a=t.graphSettings,l=t.topology,c=a?.global_horizon??o,d=a?.circuits??{};s.appendChild(this._buildListColumnsSection());const h=document.createElement("div");h.className="section";const p=document.createElement("div");p.className="section-label",p.textContent=n("sidepanel.graph_horizon"),h.appendChild(p);const g=document.createElement("div");g.className="field-row";const _=document.createElement("span");_.className="field-label",_.textContent=n("sidepanel.global_default"),g.appendChild(_);const f=document.createElement("select");for(const e of Object.keys(r)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,s=n(i);t.textContent=s!==i?s:e,e===c&&(t.selected=!0),f.appendChild(t)}if(f.addEventListener("change",()=>{const e={horizon:f.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_graph_time_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})}),g.appendChild(f),h.appendChild(g),s.appendChild(h),l?.circuits){const e=document.createElement("div");e.className="section";const i=document.createElement("div");i.className="section-label",i.textContent=n("sidepanel.circuit_scales"),e.appendChild(i);const o=Object.entries(l.circuits).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[n,i]of o){const s=this._buildPanelModeCircuitRow(n,i,d[n],c,t.configEntryId??null,t.showFavorites??!1,t.favoritePanelDeviceId,t.favoriteCircuitUuids);e.appendChild(s)}s.appendChild(e)}const v=a?.sub_devices??{};if(l?.sub_devices){const e=document.createElement("div");e.className="section";const i=document.createElement("div");i.className="section-label",i.textContent=n("sidepanel.subdevice_scales"),e.appendChild(i);const o=Object.entries(l.sub_devices).sort(([,e],[,t])=>(e.name||"").localeCompare(t.name||""));for(const[i,s]of o){const o=document.createElement("div");o.className="field-row";const a=document.createElement("span");if(a.className="field-label",a.textContent=s.name||i,a.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",o.appendChild(a),t.showFavorites&&t.favoritePanelDeviceId){const e=this._buildSubDeviceFavoriteHeart(s.entities,t.favoriteSubDeviceIds?.has(i)??!1);e&&o.appendChild(e)}const l=v[i]||{horizon:c,has_override:!1},d=l.has_override?l.horizon:c,h=document.createElement("select");h.dataset.subdevId=i;for(const e of Object.keys(r)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,s=n(i);t.textContent=s!==i?s:e,e===d&&(t.selected=!0),h.appendChild(t)}if(h.addEventListener("change",()=>{this._debounce(`subdev-${i}`,u,()=>{const e={subdevice_id:i,horizon:h.value};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("set_subdevice_graph_horizon",e).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})})}),o.appendChild(h),l.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",()=>{const s={subdevice_id:i};t.configEntryId&&(s.config_entry_id=t.configEntryId),this._callDomainService("clear_subdevice_graph_horizon",s).then(()=>{h.value=c,e.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})}),o.appendChild(e)}e.appendChild(o)}s.appendChild(e)}e.appendChild(s)}_buildPanelModeCircuitRow(e,t,i,s,o,a,l,c){const d=document.createElement("div");d.className="field-row";const h=document.createElement("span");if(h.className="field-label",h.textContent=t.name||e,h.style.cssText="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;flex:1;",d.appendChild(h),a&&l){const n=this._buildFavoriteHeart(t.entities,c?.has(e)??!1);n&&d.appendChild(n)}const p=i||{horizon:s,has_override:!1},g=p.has_override?p.horizon:s,_=document.createElement("select");_.dataset.uuid=e;for(const e of Object.keys(r)){const t=document.createElement("option");t.value=e;const i=`horizon.${e}`,s=n(i);t.textContent=s!==i?s:e,e===g&&(t.selected=!0),_.appendChild(t)}if(_.addEventListener("change",()=>{this._debounce(`circuit-${e}`,u,()=>{const t={circuit_id:e,horizon:_.value};o&&(t.config_entry_id=o),this._callDomainService("set_circuit_graph_horizon",t).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})})}),d.appendChild(_),p.has_override){const t=document.createElement("button");t.textContent="↺",t.title=n("sidepanel.reset_to_global"),Object.assign(t.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"}),t.addEventListener("click",()=>{const i={circuit_id:e};o&&(i.config_entry_id=o),this._callDomainService("clear_circuit_graph_horizon",i).then(()=>{_.value=s,t.remove(),this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})}),d.appendChild(t)}return d}_renderFavoritesMode(e){const t=this._config,i=this._createHeader(n("sidepanel.graph_settings"),n("sidepanel.favorites_subtitle"));e.appendChild(i);const s=document.createElement("div");s.className="panel-body",s.appendChild(this._buildListColumnsSection());for(const e of t.perPanelSections)s.appendChild(this._buildFavoritesPanelSection(e));e.appendChild(s)}_buildFavoritesPanelSection(e){const t=document.createElement("div");t.className="section";const n=document.createElement("div");n.className="section-label",n.textContent=e.panelName,t.appendChild(n);const i=e.graphSettings?.global_horizon??o,s=e.graphSettings?.circuits??{},r=function(e){const t=e.circuits??{};return Object.entries(t).map(([e,t])=>({uuid:e,circuit:t})).sort((e,t)=>(e.circuit.name||"").localeCompare(t.circuit.name||""))}(e.topology);for(const{uuid:n,circuit:o}of r){const r=this._buildPanelModeCircuitRow(n,o,s[n],i,e.configEntryId,!0,e.panelDeviceId,e.favoriteCircuitUuids);t.appendChild(r)}return t}_renderCircuitMode(e,t){const n=`${Oe(String(t.breaker_rating_a))}A · ${Oe(String(t.voltage))}V · Tabs [${Oe(String(t.tabs))}]`,i=this._createHeader(Oe(t.name),n);e.appendChild(i);const s=document.createElement("div");s.className="panel-body",e.appendChild(s),this._renderRelaySection(s,t),t.showFavorites&&this._renderFavoriteSection(s,t),this._renderSheddingSection(s,t),this._renderGraphHorizonSection(s,t),t.showMonitoring&&this._renderMonitoringSection(s,t)}_favoriteEntityId(e){return e?.current??e?.power??null}_subDeviceFavoriteEntityId(e){if(!e)return null;let t=null;for(const[n,i]of Object.entries(e)){if("sensor"===i.domain)return n;t||(t=n)}return t}_buildSubDeviceFavoriteHeart(e,t){const n=this._subDeviceFavoriteEntityId(e);return n?this._buildHeartButton(n,t):null}_buildListColumnsSection(){const e=document.createElement("div");e.className="section";const t=document.createElement("div");t.className="section-label",t.textContent=n("sidepanel.list_view_columns"),e.appendChild(t);const i=document.createElement("div");i.className="field-row";const s=document.createElement("span");s.className="field-label",s.textContent=n("sidepanel.columns"),i.appendChild(s);const o=qe(),r=document.createElement("div");r.className="unit-toggle";for(const e of[1,2,3]){const t=document.createElement("button");t.type="button",t.className="unit-btn"+(e===o?" unit-active":""),t.dataset.columns=String(e),t.textContent=String(e),t.addEventListener("click",()=>{je(e);for(const e of r.querySelectorAll(".unit-btn"))e.classList.toggle("unit-active",e===t);this.dispatchEvent(new CustomEvent("list-columns-changed",{detail:e,bubbles:!0,composed:!0}))}),r.appendChild(t)}return i.appendChild(r),e.appendChild(i),e}_buildFavoriteHeart(e,t){const n=this._favoriteEntityId(e);return n?this._buildHeartButton(n,t):(console.warn("SPAN Panel: circuit has no current/power sensor; favorite heart suppressed"),null)}_buildHeartButton(e,t){const i=document.createElement("button");i.type="button",i.className=t?"fav-heart active":"fav-heart",i.dataset.role="fav-heart",i.title=n("sidepanel.save_to_favorites"),i.setAttribute("role","switch"),i.setAttribute("aria-checked",String(t)),i.setAttribute("aria-label",n("sidepanel.save_to_favorites"));const s=document.createElement("ha-icon");return s.setAttribute("icon",t?"mdi:heart":"mdi:heart-outline"),i.appendChild(s),i.addEventListener("click",t=>{t.stopPropagation(),this._toggleFavoriteEntity(i,s,e).catch(()=>{})}),i}async _toggleFavoriteEntity(e,t,i){if(!this._hass)return;const s=e.classList.contains("active"),o=!s;e.classList.toggle("active",o),t.setAttribute("icon",o?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(o));try{o?await async function(e,t){const n=await Ve(e,"add_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Ge)),n?.favorites??{}}(this._hass,i):await async function(e,t){const n=await Ve(e,"remove_favorite",{entity_id:t});return document.dispatchEvent(new CustomEvent(Ge)),n?.favorites??{}}(this._hass,i)}catch(i){throw e.classList.toggle("active",s),t.setAttribute("icon",s?"mdi:heart":"mdi:heart-outline"),e.setAttribute("aria-checked",String(s)),console.warn("SPAN Panel: favorite toggle failed",i),this.errorStore?.add({key:"service:favorites",level:"error",message:n("error.favorites_toggle_failed"),persistent:!1}),i}}_renderFavoriteSection(e,t){const n=this._favoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_appendFavoriteHeartSection(e,t,i){const s=document.createElement("div");s.className="section",s.innerHTML=``;const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=n("sidepanel.save_to_favorites"),o.appendChild(r),o.appendChild(this._buildHeartButton(t,i)),s.appendChild(o),e.appendChild(s)}_renderSubDeviceMode(e,t){const n=this._createHeader(Oe(t.name),Oe(t.deviceType));e.appendChild(n);const i=document.createElement("div");i.className="panel-body",e.appendChild(i),t.showFavorites&&this._renderSubDeviceFavoriteSection(i,t),this._renderSubDeviceHorizonSection(i,t)}_renderSubDeviceFavoriteSection(e,t){const n=this._subDeviceFavoriteEntityId(t.entities);n&&this._appendFavoriteHeartSection(e,n,!0===t.isFavorite)}_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 a=t.graphHorizonInfo,l=!0===a?.has_override,c=a?.horizon||o,d=a?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))p.push({key:e,label:e});const u=l?c:"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 s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const i={subdevice_id:t.subDeviceId};t.configEntryId&&(i.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_subdevice_graph_horizon",i).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_subdevice_graph_horizon",{...i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}i.appendChild(h),e.appendChild(i)}_createHeader(e,t){const n=document.createElement("div");n.className="panel-header";const i=document.createElement("div"),s=Oe(e),o=Oe(t);i.innerHTML=`
${s}
`+(o?`
${o}
`:"");const r=document.createElement("button");return r.className="close-btn",r.innerHTML="✕",r.addEventListener("click",()=>this.close()),n.appendChild(i),n.appendChild(r),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 s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=n("sidepanel.breaker");const r=document.createElement("ha-switch");r.dataset.role="relay-toggle";const a=t.entities.switch,l=this._hass?.states?.[a]?.state;"on"===l&&r.setAttribute("checked",""),r.addEventListener("change",()=>{const e=r.hasAttribute("checked")||r.checked;this._callService("switch",e?"turn_on":"turn_off",{entity_id:a}).catch(e=>{console.warn("SPAN Panel: relay toggle failed",e),this.errorStore?.add({key:"service:relay",level:"error",message:n("error.relay_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),i.appendChild(s),e.appendChild(i)}_renderSheddingSection(e,t){if(!t.entities?.select)return;const i=document.createElement("div");i.className="section",i.innerHTML=``;const s=document.createElement("div");s.className="field-row";const o=document.createElement("span");o.className="field-label",o.textContent=n("sidepanel.priority_label");const r=document.createElement("select");r.dataset.role="shedding-select";const a=t.entities.select,l=this._hass?.states?.[a]?.state||"";for(const e of Ke){const t=f[e];if(!t)continue;const i=document.createElement("option");i.value=e,i.textContent=n(`shedding.select.${e}`)||t.label(),e===l&&(i.selected=!0),r.appendChild(i)}r.addEventListener("change",()=>{this._callService("select","select_option",{entity_id:a,option:r.value}).catch(e=>{console.warn("SPAN Panel: shedding update failed",e),this.errorStore?.add({key:"service:shedding",level:"error",message:n("error.shedding_failed"),persistent:!1})})}),s.appendChild(o),s.appendChild(r),i.appendChild(s),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 a=t.graphHorizonInfo,l=!0===a?.has_override,c=a?.horizon||o,d=a?.globalHorizon||o,h=document.createElement("div");h.className="horizon-bar";const p=[{key:"global",label:n("sidepanel.global")}];for(const e of Object.keys(r))p.push({key:e,label:e});const u=l?c:"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 s=document.createElement("button");s.type="button",s.className="horizon-segment",s.dataset.horizon=e,s.textContent=i,s.classList.toggle("active",e===u),s.classList.toggle("referenced","global"===u&&e===d),s.addEventListener("click",()=>{if(s.classList.contains("active"))return;const i={circuit_id:t.uuid};t.configEntryId&&(i.config_entry_id=t.configEntryId),"global"===e?(g("global"),this._callDomainService("clear_circuit_graph_horizon",i).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})})):(g(e),this._callDomainService("set_circuit_graph_horizon",{...i,horizon:e}).then(()=>{this.dispatchEvent(new CustomEvent("graph-settings-changed",{bubbles:!0,composed:!0}))}).catch(e=>{console.warn("SPAN Panel: graph horizon service failed",e),this.errorStore?.add({key:"service:graph_horizon",level:"error",message:n("error.graph_horizon_failed"),persistent:!1})}))}),h.appendChild(s)}i.appendChild(h),e.appendChild(i)}_renderMonitoringSection(e,t){const i=document.createElement("div");i.className="section";const s=document.createElement("div");s.className="monitoring-header";const o=document.createElement("div");o.className="section-label",o.textContent=n("sidepanel.monitoring"),o.style.margin="0";const r=document.createElement("ha-switch");r.dataset.role="monitoring-toggle";const a=t.monitoringInfo,l=null!=a&&!1!==a.monitoring_enabled;l&&r.setAttribute("checked",""),s.appendChild(o),s.appendChild(r),i.appendChild(s);const c=document.createElement("div");c.dataset.role="monitoring-details",c.style.display=l?"block":"none",i.appendChild(c);const d=!0===a?.has_override,h=document.createElement("div");h.className="radio-group",h.innerHTML=`\n \n \n `,c.appendChild(h);const p=document.createElement("div");p.dataset.role="threshold-fields",p.style.display=d?"block":"none";const u=a?.continuous_threshold_pct??80,g=a?.spike_threshold_pct??100,_=a?.window_duration_m??15,f=a?.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",f,1,180,"m",t)),c.appendChild(p),r.addEventListener("change",()=>{const e=r.checked;c.style.display=e?"block":"none";const i={circuit_id:t.entities?.power||t.uuid,monitoring_enabled:e};t.configEntryId&&(i.config_entry_id=t.configEntryId),this._callDomainService("set_circuit_threshold",i).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})});const v=h.querySelectorAll('input[type="radio"]');for(const e of v)e.addEventListener("change",()=>{const i="custom"===e.value&&e.checked;if(p.style.display=i?"block":"none",!i&&e.checked){const e={circuit_id:t.entities?.power||t.uuid};t.configEntryId&&(e.config_entry_id=t.configEntryId),this._callDomainService("clear_circuit_threshold",e).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})}});e.appendChild(i)}_createThresholdRow(e,t,i,s){const o=document.createElement("div");o.className="field-row";const r=document.createElement("span");r.className="field-label",r.textContent=e;const a=document.createElement("input");return a.type="number",a.min="0",a.max="200",a.value=String(i),a.dataset.role=`threshold-${t}`,a.addEventListener("input",()=>{this._debounce(`threshold-${t}`,u,()=>{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"]'),r=e.querySelector('[data-role="threshold-cooldown-m"]'),a={circuit_id:s.entities?.power||s.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,cooldown_duration_m:r?Number(r.value):void 0};s.configEntryId&&(a.config_entry_id=s.configEntryId),this._callDomainService("set_circuit_threshold",a).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})})}),o.appendChild(r),o.appendChild(a),o}_createDurationRow(e,t,i,s,o,r,a,l=!1){const c=document.createElement("div");c.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(s),p.max=String(o),p.value=String(i),p.dataset.role=`threshold-${t}`,l&&(p.disabled=!0);const g=document.createElement("span");return g.textContent=r,h.appendChild(p),h.appendChild(g),l||p.addEventListener("input",()=>{this._debounce(`threshold-${t}`,u,()=>{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"]'),o={circuit_id:a.uuid,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};a.configEntryId&&(o.config_entry_id=a.configEntryId),this._callDomainService("set_circuit_threshold",o).catch(e=>{console.warn("SPAN Panel: monitoring update failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})})}),c.appendChild(d),c.appendChild(h),c}_updateLiveState(){if(!this._config||this._config.panelMode)return;const e=this._config;if(!e.subDeviceMode&&!e.favoritesMode){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}):Promise.resolve()}_debounce(e,t,n){this._debounceTimers[e]&&clearTimeout(this._debounceTimers[e]),this._debounceTimers[e]=setTimeout(()=>{delete this._debounceTimers[e],n()},t)}}try{customElements.get("span-side-panel")||customElements.define("span-side-panel",Je)}catch{}let Xe=class extends Pe{constructor(){super(...arguments),this._store=null,this._unsub=null,this._errors=[]}set store(e){if(this._store===e)return;this._unsub?.(),this._unsub=null,this._store=e,this._errors=e.active;const t=e;this._unsub=e.subscribe(()=>{this._errors=t.active})}connectedCallback(){if(super.connectedCallback(),this._store&&!this._unsub){const e=this._store;this._errors=e.active,this._unsub=e.subscribe(()=>{this._errors=e.active})}}disconnectedCallback(){super.disconnectedCallback(),this._unsub?.(),this._unsub=null}render(){return 0===this._errors.length?de:le`${this._errors.map(e=>le` + + `)}`}_iconForLevel(e){switch(e){case"error":return"mdi:alert-circle";case"warning":return"mdi:alert";default:return"mdi:information"}}};async function Ze(e,t){const[n,i,s]=await Promise.all([e.callWS({type:"config/area_registry/list"}),e.callWS({type:"config/entity_registry/list"}),e.callWS({type:"config/device_registry/list"})]),o=new Map;for(const e of n)o.set(e.area_id,e.name);const r=new Map;for(const e of i)e.area_id&&r.set(e.entity_id,e.area_id);const a=new Map;for(const e of s)a.set(e.id,e.area_id);let l;if(t.device_id){const e=a.get(t.device_id);e&&(l=o.get(e))}for(const e of Object.values(t.circuits)){let t;for(const n of Object.values(e.entities)){if(!n)continue;const e=r.get(n);if(e){t=o.get(e);break}}t||(t=l),e.area=t}}async function Ye(e,t,i){if(!t)throw new Error(n("card.device_not_found"));const s={type:`${a}/panel_topology`,device_id:t},o=i?await i.callWS(e,s,{errorId:"fetch:topology"}):await e.callWS(s),r=o.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}(o.circuits);if(!r)throw new Error(n("card.topology_error"));const l={type:"config/device_registry/list"},c=i?await i.callWS(e,l,{errorId:"fetch:topology"}):await e.callWS(l),d=(h=c.find(e=>e.id===t),h?{id:h.id,name:h.name,name_by_user:h.name_by_user,config_entries:h.config_entries,identifiers:h.identifiers,via_device_id:h.via_device_id,sw_version:h.sw_version,model:h.model}:null);var h;return await Ze(e,o),{topology:o,panelDevice:d,panelSize:r}}function et(){return`
\n ${Object.entries(f).filter(([e])=>"unknown"!==e).map(([,e])=>{const t=Oe(e.icon),n=Oe(e.color),i=Oe(e.label());let s;if(e.icon2){s=``}else if(e.textLabel){s=`${Oe(e.textLabel)}`}else s=``;return`
${s}${i}
`}).join("")}\n
`}function tt(e,t,i){const s="current"===(t.chart_metric||"power"),o=!!e.panel_entities?.site_power,r=!!e.panel_entities?.dsm_state,a=!!e.panel_entities?.current_power,l=!!e.panel_entities?.feedthrough_power,c=!!e.panel_entities?.pv_power,d=!!e.panel_entities?.battery_level;return`\n
\n ${o?`\n
\n ${n("header.site")}\n
\n 0\n ${s?"A":"kW"}\n
\n
`:""}\n ${r?`\n
\n ${n("header.grid")}\n
\n --\n
\n
`:""}\n ${a?`\n
\n ${n("header.upstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${l?`\n
\n ${n("header.downstream")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${c?`\n
\n ${n("header.solar")}\n
\n --\n ${s?"A":"kW"}\n
\n
`:""}\n ${d?`\n
\n ${n("header.battery")}\n
\n \n %\n
\n
`:""}\n
\n `}function nt(e,t,i={}){const s=Oe(e.device_name||n("header.default_name")),o=Oe(e.serial||""),r=Oe(e.firmware||""),a="current"===(t.chart_metric||"power"),l=!1!==i.showSwitches;return`\n
\n
\n
\n

${s}

\n ${o}\n \n ${l?`
\n ${Oe(n("header.enable_switches"))}\n
\n \n
\n
`:""}\n
\n ${tt(e,t)}\n
\n
\n
\n ${r}\n
\n \n \n
\n
\n ${et()}\n
\n
\n `}Xe.styles=C` + :host { + display: block; + } + .banner-row { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + font-size: 13px; + line-height: 1.4; + } + .banner-row + .banner-row { + border-top: 1px solid rgba(128, 128, 128, 0.2); + } + .banner-row.level-error { + background: color-mix(in srgb, var(--error-color, #db4437) 15%, transparent); + color: var(--error-color, #db4437); + } + .banner-row.level-warning { + background: color-mix(in srgb, var(--warning-color, #ff9800) 15%, transparent); + color: var(--warning-color, #ff9800); + } + .banner-row.level-info { + background: color-mix(in srgb, var(--info-color, #4285f4) 15%, transparent); + color: var(--info-color, #4285f4); + } + .icon { + flex-shrink: 0; + width: 18px; + height: 18px; + --mdc-icon-size: 18px; + } + .message { + flex: 1; + min-width: 0; + } + .retry-btn { + flex-shrink: 0; + background: none; + border: 1px solid currentColor; + border-radius: 4px; + color: inherit; + cursor: pointer; + font-size: 12px; + padding: 2px 8px; + } + .retry-btn:hover { + opacity: 0.8; + } + `,m([Me()],Xe.prototype,"_errors",void 0),Xe=m([Ee("span-error-banner")],Xe);const it=g.power;function st(e){return it.unit(e)}function ot(e){return(e<0?"-":"")+it.format(e)}function rt(e){return(Math.abs(e)/1e3).toFixed(1)}function at(e){return Math.ceil(e/2)}function lt(e){return e%2==0?1:0}function ct(e){if(2!==e.length)return null;const[t,n]=[Math.min(...e),Math.max(...e)];return at(t)===at(n)?"row-span":lt(t)===lt(n)?"col-span":"row-span"}function dt(e){const t=e.chart_metric??s;return g[t]??g[s]}function ht(e,t){const n=function(e){return dt(e).entityRole}(t);return e.entities?.[n]??e.entities?.power??null}class pt{constructor(){this._status=null,this._lastFetch=0,this._inflight=null,this._generation=0,this._errorStore=null,this._retry=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}async fetch(e,t){const i=Date.now();if(this._inflight&&this._inflight.gen===this._generation)return this._inflight.promise;if(this._status&&i-this._lastFetch<3e4)return this._status;const s=this._generation,o=(async()=>{try{const i={};t&&(i.config_entry_id=t);const o={type:"call_service",domain:a,service:"get_monitoring_status",service_data:i,return_response:!0},r=this._retry?await this._retry.callWS(e,o,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await e.callWS(o),l=r?.response??null;return s===this._generation&&(this._status=l,this._lastFetch=Date.now()),l}catch(e){return console.warn("SPAN Panel: monitoring status fetch failed",e),s===this._generation&&(this._status=null),this._retry||this._errorStore?.add({key:"fetch:monitoring",level:"warning",message:n("error.monitoring_failed"),persistent:!1}),null}finally{this._inflight?.gen===s&&(this._inflight=null)}})();return this._inflight={gen:s,promise:o},o}invalidate(){this._lastFetch=0,this._generation++}get status(){return this._status}clear(){this._status=null,this._lastFetch=0,this._generation++}}class ut{constructor(){this._caches=new Map,this._errorStore=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e;for(const t of this._caches.values())t.errorStore=e}async fetchOne(e,t){let n=this._caches.get(t);return n||(n=new pt,n.errorStore=this._errorStore,this._caches.set(t,n)),n.fetch(e,t)}invalidate(){for(const e of this._caches.values())e.invalidate()}clear(){this._caches.clear()}}function gt(e,t){return e?.circuits?e.circuits[t]??null:null}function _t(e){return!!e&&void 0!==e.continuous_threshold_pct}function ft(e,t,n,i){const s=[];return n||s.push("circuit-off"),i&&s.push("circuit-producer"),function(e){return!!e&&null!=e.over_threshold_since}(t)&&s.push("circuit-alert"),_t(t)&&s.push("circuit-custom-monitoring"),s.join(" ")}function vt(e,t,i,s,o,r,a,d,h,p=!1){const u=t.entities?.power,g=u?r.states[u]:null,_=g&&parseFloat(g.state)||0,m=t.device_type===c||_<0,b=t.entities?.switch,y=b?r.states[b]:null,w=y?"on"===y.state:(g?.attributes?.relay_state||t.relay_state)===l,x=t.breaker_rating_a,S=x?`${Math.round(x)}A`:"",$=Oe(t.name||n("grid.unknown")),C=dt(a);let P;if("current"===C.entityRole){const e=t.entities?.current,n=e?r.states[e]:null,i=n&&parseFloat(n.state)||0;P=`${C.format(i)}A`}else P=`${ot(_)}${st(_)}`;const k=h||"unknown";let E="";if("unknown"!==k){const e=f[k]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"},t=Oe(e.label()),n=Oe(e.icon),i=Oe(e.color);if(e.icon2){E=`\n \n \n `}else if(e.textLabel){E=`\n \n ${Oe(e.textLabel)}\n `}else E=``}const z=d&&_t(d)?v:"#555",A=``;let N="",M=d?.utilization_pct??null;if(null==M&&t.breaker_rating_a){const e=t.entities?.current,n=e?r.states[e]:null,i=n?Math.abs(parseFloat(n.state)||0):0;M=Math.round(i/t.breaker_rating_a*1e3)/10}if(null!=M){N=`=80?"utilization-warning":"utilization-normal"}">${Math.round(M)}%`}return`\n
\n
\n
\n ${S?`${S}`:""}\n ${N}\n ${$}\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 ${E}\n ${A}\n
\n
\n
\n `}function mt(e,t){return`\n
\n \n
\n `}const bt={names:["power","battery power"],suffixes:["_power"]},yt={names:["battery level","battery percentage"],suffixes:["_battery_level","_battery_percentage"]},wt={names:["state of energy"],suffixes:["_soe_kwh"]},xt={names:["nameplate capacity"],suffixes:["_nameplate_capacity"]};function St(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 $t(e){return St(e,bt)}function Ct(e){return St(e,yt)}function Pt(e){return St(e,wt)}function kt(e){return St(e,xt)}function Et(e,t,i){const s=!1!==i.show_battery,o=!1!==i.show_evse;if(!e.sub_devices)return"";const r=Object.entries(e.sub_devices).filter(([,e])=>!(e.type===d&&!s)&&!(e.type===h&&!o));if(0===r.length)return"";const a=r.filter(([,e])=>e.type===h).length;let l=0,c="";for(const[e,s]of r){const o=s.type===h?n("subdevice.ev_charger"):s.type===d?n("subdevice.battery"):n("subdevice.fallback"),r=$t(s),p=r?t.states[r]:void 0,u=p&&parseFloat(p.state)||0,g=s.type===d,_=s.type===h,f=g?Ct(s):null,v=g?Pt(s):null,m=g?kt(s):null,b=zt(s,t,i,new Set([r,f,v,m].filter(e=>null!==e))),y=At(e,s,g,r,f,v);let w="";g?w="sub-device-bess":_&&(l++,l===a&&a%2==1&&(w="sub-device-full")),c+=`\n
\n
\n ${Oe(o)}\n ${Oe(s.name||"")}\n ${r?`${ot(u)} ${st(u)}`:""}\n \n
\n ${y}\n ${b}\n
\n `}return c}function zt(e,t,n,i){const s=n.visible_sub_entities||{};let o="";if(!e.entities)return o;for(const[n,r]of Object.entries(e.entities)){if(i.has(n))continue;if(!0!==s[n])continue;const a=t.states[n];if(!a)continue;let l=r.original_name||a.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(a);else{d=a.state;const e=a.attributes.unit_of_measurement||"";e&&(d+=" "+e)}if("Wh"===(a.attributes.unit_of_measurement||"")){const e=parseFloat(a.state);isNaN(e)||(d=(e/1e3).toFixed(1)+" kWh")}o+=`\n
\n ${Oe(l)}:\n ${Oe(d)}\n
\n `}return o}function At(e,t,i,s,o,r){if(i){const t=[{key:`${p}${e}_soc`,title:n("subdevice.soc"),available:!!o},{key:`${p}${e}_soe`,title:n("subdevice.soe"),available:!!r},{key:`${p}${e}_power`,title:n("subdevice.power"),available:!!s}].filter(e=>e.available);return`\n
\n ${t.map(e=>`\n
\n
${Oe(e.title)}
\n
\n
\n `).join("")}\n
\n `}return s?`
`:""}function Nt(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 Mt(e){const t=r[e];return t?t.ms:r[o].ms}function It(e){const t=e/1e3;return t<=600?Math.ceil(t):Math.min(5e3,Math.ceil(t/5))}function Tt(e){return Math.max(500,Math.floor(e/5e3))}function Dt(e,t,n,i,s,o){e.has(t)||e.set(t,[]);const r=e.get(t);r.push({time:i,value:n});const a=r.findIndex(e=>e.time>=s);a>0?r.splice(0,a):-1===a&&(r.length=0),r.length>o&&r.splice(0,r.length-o)}function Ft(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 Lt(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),r=i/36e5>72?"hour":"5minute",a=await e.callWS({type:"recorder/statistics_during_period",start_time:o,statistic_ids:t,period:r,types:["mean"]});for(const[e,t]of Object.entries(a)){const i=n.get(e);if(!i||!t)continue;const o=[];for(const e of t){const t=e.mean;if(null==t||!Number.isFinite(t))continue;const n=e.start;n>0&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];t.sort((e,t)=>e.time-t.time),s.set(i,t)}}}async function Ht(e,t,n,i,s){const o=new Date(Date.now()-i).toISOString(),r=await e.callWS({type:"history/history_during_period",start_time:o,entity_ids:t,minimal_response:!0,significant_changes_only:!0,no_attributes:!0}),a=It(i),l=Tt(i);for(const[e,t]of Object.entries(r)){const i=n.get(e);if(!i||!t)continue;const o=[];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&&o.push({time:n,value:t})}if(o.length>0){const e=s.get(i)||[],t=[...o,...e];s.set(i,Ft(t,a,l))}}}function Ot(e){if(!e.sub_devices)return[];const t=[];for(const[n,i]of Object.entries(e.sub_devices)){const e={power:$t(i)};i.type===d&&(e.soc=Ct(i),e.soe=Pt(i));for(const[i,s]of Object.entries(e))s&&t.push({entityId:s,key:`${p}${n}_${i}`,devId:n})}return t}async function Rt(e,t,n,i,s,o){if(!t||!e)return;const r=new Map;for(const[e,i]of Object.entries(t.circuits)){const t=ht(i,n);if(!t)continue;let o;o=s&&s.has(e)?Mt(s.get(e)):Nt(n),r.has(o)||r.set(o,{entityIds:[],uuidByEntity:new Map});const a=r.get(o);a.entityIds.push(t),a.uuidByEntity.set(t,e)}for(const{entityId:e,key:i,devId:s}of Ot(t)){let t;t=o&&o.has(s)?Mt(o.get(s)):Nt(n),r.has(t)||r.set(t,{entityIds:[],uuidByEntity:new Map});const a=r.get(t);a.entityIds.push(e),a.uuidByEntity.set(e,i)}const a=[];for(const[t,n]of r){if(0===n.entityIds.length)continue;t>2592e5?a.push(Lt(e,n.entityIds,n.uuidByEntity,t,i)):a.push(Ht(e,n.entityIds,n.uuidByEntity,t,i))}await Promise.all(a)}function qt(e,t,n,i,o,r,a,l,c){const{options:d,series:h}=function(e,t,n,i,o,r=!1){n||(n=g[s]);const a=i?"140, 160, 220":"77, 217, 175",l=`rgb(${a})`,c=Date.now(),d=c-t,h=void 0!==n.fixedMin&&void 0!==n.fixedMax,p=(e??[]).filter(e=>e.time>=d).map(e=>[e.time,Math.abs(e.value)]),u=[{type:"line",data:p,showSymbol:!1,smooth:!1,...r?{}:{step:"end"},lineStyle:{width:1.5,color:l},areaStyle:{color:{type:"linear",x:0,y:0,x2:0,y2:1,colorStops:[{offset:0,color:`rgba(${a}, 0.18)`},{offset:1,color:`rgba(${a}, 0.18)`}]}},itemStyle:{color:l}}],_=p.length>0?function(e){let t=0;for(const n of e)n[1]>t&&(t=n[1]);return t}(p):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}}};h?(f.min=n.fixedMin,f.max=n.fixedMax):_<1&&(f.min=0,f.max=1),o&&"current"===n.entityRole&&(f.min=0,f.max=Math.ceil(1.25*o),u.push({type:"line",data:[[d,.8*o],[c,.8*o]],showSymbol:!1,lineStyle:{width:1,color:"rgba(255, 200, 40, 0.6)",type:"dashed"},itemStyle:{color:"transparent"},tooltip:{show:!1}}),u.push({type:"line",data:[[d,o],[c,o]],showSymbol:!1,lineStyle:{width:1.5,color:"rgba(255, 60, 60, 0.7)",type:"solid"},itemStyle:{color:"transparent"},tooltip:{show:!1}}));const v={xAxis:{type:"time",min:d,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||0===e.length)return"";const t=e[0],i=new Date(t.value[0]).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),s=parseFloat(t.value[1].toFixed(2));return`
${i}
${n.format(s)} ${n.unit(s)}
`}},animation:!1};return{options:v,series:u}}(n,i,o,r,l,c),p=a??120;e.style.minHeight=p+"px";let u=e.querySelector("ha-chart-base");u||(u=document.createElement("ha-chart-base"),u.style.display="block",u.style.width="100%",u.hass=t,e.innerHTML="",e.appendChild(u));const _=e.clientHeight;u.height=(_>0?_:p)+"px",u.hass=t,u.options=d,u.data=h}function jt(e){return"function"==typeof globalThis.CSS?.escape?CSS.escape(e):e.replace(/["\\]/g,"\\$&")}function Ut(e,t,n,i,s){const o="current"===(i.chart_metric||"power"),r=e.querySelector(".stat-consumption .stat-value"),a=e.querySelector(".stat-consumption .stat-unit");if(o){const e=n.panel_entities?.site_power,i=e?t.states[e]:null,s=i?parseFloat(i.attributes?.amperage):NaN;r&&(r.textContent=Number.isFinite(s)?Math.abs(s).toFixed(1):"--"),a&&(a.textContent="A")}else{let e=s;const i=n.panel_entities?.site_power;if(i){const n=t.states[i];n&&(e=Math.abs(parseFloat(n.state)||0))}r&&(r.textContent=rt(e)),a&&(a.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(o){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=rt(e),c&&(c.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(o){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=rt(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(o){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=rt(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:"--"}}function Wt(e,t,i,s,o,r){if(!e||!i||!t)return;const a=Nt(s);let d=0;for(const[,e]of Object.entries(i.circuits)){const n=e.entities?.power;if(!n)continue;const i=t.states[n],s=i&&parseFloat(i.state)||0;e.device_type!==c&&(d+=Math.abs(s))}!function(e,t,n,i,s){const o=e.querySelector(".panel-stats");o&&Ut(o,t,n,i,s)}(e,t,i,s,d);const h=dt(s),p="current"===h.entityRole;for(const[s,d]of Object.entries(i.circuits)){const i=e.querySelector(`.circuit-slot[data-uuid="${jt(s)}"]`);if(!i)continue;const u=d.entities?.power,g=u?t.states[u]:null,_=g&&parseFloat(g.state)||0,v=d.device_type===c||_<0,m=d.entities?.switch,b=m?t.states[m]:null,y=b?"on"===b.state:(g?.attributes?.relay_state||d.relay_state)===l,w=i.querySelector(".power-value");if(w)if(p){const e=d.entities?.current,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;w.innerHTML=`${h.format(i)}A`}else w.innerHTML=`${ot(_)}${st(_)}`;const x=i.querySelector(".toggle-pill");if(x){x.className="toggle-pill "+(y?"toggle-on":"toggle-off");const e=x.querySelector(".toggle-label");e&&(e.textContent=n(y?"grid.on":"grid.off"))}let S;if(i.classList.toggle("circuit-off",!y),i.classList.toggle("circuit-producer",v),d.always_on)S="always_on";else{const e=d.entities?.select,n=e?t.states[e]:null;S=n?n.state:"unknown"}const $=f[S]??f.unknown,C=i.querySelector(".shedding-icon");C&&(C.setAttribute("icon",$.icon),C.style.color=$.color,C.title=$.label());const P=i.querySelector(".shedding-icon-secondary");P&&($.icon2?(P.setAttribute("icon",$.icon2),P.style.color=$.color,P.style.display=""):P.style.display="none");const k=i.querySelector(".shedding-label");k&&($.textLabel?(k.textContent=$.textLabel,k.style.color=$.color,k.style.display=""):k.style.display="none");const E=i.querySelector(".chart-container");if(E){const e=o.get(s)||[],n=i.classList.contains("circuit-col-span")?200:100,l=r?.has(s)?Mt(r.get(s)):a,p=d.device_type===c;qt(E,t,e,l,h,v,n,d.breaker_rating_a??void 0,p)}}}class Gt{get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this._retry=e?new We(e):null}constructor(){this._errorStore=null,this._retry=null,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 i={};t&&(i.config_entry_id=t);const s={type:"call_service",domain:a,service:"get_graph_settings",service_data:i,return_response:!0},o=this._retry?await this._retry.callWS(e,s,{errorId:"fetch:graph_settings",errorMessage:n("error.graph_settings_failed")}):await e.callWS(s);this._settings=o?.response??null,this._lastFetch=Date.now()}catch(e){console.warn("SPAN Panel: graph settings fetch failed",e),this._settings=null,this._retry||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:n("error.graph_settings_failed"),persistent:!1})}finally{this._fetching=!1}return this._settings}invalidate(){this._lastFetch=0}get settings(){return this._settings}clear(){this._settings=null,this._lastFetch=0}}function Vt(e,t){if(!e)return o;const n=e.circuits?.[t];return n?.has_override?n.horizon:e.global_horizon??o}function Bt(e,t){if(!e)return o;const n=e.sub_devices?.[t];return n?.has_override?n.horizon:e.global_horizon??o}class Qt{constructor(){this.powerHistory=new Map,this.horizonMap=new Map,this.subDeviceHorizonMap=new Map,this.monitoringCache=new pt,this.monitoringMultiCache=new ut,this.graphSettingsCache=new Gt,this._errorStore=null,this._hass=null,this._topology=null,this._config=null,this._configEntryId=null,this._favRefs=null,this._perPanelInfo=new Map,this._panelFavorites=null,this._showMonitoring=!1,this._updateInterval=null,this._recorderRefreshInterval=null,this._resizeObserver=null,this._lastWidth=0,this._resizeDebounce=null}get errorStore(){return this._errorStore}set errorStore(e){this._errorStore=e,this.monitoringCache.errorStore=e,this.graphSettingsCache.errorStore=e,this.monitoringMultiCache.errorStore=e}get hass(){return this._hass}set hass(e){this._hass=e}get topology(){return this._topology}get config(){return this._config}set showMonitoring(e){this._showMonitoring=e}init(e,t,n,i){this._topology=e,this._config=t,this._hass=n,this._configEntryId=i}setFavoriteRefs(e){this._favRefs=e}clearFavoriteRefs(){this._favRefs=null}setPanelFavorites(e){this._panelFavorites=e}setFavoritesPerPanelInfo(e){this._perPanelInfo=e??new Map}get _inFavoritesView(){return null!==this._favRefs}setConfig(e){this._config=e}buildHorizonMaps(e){if(this.horizonMap.clear(),this.subDeviceHorizonMap.clear(),e&&this._topology?.circuits)for(const t of Object.keys(this._topology.circuits))this.horizonMap.set(t,Vt(e,t));if(e&&this._topology?.sub_devices)for(const t of Object.keys(this._topology.sub_devices))this.subDeviceHorizonMap.set(t,Bt(e,t))}async fetchAndBuildHorizonMaps(){try{this._favRefs?await this._buildFavoritesHorizonMaps():(await this.graphSettingsCache.fetch(this._hass,this._configEntryId),this.buildHorizonMaps(this.graphSettingsCache.settings))}catch(e){console.warn("SPAN Panel: graph settings fetch failed",e),this.graphSettingsCache.errorStore||this._errorStore?.add({key:"fetch:graph_settings",level:"warning",message:n("error.graph_settings_failed"),persistent:!1})}}async fetchMergedMonitoringStatus(e){if(!this._hass||0===e.length)return null;const t=this._hass;return function(e){let t=!1;const n={},i={};for(const s of e)s&&(t=!0,s.circuits&&Object.assign(n,s.circuits),s.mains&&Object.assign(i,s.mains));return t?{circuits:n,mains:i}:null}(await Promise.all(e.map(e=>this.monitoringMultiCache.fetchOne(t,e))))}async _buildFavoritesHorizonMaps(){if(!this._hass||!this._favRefs||!this._topology)return;const e=new Set;for(const t of Object.values(this._favRefs))t.configEntryId&&e.add(t.configEntryId);const t=new Map;await Promise.all(Array.from(e).map(async e=>{t.set(e,await this._fetchGraphSettingsFresh(e))})),this.horizonMap.clear(),this.subDeviceHorizonMap.clear();for(const e of Object.keys(this._topology.circuits)){const n=this._favRefs[e],i=n?.configEntryId?t.get(n.configEntryId)??null:null,s=n?.targetId??e;this.horizonMap.set(e,Vt(i,s))}if(this._topology.sub_devices)for(const e of Object.keys(this._topology.sub_devices)){const n=this._favRefs[e],i=n?.configEntryId?t.get(n.configEntryId)??null:null,s=n?.targetId??e;this.subDeviceHorizonMap.set(e,Bt(i,s))}}async loadHistory(){await Rt(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(!r[i]?.useRealtime)continue;const s=ht(n,this._config);if(!s)continue;const a=this._hass.states[s];if(!a)continue;const l=parseFloat(a.state);if(isNaN(l))continue;const c=Mt(i),d=It(c),h=Tt(c),p=e-c,u=this.powerHistory.get(t)??[];u.length>0&&e-u[u.length-1].time0&&e-u[u.length-1].time0&&this._topology)for(const{key:e,devId:t}of Ot(this._topology))i.has(t)&&s.add(e);const o=new Map;try{await Rt(this._hass,this._topology,this._config,o,t,i);for(const e of t.keys()){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}for(const e of s){const t=o.get(e);t?this.powerHistory.set(e,t):this.powerHistory.delete(e)}this.updateDOM(e)}catch(e){console.warn("SPAN Panel: history refresh failed",e),this._errorStore?.add({key:"fetch:history",level:"warning",message:n("error.history_failed"),persistent:!1})}}updateDOM(e){this._hass&&this._topology&&this._config&&(Wt(e,this._hass,this._topology,this._config,this.powerHistory,this.horizonMap),function(e,t,n,i,s,o){if(!n.sub_devices)return;const r=Nt(i);for(const[i,a]of Object.entries(n.sub_devices)){const n=e.querySelector(`[data-subdev="${jt(i)}"]`);if(!n)continue;const l=$t(a);if(l){const e=t.states[l],i=e&&parseFloat(e.state)||0,s=n.querySelector(".sub-power-value");s&&(s.innerHTML=`${ot(i)} ${st(i)}`)}const c=n.querySelectorAll("[data-chart-key]");for(const e of c){const n=e.dataset.chartKey;if(!n)continue;const a=s.get(n)||[];let l=_.power;n.endsWith("_soc")?l=_.soc:n.endsWith("_soe")&&(l=_.soe);const c=!!e.closest(".bess-chart-col");qt(e,t,a,o?.has(i)?Mt(o.get(i)):r,l,!1,c?120:150,void 0,n.endsWith("_soc")||n.endsWith("_soe"))}for(const e of Object.keys(a.entities||{})){const i=n.querySelector(`[data-eid="${jt(e)}"]`);if(!i)continue;const s=t.states[e];if(s){let e;if(t.formatEntityState)e=t.formatEntityState(s);else{e=s.state;const t=s.attributes.unit_of_measurement||"";t&&(e+=" "+t)}if("Wh"===(s.attributes.unit_of_measurement||"")){const t=parseFloat(s.state);isNaN(t)||(e=(t/1e3).toFixed(1)+" kWh")}i.textContent=e}}}}(e,this._hass,this._topology,this._config,this.powerHistory,this.subDeviceHorizonMap))}async onGraphSettingsChanged(e){if(this._hass){this._favRefs?await this._buildFavoritesHorizonMaps():(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 i=e.target,s=i?.closest(".toggle-pill");if(!s)return;const o=t.querySelector(".slide-confirm");if(!o||!o.classList.contains("confirmed"))return;e.stopPropagation(),e.preventDefault();const r=s.closest("[data-uuid]");if(!r||!this._topology||!this._hass)return;const a=r.dataset.uuid;if(!a)return;const l=this._topology.circuits[a];if(!l)return;const c=l.entities?.switch;if(!c)return;const d=this._hass.states[c];if(!d)return void console.warn("SPAN Panel: switch entity not found:",c);const h="on"===d.state?"turn_off":"turn_on";this._hass.callService("switch",h,{},{entity_id:c}).catch(e=>{console.warn("SPAN Panel: switch service call failed",e),this._errorStore?.add({key:"service:relay",level:"error",message:n("error.relay_failed"),persistent:!1})})}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,s.errorStore=this.errorStore,i.classList.contains("panel-gear")){if(this._inFavoritesView){const e=await this._buildFavoritesSections();if(0===e.length)return;return void s.open({favoritesMode:!0,perPanelSections:e})}return await this.graphSettingsCache.fetch(this._hass,this._configEntryId),void s.open({panelMode:!0,topology:this._topology,graphSettings:this.graphSettingsCache.settings,showFavorites:null!==this._panelFavorites,favoritePanelDeviceId:this._panelFavorites?.panelDeviceId,favoriteCircuitUuids:this._panelFavorites?.circuitUuids,favoriteSubDeviceIds:this._panelFavorites?.subDeviceIds,configEntryId:this._configEntryId})}const r=i.dataset.uuid;if(r&&this._topology){const e=this._topology.circuits[r];if(e){const t=this._favRefs?.[r]??null,n=t&&"circuit"===t.kind?t.targetId:r,i=t?.configEntryId??this._configEntryId;let a,l;t?[a,l]=await Promise.all([this._fetchGraphSettingsFresh(i),this._fetchMonitoringStatusFresh(i)]):(await Promise.all([this.graphSettingsCache.fetch(this._hass,i),this.monitoringCache.fetch(this._hass,i)]),a=this.graphSettingsCache.settings,l=this.monitoringCache.status);const c=e.entities?.current??e.entities?.power,d=c?l?.circuits?.[c]??null:null,h=a?.global_horizon??o,p=a?.circuits?.[n],u=p?{...p,globalHorizon:h}:{horizon:h,has_override:!1,globalHorizon:h},g=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,_=null!==t||(this._panelFavorites?.circuitUuids.has(n)??!1),f=this._inFavoritesView||null!==this._panelFavorites;return void s.open({...e,uuid:n,monitoringInfo:d,showMonitoring:this._showMonitoring,graphHorizonInfo:u,showFavorites:f,favoritePanelDeviceId:g,isFavorite:_,configEntryId:i})}}const a=i.dataset.subdevId;if(a&&this._topology?.sub_devices?.[a]){const e=this._topology.sub_devices[a],t=this._favRefs?.[a]??null,n=t&&"sub_device"===t.kind?t.targetId:a,i=t?.configEntryId??this._configEntryId;let r;t?r=await this._fetchGraphSettingsFresh(i):(await this.graphSettingsCache.fetch(this._hass,i),r=this.graphSettingsCache.settings);const l=r?.global_horizon??o,c=r?.sub_devices?.[n],d=c?{...c,globalHorizon:l}:{horizon:l,has_override:!1,globalHorizon:l},h=t?.panelDeviceId??this._panelFavorites?.panelDeviceId,p=null!==t||(this._panelFavorites?.subDeviceIds.has(n)??!1),u=this._inFavoritesView||null!==this._panelFavorites;s.open({subDeviceMode:!0,subDeviceId:n,name:e.name??n,deviceType:e.type??"",entities:e.entities,graphHorizonInfo:d,showFavorites:u,favoritePanelDeviceId:h,isFavorite:p,configEntryId:i})}}async _buildFavoritesSections(){if(!this._hass||!this._favRefs)return[];const e=function(e,t){const n=new Map;for(const i of Object.values(e)){if("circuit"!==i.kind)continue;const e=t.get(i.panelDeviceId);if(void 0===e)continue;let s=n.get(i.panelDeviceId);void 0===s&&(s={panelDeviceId:i.panelDeviceId,panelName:e.panelName,topology:e.topology,configEntryId:e.configEntryId,favoriteCircuitUuids:new Set},n.set(i.panelDeviceId,s)),s.favoriteCircuitUuids.add(i.targetId)}return Array.from(n.values()).sort((e,t)=>e.panelName.localeCompare(t.panelName))}(this._favRefs,this._perPanelInfo);if(0===e.length)return[];return await Promise.all(e.map(async e=>({panelDeviceId:e.panelDeviceId,panelName:e.panelName,topology:e.topology,graphSettings:await this._fetchGraphSettingsFresh(e.configEntryId),favoriteCircuitUuids:e.favoriteCircuitUuids,configEntryId:e.configEntryId})))}async _fetchGraphSettingsFresh(e){if(!this._hass)return null;try{const t={};e&&(t.config_entry_id=e);const i={type:"call_service",domain:a,service:"get_graph_settings",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:graph_settings",errorMessage:n("error.graph_settings_failed")}):await this._hass.callWS(i);return o?.response??null}catch(e){return console.warn("SPAN Panel: fresh graph settings fetch failed",e),null}}async _fetchMonitoringStatusFresh(e){if(!this._hass)return null;try{const t={};e&&(t.config_entry_id=e);const i={type:"call_service",domain:a,service:"get_monitoring_status",service_data:t,return_response:!0},s=this._errorStore?new We(this._errorStore):null,o=s?await s.callWS(this._hass,i,{errorId:"fetch:monitoring",errorMessage:n("error.monitoring_failed")}):await this._hass.callWS(i),r=o?.response;return r?{circuits:r.circuits,mains:r.mains}:null}catch(e){return console.warn("SPAN Panel: fresh monitoring status fetch failed",e),null}}bindSlideConfirm(e,t){const n=e.querySelector(".slide-confirm-knob"),i=e.querySelector(".slide-confirm-text");if(!n||!i)return;let s=!1,o=0,r=0;const a=t=>{e.classList.contains("confirmed")||(s=!0,o=t-n.offsetLeft,r=e.offsetWidth-n.offsetWidth-4,n.classList.remove("snapping"))},l=e=>{if(!s)return;const t=Math.max(2,Math.min(e-o,r));n.style.left=t+"px"},c=()=>{if(!s)return;s=!1;(n.offsetLeft-2)/r>=.9?(n.style.left=r+"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(),a(e.clientX)}),e.addEventListener("mousemove",e=>l(e.clientX)),e.addEventListener("mouseup",c),e.addEventListener("mouseleave",c),n.addEventListener("touchstart",e=>{e.preventDefault(),a(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"))})}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.monitoringMultiCache.clear(),this.graphSettingsCache.clear()}}const Kt='\n :host {\n --span-accent: var(--primary-color, #4dd9af);\n }\n\n ha-card {\n padding: 24px;\n background: var(--card-background-color, #1c1c1c);\n color: var(--primary-text-color, #e0e0e0);\n border-radius: var(--ha-card-border-radius, 12px);\n border: var(--ha-card-border-width, 1px) solid var(--ha-card-border-color, var(--divider-color, #333));\n box-shadow: var(--ha-card-box-shadow, none);\n }\n\n .panel-header {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n align-items: flex-start;\n gap: 8px 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .header-left { flex: 1 1 300px; min-width: 0; }\n .header-center { flex: 0 0 auto; }\n .header-right { flex: 0 1 auto; min-width: 0; }\n\n .panel-identity {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 8px 12px;\n margin-bottom: 12px;\n }\n\n .panel-title {\n font-size: 1.8em;\n font-weight: 700;\n margin: 0;\n color: var(--primary-text-color, #fff);\n }\n\n .panel-serial {\n font-size: 0.85em;\n color: var(--secondary-text-color, #999);\n font-family: monospace;\n }\n\n .panel-stats {\n display: flex;\n flex-wrap: wrap;\n gap: 16px 32px;\n }\n\n /* Favorites view header: gear + slide-to-arm + right-anchored legend/W-A cluster. */\n .favorites-summary {\n padding: 8px 24px;\n border-bottom: 1px solid var(--divider-color, #e0e0e0);\n display: flex;\n align-items: center;\n gap: 12px;\n }\n /* Override the generic .gear-icon { margin-left: auto } rule so the\n favorites gear stays flush-left instead of floating to the right of\n the flex row (same idea as .panel-identity .panel-gear does for\n real-panel headers). */\n .favorites-summary .favorites-gear {\n margin-left: 0;\n }\n /* Right-anchored cluster wrapping the shedding legend + W/A unit toggle.\n margin-left:auto moved here from .favorites-summary-unit-toggle so the\n legend and toggle cluster together, matching the real-panel header\n layout. */\n .favorites-summary-right {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 16px;\n }\n .favorites-subdevices-section {\n padding: 8px 16px 0;\n }\n\n /* Favorites view: responsive grid of per-contributing-panel status cards. */\n .favorites-panel-stats-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));\n gap: 12px;\n padding: 12px 24px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n .favorites-panel-card {\n background: var(--secondary-background-color, rgba(255, 255, 255, 0.04));\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n padding: 10px 14px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n .favorites-panel-card-title {\n font-size: 0.85em;\n font-weight: 600;\n color: var(--primary-text-color);\n opacity: 0.85;\n }\n .favorites-panel-card .panel-stats {\n gap: 10px 20px;\n }\n .favorites-panel-card .stat-value {\n font-size: 1.15em;\n }\n\n .stat { display: flex; flex-direction: column; }\n .stat-label { font-size: 0.8em; color: var(--secondary-text-color, #999); margin-bottom: 2px; }\n .stat-row { display: flex; align-items: baseline; gap: 2px; }\n .stat-value { font-size: 1.5em; font-weight: 700; color: var(--primary-text-color, #fff); }\n .stat-unit { font-size: 0.7em; font-weight: 400; color: var(--secondary-text-color, #999); }\n\n .header-right { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; padding-top: 8px; }\n .header-right-top { display: flex; gap: 20px; align-items: center; }\n .meta-item { font-size: 0.8em; color: var(--secondary-text-color, #999); }\n\n .shedding-legend { display: flex; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }\n .shedding-legend-item { display: inline-flex; align-items: center; gap: 3px; }\n .shedding-legend-item ha-icon { --mdc-icon-size: 16px; }\n .shedding-legend-secondary { --mdc-icon-size: 12px; opacity: 0.8; }\n .shedding-legend-text { font-size: 9px; font-weight: 600; }\n .shedding-legend-label { font-size: 0.7em; color: var(--secondary-text-color, #999); }\n\n .panel-gear {\n background: none;\n border: none;\n cursor: pointer;\n color: var(--secondary-text-color);\n opacity: 0.6;\n padding: 4px;\n margin-left: 8px;\n vertical-align: middle;\n }\n .panel-gear:hover { opacity: 1; }\n .header-center {\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding-top: 8px;\n }\n .panel-identity .panel-gear {\n margin-left: 0;\n }\n .slide-confirm {\n position: relative;\n display: inline-flex;\n align-items: center;\n width: 160px;\n height: 28px;\n border-radius: 14px;\n background: color-mix(in srgb, var(--primary-color, #4dd9af) 20%, var(--secondary-background-color, #333));\n vertical-align: middle;\n overflow: hidden;\n user-select: none;\n touch-action: none;\n }\n .slide-confirm-text {\n position: absolute;\n width: 100%;\n text-align: center;\n font-size: 0.65em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n pointer-events: none;\n z-index: 0;\n }\n .slide-confirm-knob {\n position: absolute;\n left: 2px;\n top: 2px;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n background: var(--secondary-text-color, #666);\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: grab;\n z-index: 1;\n transition: none;\n }\n .slide-confirm-knob ha-icon {\n --mdc-icon-size: 14px;\n color: var(--card-background-color, #1c1c1c);\n }\n .slide-confirm-knob.snapping {\n transition: left 0.25s ease;\n }\n .slide-confirm.confirmed {\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n }\n .slide-confirm.confirmed .slide-confirm-text {\n color: var(--state-active-color, var(--span-accent));\n }\n .slide-confirm.confirmed .slide-confirm-knob {\n background: var(--state-active-color, var(--span-accent));\n }\n .switches-disabled .toggle-pill {\n opacity: 0.3;\n pointer-events: none;\n }\n .unit-toggle {\n display: inline-flex;\n background: var(--secondary-background-color, #333);\n border-radius: 6px;\n overflow: hidden;\n margin-left: 8px;\n }\n .unit-btn {\n padding: 4px 10px;\n border: none;\n background: none;\n color: var(--secondary-text-color);\n font-size: 0.75em;\n font-weight: 600;\n cursor: pointer;\n }\n .unit-btn.unit-active {\n background: var(--primary-color, #4dd9af);\n color: var(--text-primary-color, #000);\n }\n\n .monitoring-summary {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 6px 16px;\n font-size: 0.8em;\n background: rgba(76, 175, 80, 0.1);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n }\n .monitoring-active { color: #4caf50; }\n .monitoring-counts { display: flex; gap: 12px; }\n .count-warning { color: #ff9800; }\n .count-alert { color: #f44336; }\n .count-overrides { color: var(--secondary-text-color); }\n\n .panel-grid {\n display: grid;\n grid-template-columns: 28px 1fr 1fr 28px;\n gap: 8px;\n align-items: stretch;\n }\n\n .tab-label {\n display: flex;\n align-items: center;\n font-size: 0.85em;\n font-weight: 600;\n color: var(--secondary-text-color, #999);\n user-select: none;\n }\n .tab-left { justify-content: flex-start; }\n .tab-right { justify-content: flex-end; }\n\n .circuit-slot {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px 20px;\n min-height: 140px;\n transition: opacity 0.3s;\n position: relative;\n overflow: hidden;\n }\n\n .circuit-col-span { min-height: 280px; }\n .circuit-row-span { border-left: 3px solid var(--span-accent); }\n .circuit-off .circuit-name,\n .circuit-off .breaker-badge,\n .circuit-off .power-value,\n .circuit-off .chart-container { opacity: 0.35; }\n .circuit-off .toggle-pill,\n .circuit-off .gear-icon { opacity: 1; }\n\n .circuit-empty {\n opacity: 0.2;\n min-height: 60px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-style: dashed;\n }\n .empty-label { color: var(--secondary-text-color, #999); font-size: 0.85em; }\n\n .circuit-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 6px;\n gap: 8px;\n }\n\n .circuit-info { display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0; }\n\n .breaker-badge {\n background: color-mix(in srgb, var(--span-accent) 15%, transparent);\n color: var(--span-accent);\n font-size: 0.7em;\n font-weight: 700;\n padding: 2px 7px;\n border-radius: 4px;\n white-space: nowrap;\n border: 1px solid color-mix(in srgb, var(--span-accent) 25%, transparent);\n flex-shrink: 0;\n }\n\n .circuit-name {\n font-size: 0.9em;\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--primary-text-color, #e0e0e0);\n }\n\n .circuit-controls { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }\n\n .power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .power-value strong { font-weight: 700; font-size: 1.1em; }\n .power-unit { font-size: 0.8em; font-weight: 400; color: var(--secondary-text-color, #999); margin-left: 1px; }\n .circuit-producer .power-value strong { color: var(--info-color, #4fc3f7); }\n\n .toggle-pill {\n display: flex;\n align-items: center;\n gap: 3px;\n padding: 2px 4px;\n border-radius: 10px;\n cursor: pointer;\n font-size: 0.65em;\n font-weight: 600;\n transition: background 0.2s;\n user-select: none;\n min-width: 40px;\n }\n .toggle-on {\n padding-left: 6px;\n background: color-mix(in srgb, var(--state-active-color, var(--span-accent)) 25%, transparent);\n color: var(--state-active-color, var(--span-accent));\n }\n .toggle-off {\n padding-right: 6px;\n background: color-mix(in srgb, var(--secondary-text-color) 15%, transparent);\n color: var(--secondary-text-color, #999);\n }\n .toggle-knob {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n transition: background 0.2s, margin 0.2s;\n }\n .toggle-on .toggle-knob {\n background: var(--state-active-color, var(--span-accent));\n margin-left: auto;\n }\n .toggle-off .toggle-knob {\n background: var(--secondary-text-color, #999);\n margin-right: auto;\n order: -1;\n }\n\n .circuit-status {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-top: 4px;\n padding: 0 4px;\n }\n .shedding-icon { opacity: 0.8; cursor: default; }\n .shedding-composite {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n }\n .shedding-icon-secondary { opacity: 0.8; }\n .shedding-label {\n font-size: 10px;\n font-weight: 600;\n opacity: 0.8;\n }\n .gear-icon {\n background: none;\n border: none;\n cursor: pointer;\n padding: 2px;\n opacity: 0.6;\n transition: opacity 0.2s;\n margin-left: auto;\n }\n .gear-icon:hover { opacity: 1; }\n .utilization {\n font-size: 0.75em;\n font-weight: 600;\n }\n .utilization-normal { color: #4caf50; }\n .utilization-warning { color: #ff9800; }\n .utilization-alert { color: #f44336; }\n .circuit-alert {\n border-color: #f44336 !important;\n box-shadow: 0 0 8px rgba(244, 67, 54, 0.3);\n }\n .circuit-custom-monitoring {\n border-left: 3px solid #ff9800;\n }\n\n .chart-container {\n width: 100%;\n aspect-ratio: 4 / 1;\n margin-top: 4px;\n overflow: hidden;\n min-width: 0;\n }\n\n .sub-devices {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 12px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .sub-device {\n background: var(--secondary-background-color, var(--card-background-color, #2a2a2a));\n border: 1px solid var(--divider-color, #333);\n border-radius: 12px;\n padding: 14px 16px;\n }\n .sub-device-bess,\n .sub-device-full {\n grid-column: 1 / -1;\n }\n\n .sub-device-header { display: flex; gap: 10px; align-items: baseline; margin-bottom: 8px; }\n .sub-device-type { font-size: 0.7em; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: var(--span-accent); }\n .sub-device-name { font-size: 0.85em; color: var(--secondary-text-color, #999); flex: 1; }\n .sub-power-value { font-size: 0.9em; color: var(--primary-text-color, #fff); white-space: nowrap; }\n .sub-power-value strong { font-weight: 700; font-size: 1.1em; }\n .sub-device .chart-container { margin-bottom: 8px; aspect-ratio: auto; }\n\n .bess-charts {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(0, 1fr));\n gap: 12px;\n margin-bottom: 10px;\n }\n .bess-chart-col { min-width: 0; }\n .bess-chart-title {\n font-size: 0.75em;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--secondary-text-color, #999);\n margin-bottom: 4px;\n }\n .bess-chart-col .chart-container { aspect-ratio: auto; }\n .sub-entity { display: flex; gap: 6px; padding: 3px 0; font-size: 0.85em; }\n .sub-entity-name { color: var(--secondary-text-color, #999); }\n .sub-entity-value { font-weight: 500; color: var(--primary-text-color, #e0e0e0); }\n\n /* ── Shared tab bar ────────────────────────────────────── */\n\n .shared-tab-bar {\n display: flex;\n gap: 0;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--divider-color, #333);\n }\n\n .shared-tab {\n padding: 8px 16px;\n cursor: pointer;\n font-size: 0.9em;\n font-weight: 500;\n color: var(--primary-text-color);\n opacity: 0.6;\n border: none;\n border-bottom: 2px solid transparent;\n background: none;\n transition: opacity 0.15s;\n }\n\n .shared-tab:hover {\n opacity: 0.85;\n }\n\n .shared-tab.active {\n opacity: 1;\n border-bottom-color: var(--span-accent);\n }\n\n /* ── List view search ──────────────────────────────────── */\n\n .list-search-container {\n margin-bottom: 12px;\n position: relative;\n }\n\n .list-search {\n width: 100%;\n padding: 8px 36px 8px 12px;\n border-radius: 8px;\n border: 1px solid var(--divider-color, #333);\n background: var(--secondary-background-color, #2a2a2a);\n color: var(--primary-text-color);\n font-size: 0.9em;\n box-sizing: border-box;\n outline: none;\n }\n\n .list-search:focus {\n border-color: var(--span-accent);\n }\n\n .list-search-clear {\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 2px;\n display: flex;\n align-items: center;\n opacity: 0.7;\n }\n\n .list-search-clear:hover {\n opacity: 1;\n }\n\n .list-unit-toggle {\n display: inline-flex;\n margin-bottom: 12px;\n }\n\n /* ── List rows ─────────────────────────────────────────── */\n\n .list-view {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n /* Each circuit is wrapped in a .list-cell so the row + its optional\n expanded chart stay together. In single-column flex mode the cell\n just stacks naturally. In multi-column grid mode the cell becomes\n one grid item, so the chart is always in the same column as its\n row. Area headers (rendered as siblings, not inside a cell) span\n all columns via their inline "grid-column: 1 / -1". */\n .list-cell {\n display: flex;\n flex-direction: column;\n min-width: 0;\n }\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: grid;\n grid-template-columns: repeat(var(--list-cols), minmax(0, 1fr));\n gap: 6px 8px;\n flex-direction: initial;\n }\n /* On narrow viewports a 2/3-column list would squeeze rows into an\n unreadable shape, so force stacking regardless of user preference. */\n @media (max-width: 599px) {\n .list-view[data-columns="2"],\n .list-view[data-columns="3"] {\n display: flex;\n flex-direction: column;\n }\n }\n\n .list-row {\n display: flex;\n align-items: center;\n padding: 12px 16px;\n gap: 10px;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.15s;\n }\n\n .list-row:hover {\n background: var(--secondary-background-color, #2a2a2a);\n }\n\n .list-row.circuit-off {\n opacity: 0.5;\n }\n\n .list-row.list-row-expanded {\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n border-bottom-color: transparent;\n }\n\n .list-circuit-name {\n flex: 1;\n color: var(--primary-text-color);\n font-size: 0.9em;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .list-status-badge {\n font-size: 0.75em;\n font-weight: 600;\n padding: 2px 8px;\n border-radius: 4px;\n flex-shrink: 0;\n }\n\n .list-status-on {\n color: #4dd9af;\n }\n\n .list-status-off {\n color: #f44336;\n }\n\n .list-power-value {\n font-size: 0.9em;\n font-weight: 600;\n min-width: 70px;\n text-align: right;\n flex-shrink: 0;\n }\n\n .list-expand-toggle {\n background: none;\n border: none;\n color: var(--secondary-text-color);\n cursor: pointer;\n padding: 4px;\n transition: transform 0.2s;\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n\n .list-expand-toggle.expanded {\n transform: rotate(180deg);\n }\n\n .list-row .gear-icon {\n background: transparent;\n border: none;\n padding: 2px;\n cursor: pointer;\n color: #555;\n display: inline-flex;\n align-items: center;\n }\n .list-row .gear-icon:hover {\n color: var(--primary-text-color);\n }\n\n /* ── Expanded circuit content ──────────────────────────── */\n\n .list-expanded-content {\n padding: 0;\n background: var(--card-background-color, #1c1c1c);\n border: 1px solid var(--divider-color, #333);\n border-top: none;\n border-radius: 0 0 8px 8px;\n margin-top: -6px;\n margin-bottom: 2px;\n }\n\n .circuit-slot.circuit-chart-only {\n border: none;\n margin: 0;\n background: none;\n padding: 8px 12px;\n min-height: 0;\n }\n\n /* ── Area headers ──────────────────────────────────────── */\n\n .area-header {\n padding: 16px 12px 6px;\n font-weight: 600;\n font-size: 0.85em;\n color: var(--secondary-text-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n }\n\n /* ── No results ────────────────────────────────────────── */\n\n .list-no-results {\n padding: 24px;\n text-align: center;\n color: var(--secondary-text-color);\n }\n\n';class Jt{constructor(){this._ctrl=new Qt,this._container=null,this._onGearClick=null,this._onToggleClick=null,this._onSidePanelClosed=null,this._onGraphSettingsChanged=null}get hass(){return this._ctrl.hass}set hass(e){this._ctrl.hass=e}set errorStore(e){this._ctrl.errorStore=e}setPanelFavorites(e){this._ctrl.setPanelFavorites(e)}async render(e,t,i,s,o){let r,a;this.stop(),this._ctrl.reset(),this._ctrl.showMonitoring=!0,this._container=e,this._ctrl.hass=t;try{const e=await Ye(t,i);r=e.topology,a=e.panelSize}catch(t){return void(e.innerHTML=`

${Oe(t.message)}

`)}this._ctrl.init(r,s,t,o??null),await this._ctrl.monitoringCache.fetch(t,o??null),await this._ctrl.fetchAndBuildHorizonMaps();const l=Math.ceil(a/2),c=this._ctrl.monitoringCache.status,d=nt(r,s),h=function(e){if(!e)return"";const t=Object.values(e.circuits??{}),i=Object.values(e.mains??{}),s=[...t,...i],o=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=80&&e.utilization_pct<100).length,r=s.filter(e=>void 0!==e.utilization_pct&&e.utilization_pct>=100).length,a=s.filter(e=>e.has_override).length;return`\n
\n ✓ ${n("status.monitoring")} · ${t.length} ${n("status.circuits")} · ${i.length} ${n("status.mains")}\n \n ${o>0?`${o} ${n(o>1?"status.warnings":"status.warning")}`:""}\n ${r>0?`${r} ${n(r>1?"status.alerts":"status.alert")}`:""}\n ${a>0?`${a} ${n(a>1?"status.overrides":"status.override")}`:""}\n \n
\n `}(c),p=function(e,t,n,i,s){const o=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),s=1===e.length?"single":ct(e)??"single";o.set(i,{uuid:t,circuit:n,layout:s});for(const t of e)r.add(t)}const a=new Set,l=new Set;for(const[e,t]of o)if("col-span"===t.layout){const n=t.circuit.tabs,i=at(Math.max(...n));0===lt(e)?a.add(i):l.add(i)}function c(e){const t=e.circuit.entities?.current??e.circuit.entities?.power,i=s?gt(s,t??""):null;let o;if(e.circuit.always_on)o="always_on";else{const t=e.circuit.entities?.select;o=t&&n.states[t]?n.states[t].state:"unknown"}return{monInfo:i,sheddingPriority:o}}let d="";for(let e=1;e<=t;e++){const t=2*e-1,s=2*e,h=o.get(t),p=o.get(s);if(d+=`
${t}
`,h&&"row-span"===h.layout){const{monInfo:t,sheddingPriority:o}=c(h);d+=vt(h.uuid,h.circuit,e,"2 / 4","row-span",n,i,t,o),d+=`
${s}
`;continue}if(!a.has(e))if(!h||"col-span"!==h.layout&&"single"!==h.layout)r.has(t)||(d+=mt(e,"2"));else{const{monInfo:t,sheddingPriority:s}=c(h);d+=vt(h.uuid,h.circuit,e,"2",h.layout,n,i,t,s)}if(!l.has(e))if(!p||"col-span"!==p.layout&&"single"!==p.layout)r.has(s)||(d+=mt(e,"3"));else{const{monInfo:t,sheddingPriority:s}=c(p);d+=vt(p.uuid,p.circuit,e,"3",p.layout,n,i,t,s)}d+=`
${s}
`}return d}(r,l,t,s,c),u=Et(r,t,s);e.innerHTML=`\n \n ${d}\n ${h}\n ${u?`
${u}
`:""}\n ${!1!==s.show_panel?`\n
\n ${p}\n
\n `:""}\n \n `,this._onGearClick=t=>{this._ctrl.onGearClick(t,e)},this._onToggleClick=t=>{this._ctrl.onToggleClick(t,e)},e.addEventListener("click",this._onGearClick),e.addEventListener("click",this._onToggleClick),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 g=e.querySelector(".slide-confirm");g&&(this._ctrl.bindSlideConfirm(g,e),e.classList.add("switches-disabled")),this._ctrl.setupResizeObserver(e,e),this._ctrl.startIntervals(e)}stop(){this._ctrl.stopIntervals(),this._container&&(this._onGearClick&&(this._container.removeEventListener("click",this._onGearClick),this._onGearClick=null),this._onToggleClick&&(this._container.removeEventListener("click",this._onToggleClick),this._onToggleClick=null),this._onSidePanelClosed&&(this._container.removeEventListener("side-panel-closed",this._onSidePanelClosed),this._onSidePanelClosed=null),this._onGraphSettingsChanged&&(this._container.removeEventListener("graph-settings-changed",this._onGraphSettingsChanged),this._onGraphSettingsChanged=null))}}const Xt="\n display:flex;align-items:center;gap:8px;margin-bottom:8px;\n",Zt="\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",Yt="\n min-width:130px;font-size:0.85em;color:var(--secondary-text-color);\n",en="\n min-width:160px;font-size:0.85em;color:var(--secondary-text-color);\n",tn="\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 nn(e,t,n,i,s){return`\n ${i}\n `}class sn{constructor(){this.errorStore=null,this._debounceTimer=null,this._configEntryId=null,this._notifyCloseHandler=null,this._headerHTML=""}stop(){this._notifyCloseHandler&&(document.removeEventListener("click",this._notifyCloseHandler),this._notifyCloseHandler=null),this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null)}async render(e,t,i,s=""){let o;void 0!==i&&(this._configEntryId=i),this._headerHTML=s,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=function(e){if(!e||"object"!=typeof e)return null;const t=e,n={};return"boolean"==typeof t.enabled&&(n.enabled=t.enabled),t.global_settings&&"object"==typeof t.global_settings&&(n.global_settings=t.global_settings),t.circuits&&"object"==typeof t.circuits&&(n.circuits=t.circuits),t.mains&&"object"==typeof t.mains&&(n.mains=t.mains),n}(n?.response)}catch(e){console.warn("SPAN Panel: monitoring status fetch failed",e),o=null}const r=o?.global_settings??{},l=!0===o?.enabled,c=o?.circuits??{},d=o?.mains??{},h=new Set;for(const e of Object.keys(t.states||{}))e.startsWith("notify.")&&h.add(e);const p=new Set(["notify","send_message"]);for(const e of Object.keys(t.services?.notify||{}))p.has(e)||h.add(`notify.${e}`);h.add("event_bus");const u=[...h].sort(),g=r.notify_targets??"",_=("string"==typeof g?g.split(","):g).map(e=>e.trim()).filter(Boolean),f=u.length>0&&u.every(e=>_.includes(e)),v=r.notification_title_template??"SPAN: {name} {alert_type}",m=r.notification_message_template??"{name} at {current_a}A ({utilization_pct}% of {breaker_rating_a}A rating)",b=r.notification_priority??"default",y=Object.entries(c).sort(([,e],[,t])=>(e.name??"").localeCompare(t.name??"")),w=Object.entries(d),x=[...y,...w],S=x.length>0&&x.every(([,e])=>!1!==e.monitoring_enabled),$=x.some(([,e])=>!1!==e.monitoring_enabled),C=y.map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","circuit")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","circuit")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","circuit")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","circuit")}\n \n ${o?``:""}\n \n \n `}).join(""),P=Object.entries(d).map(([e,t])=>{const i=Oe(t.name??e),s=!1!==t.monitoring_enabled,o=!0===t.has_override,r=s?"":"opacity:0.4;",a=Oe(e);return`\n \n \n \n \n ${nn(a,"continuous_threshold_pct",t.continuous_threshold_pct,"%","mains")}\n ${nn(a,"spike_threshold_pct",t.spike_threshold_pct,"%","mains")}\n ${nn(a,"window_duration_m",t.window_duration_m,"m","mains")}\n ${nn(a,"cooldown_duration_m",t.cooldown_duration_m,"m","mains")}\n \n ${o?``:""}\n \n \n `}).join("");e.innerHTML=`\n ${this._headerHTML}\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
\n ${0===u.length?`
${n("notification.no_targets")}
`:u.map(e=>{const i=_.includes(e),s="event_bus"===e,o=s?null:t.states[e],r=o?.attributes?.friendly_name,a=s?n("notification.event_bus_target"):r?`${Oe(r)} (${Oe(e)})`:Oe(e);return``}).join("")}\n
\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} {local_time}\n
\n
\n ${n("notification.event_bus_help")} span_panel_current_alert\n ${n("notification.event_bus_payload")} alert_source alert_id\n alert_name alert_type current_a\n breaker_rating_a threshold_pct utilization_pct\n panel_serial window_duration_s local_time\n
\n\n
\n ${n("notification.test_label")}\n \n \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 ${P}\n ${C}\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);const E=e.querySelector("#notify-all-targets");if(E&&u.length>0){const e=_.length>0;!f&&e&&(E.indeterminate=!0)}this._bindGlobalControls(e,t),this._bindNotifyTargetSelect(e,t),this._bindNotificationSettings(e,t),this._bindToggleAll(e,t,c,d),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"),s=e.querySelector("#global-fields"),o=e.querySelector("#global-status"),r=()=>{const t=[["continuous_threshold_pct","#g-continuous"],["spike_threshold_pct","#g-spike"],["window_duration_m","#g-window"],["cooldown_duration_m","#g-cooldown"]],n={};for(const[i,s]of t){const t=e.querySelector(s);if(!t)return null;const o=parseInt(t.value,10);if(Number.isNaN(o))return null;n[i]=o}return n},a=(e,t,i)=>{if(!e)return;const s=t instanceof Error?t.message:i;e.textContent=`${n("error.prefix")} ${s}`,e.style.color="var(--error-color, #f44336)"},l=()=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{const i=r();if(i)try{await this._callSetGlobal(t,i),await this.render(e,t)}catch(e){a(o,e,n("error.failed_save"))}else a(o,null,n("error.failed_save"))},u)};i&&i.addEventListener("change",async()=>{const o=i.checked;s&&(s.style.opacity=o?"":"0.4",s.style.pointerEvents=o?"":"none");const l=e.querySelector("#global-status");try{if(o){const e=r();if(!e)return void a(l,null,n("error.failed"));await this._callSetGlobal(t,e)}else await this._callSetGlobal(t,{enabled:!1})}catch(e){return void a(l,e,n("error.failed"))}await this.render(e,t)});for(const t of e.querySelectorAll("#global-fields input[type=number]"))t.addEventListener("input",l)}_bindNotifyTargetSelect(e,t){const i=e.querySelector("#notify-target-btn"),s=e.querySelector("#notify-target-dropdown"),o=e.querySelector("#notify-target-label");if(!i||!s)return;i.addEventListener("click",e=>{e.stopPropagation();const t="none"!==s.style.display;s.style.display=t?"none":"block"});const r=t=>{const n=e.querySelector("#notify-target-select");n&&!n.contains(t.target)&&(s.style.display="none")};document.addEventListener("click",r),this._notifyCloseHandler=r;const a=()=>{const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value);if(o){const e=i.map(e=>"event_bus"===e?n("notification.event_bus_target"):e);o.textContent=e.length?e.join(", "):n("notification.none_selected")}const s=e.querySelector("#notify-all-targets");if(s){const t=[...e.querySelectorAll(".notify-target-cb")];s.checked=t.length>0&&t.every(e=>e.checked),s.indeterminate=!s.checked&&t.some(e=>e.checked)}this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{notify_targets:i.join(", ")})}catch(e){console.warn("SPAN Panel: notification targets save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)},l=e.querySelector("#notify-all-targets");l&&l.addEventListener("change",()=>{for(const t of e.querySelectorAll(".notify-target-cb"))t.checked=l.checked;const t=e.querySelector("#notify-target-btn");t&&(t.style.opacity=l.checked?"0.4":"",t.style.pointerEvents=l.checked?"none":""),l.checked&&(s.style.display="none"),a()});for(const t of e.querySelectorAll(".notify-target-cb"))t.addEventListener("change",()=>{a()})}_bindNotificationSettings(e,t){const i=e.querySelector("#g-priority"),s=e.querySelector("#g-title-template"),o=e.querySelector("#g-message-template"),r=(e,i)=>{this._debounceTimer&&clearTimeout(this._debounceTimer),this._debounceTimer=setTimeout(async()=>{try{await this._callSetGlobal(t,{[e]:i})}catch(e){console.warn("SPAN Panel: notification settings save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}},u)};i&&i.addEventListener("change",async()=>{try{await this._callSetGlobal(t,{notification_priority:i.value}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: notification priority change failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}}),s&&s.addEventListener("input",()=>{r("notification_title_template",s.value)}),o&&o.addEventListener("input",()=>{r("notification_message_template",o.value)});const l=e.querySelector("#test-notification-btn"),c=e.querySelector("#test-notification-status");l&&l.addEventListener("click",async()=>{l.disabled=!0,c&&(c.textContent=n("notification.test_sending"),c.style.color="var(--secondary-text-color)");try{this._debounceTimer&&(clearTimeout(this._debounceTimer),this._debounceTimer=null);const i=[...e.querySelectorAll(".notify-target-cb:checked")].map(e=>e.value).join(", ");await this._callSetGlobal(t,{notify_targets:i});const s={};this._configEntryId&&(s.config_entry_id=this._configEntryId),await t.callWS({type:"call_service",domain:a,service:"test_notification",service_data:s}),c&&(c.textContent=n("notification.test_sent"),c.style.color="var(--success-color, #4caf50)")}catch(e){if(c){const t=e instanceof Error?e.message:n("error.failed");c.textContent=`${n("error.prefix")} ${t}`,c.style.color="var(--error-color, #f44336)"}}finally{l.disabled=!1}})}_bindToggleAll(e,t,i,s){const o=e.querySelector("#toggle-all-circuits");o&&o.addEventListener("change",async()=>{const r=o.checked,l=[...Object.keys(i).map(e=>t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: circuit monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})})),...Object.keys(s).map(e=>t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:e,monitoring_enabled:r})}).catch(e=>{console.warn("SPAN Panel: mains monitoring toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1})}))];await Promise.all(l),await this.render(e,t)})}_bindMainsToggles(e,t){for(const i of e.querySelectorAll(".mains-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_mains_threshold",service_data:this._serviceData({leg:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: mains threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindCircuitToggles(e,t){for(const i of e.querySelectorAll(".circuit-toggle"))i.addEventListener("change",async()=>{const s=i.dataset.entity,o=i.checked;try{await t.callWS({type:"call_service",domain:a,service:"set_circuit_threshold",service_data:this._serviceData({circuit_id:s,monitoring_enabled:o})})}catch(e){return console.warn("SPAN Panel: circuit threshold toggle failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),void(i.checked=!o)}await this.render(e,t)})}_bindThresholdInputs(e,t){const i=new Map;for(const s of e.querySelectorAll(".threshold-input"))s.addEventListener("input",()=>{const o=`${s.dataset.entity}-${s.dataset.field}`,r=i.get(o);r&&clearTimeout(r),i.set(o,setTimeout(async()=>{const i=parseInt(s.value,10);if(!i||i<1)return;const o=s.dataset.entity,r=s.dataset.field,l=s.dataset.type,c="mains"===l?"set_mains_threshold":"set_circuit_threshold",d="mains"===l?"leg":"circuit_id";try{await t.callWS({type:"call_service",domain:a,service:c,service_data:this._serviceData({[d]:o,[r]:i})}),await this.render(e,t)}catch(e){console.warn("SPAN Panel: threshold input save failed",e),this.errorStore?.add({key:"service:monitoring",level:"error",message:n("error.threshold_failed"),persistent:!1}),s.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;if(!i)return;const s=n.dataset.type,o="mains"===s?"clear_mains_threshold":"clear_circuit_threshold",r=this._serviceData("mains"===s?{leg:i}:{circuit_id:i});await t.callService(a,o,r),await this.render(e,t)})}}function on(e=""){const t=e?` value="${Oe(e)}"`:"",i=e?"":"display:none;";return`\n
\n \n \n
\n `}function rn(e,t,i,s,o,r,a){const c=t.entities?.power,d=c?i.states[c]:null,h=d&&parseFloat(d.state)||0,p=t.entities?.switch,u=p?i.states[p]:null,g=u?"on"===u.state:(d?.attributes?.relay_state||t.relay_state)===l,_=t.breaker_rating_a,m=_?`${Math.round(_)}A`:"",b=Oe(t.name||n("grid.unknown")),y=dt(s),w="current"===y.entityRole;let x;if(g)if(w){const e=t.entities?.current,n=e?i.states[e]:null,s=n&&parseFloat(n.state)||0;x=`${y.format(s)}A`}else x=`${ot(h)}${st(h)}`;else x="";const S=r||"unknown";let $="";if("unknown"!==S){const e=f[S]??f.unknown??{icon:"mdi:help",color:"#999",label:()=>"Unknown"};$=e.icon2?`\n \n \n `:e.textLabel?`\n \n ${e.textLabel}\n `:``}let C="",P=o?.utilization_pct??null;if(null==P&&t.breaker_rating_a){const e=t.entities?.current,n=e?i.states[e]:null,s=n?Math.abs(parseFloat(n.state)||0):0;P=Math.round(s/t.breaker_rating_a*1e3)/10}if(null!=P){C=`=80?"utilization-warning":"utilization-normal"}">${Math.round(P)}%`}const k=!!o&&_t(o)?v:"#555",E=``,z=!1!==t.is_user_controllable&&!!t.entities?.switch?`
\n ${n(g?"grid.on":"grid.off")}\n \n
`:`${g?"ON":"OFF"}`;return`\n
\n ${m?`${m}`:""}\n ${C}\n ${b}\n ${$}\n ${z}\n \n ${x}\n \n ${E}\n \n
\n `}function an(e,t,n,i,s){const o=t.entities?.power,r=o?n.states[o]:null,a=r&&parseFloat(r.state)||0,d=t.device_type===c||a<0,h=t.entities?.switch,p=h?n.states[h]:null,u=ft(0,s,p?"on"===p.state:(r?.attributes?.relay_state||t.relay_state)===l,d),g=Oe(e);return`\n
\n
\n
\n
\n
\n `}function ln(e){return`
${Oe(e)}
`}function cn(e,t,n){const i=e.entities?.switch,s=i?t.states[i]:null,o=e.entities?.power,r=o?t.states[o]:null,a=s?"on"===s.state:(r?.attributes?.relay_state||e.relay_state)===l;let c;if("current"===(n.chart_metric||"power")){const n=e.entities?.current,i=n?t.states[n]:null;c=i?Math.abs(parseFloat(i.state)||0):0}else c=r?Math.abs(parseFloat(r.state)||0):0;return{isOn:a,value:c}}function dn(e,t){if(e.always_on)return"always_on";const n=e.entities?.select,i=n?t.states[n]:null;return i?i.state:"unknown"}function hn(e,t,n,i){const s=cn(e,n,i),o=cn(t,n,i);return s.isOn&&!o.isOn?-1:!s.isOn&&o.isOn?1:o.value-s.value}function pn(e,t,n){return e.sort((e,i)=>hn(e[1],i[1],t,n))}function un(e){return e.entities?.current??e.entities?.power??""}class gn{constructor(e){this._expandedUuids=new Set,this._searchQuery="",this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null,this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null,this._viewName=null,this._columns=1,this._ctrl=e}setColumns(e){const t=Math.max(1,Math.min(3,Math.floor(e)));this._columns=t}setInitialExpansion(e){this._expandedUuids=new Set(e)}setInitialSearchQuery(e){this._searchQuery=e}setViewName(e){this._viewName=e}renderActivityView(e,t,n,i,s,o){this._unbindEvents(),this._hass=t,this._topology=n,this._config=i,this._monitoringStatus=s;const r=pn(Object.entries(n.circuits),t,i);let a=o+on(this._searchQuery);a+=`
`;for(const[e,n]of r){const o=gt(s,un(n)),r=dn(n,t),l=this._expandedUuids.has(e);a+=`
`,a+=rn(e,n,t,i,o,r,l),l&&(a+=an(e,n,t,0,o)),a+="
"}a+="
",a+="",e.innerHTML=a;const l=e.querySelector("span-side-panel");l&&(l.hass=t,l.errorStore=this._ctrl.errorStore),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}renderAreaView(e,t,i,s,o,r){this._unbindEvents(),this._hass=t,this._topology=i,this._config=s,this._monitoringStatus=o;const a=n("list.unassigned_area"),l=new Map;for(const[e,t]of Object.entries(i.circuits)){const n=t.area??a,i=l.get(n);i?i.push([e,t]):l.set(n,[[e,t]])}const c=[...l.keys()].sort((e,t)=>e===a?1:t===a?-1:e.localeCompare(t));let d=r+on(this._searchQuery);d+=`
`;for(const e of c){const n=l.get(e);if(!n)continue;const i=pn(n,t,s);d+=ln(e);for(const[e,n]of i){const i=gt(o,un(n)),r=dn(n,t),a=this._expandedUuids.has(e);d+=`
`,d+=rn(e,n,t,s,i,r,a),a&&(d+=an(e,n,t,0,i)),d+="
"}}d+="
",d+="",e.innerHTML=d;const h=e.querySelector("span-side-panel");h&&(h.hass=t,h.errorStore=this._ctrl.errorStore),this._bindEvents(e),this._searchQuery&&this._applyFilter(e),this._ctrl.updateDOM(e)}updateCollapsedRows(e,t,i,s){const o=dt(s),r="current"===o.entityRole,a=e.querySelectorAll(".list-row[data-row-uuid]");for(const e of a){const a=e.dataset.rowUuid;if(!a)continue;const l=i.circuits[a];if(!l)continue;const{isOn:c,value:d}=cn(l,t,s),h=e.querySelector(".list-power-value");if(h)if(c)if(r)h.innerHTML=`${o.format(d)}A`;else{const e=l.entities?.power,n=e?t.states[e]:null,i=n&&parseFloat(n.state)||0;h.innerHTML=`${ot(i)}${st(i)}`}else h.innerHTML="";const p=e.querySelector(".toggle-pill");if(p){p.classList.toggle("toggle-on",c),p.classList.toggle("toggle-off",!c);const e=p.querySelector(".toggle-label");e&&(e.textContent=n(c?"grid.on":"grid.off"))}const u=e.querySelector(".list-status-badge");u&&(u.textContent=c?"ON":"OFF",u.classList.toggle("list-status-on",c),u.classList.toggle("list-status-off",!c)),e.classList.toggle("circuit-off",!c)}!function(e,t,n,i){const s=e.querySelector(".list-view");if(s)for(const e of function(e,t){let n={anchor:null,units:[]};const i=[n];for(const s of[...e.children])if(s.classList.contains("area-header"))n={anchor:s,units:[]},i.push(n);else if(s.classList.contains("list-cell")){const e=s.dataset.cellUuid,i=e?t.circuits[e]:void 0;e&&i&&n.units.push({cell:s,uuid:e,circuit:i})}return i}(s,n)){if(e.units.length<2)continue;const n=[...e.units].sort((e,n)=>hn(e.circuit,n.circuit,t,i));if(!n.some((t,n)=>t.uuid!==e.units[n].uuid))continue;let o=e.anchor;for(const e of n)o?o.after(e.cell):s.prepend(e.cell),o=e.cell}}(e,t,i,s)}stop(){this._unbindEvents(),null===this._viewName&&(this._expandedUuids.clear(),this._searchQuery=""),this._hass=null,this._topology=null,this._config=null,this._monitoringStatus=null}_dispatchFavoritesViewState(){if(!this._viewName||!this._container)return;const e={view:this._viewName,expanded:[...this._expandedUuids],searchQuery:this._searchQuery};this._container.dispatchEvent(new CustomEvent("favorites-view-state-changed",{detail:e,bubbles:!0,composed:!0}))}_bindEvents(e){this._container=e,this._clickHandler=t=>{const n=t.target;if(!n)return;const i=n.closest(".list-expand-toggle");if(i){const e=i.dataset.expandUuid;return void(e&&this._toggleExpand(e))}if(n.closest(".gear-icon"))return void this._ctrl.onGearClick(t,e);if(n.closest(".toggle-pill"))return void this._ctrl.onToggleClick(t,e);if(n.closest(".list-search-clear")){const t=e.querySelector(".list-search");return void(t&&(t.value="",t.dispatchEvent(new Event("input",{bubbles:!0}))))}const s=n.closest(".unit-btn");if(s){const t=s.dataset.unit;t&&e.dispatchEvent(new CustomEvent("unit-changed",{detail:t,bubbles:!0,composed:!0}))}},this._inputHandler=t=>{const n=t.target;n&&n.classList.contains("list-search")&&(this._searchQuery=n.value.toLowerCase(),this._applyFilter(e),this._dispatchFavoritesViewState())},this._graphSettingsHandler=()=>{this._ctrl.onGraphSettingsChanged(e).then(()=>{this._ctrl.updateDOM(e)}).catch(()=>{})},e.addEventListener("click",this._clickHandler),e.addEventListener("input",this._inputHandler),e.addEventListener("graph-settings-changed",this._graphSettingsHandler);const t=e.querySelector(".slide-confirm");t&&(this._ctrl.bindSlideConfirm(t,e),e.classList.add("switches-disabled"))}_unbindEvents(){this._container&&(this._clickHandler&&this._container.removeEventListener("click",this._clickHandler),this._inputHandler&&this._container.removeEventListener("input",this._inputHandler),this._graphSettingsHandler&&this._container.removeEventListener("graph-settings-changed",this._graphSettingsHandler)),this._container=null,this._clickHandler=null,this._inputHandler=null,this._graphSettingsHandler=null}_applyFilter(e){const t=e.querySelector(".list-search-clear");t&&(t.style.display=this._searchQuery?"":"none");const n=e.querySelectorAll(".list-cell[data-cell-uuid]");for(const e of n){const t=e.querySelector(".list-circuit-name"),n=(t?.textContent?.toLowerCase()??"").includes(this._searchQuery);e.style.display=n?"":"none"}const i=e.querySelectorAll(".area-header");for(const e of i){let t=!1,n=e.nextElementSibling;for(;n&&!n.classList.contains("area-header");){if(n.classList.contains("list-cell")&&"none"!==n.style.display){t=!0;break}n=n.nextElementSibling}e.style.display=t?"":"none"}}_toggleExpand(e){if(!(this._container&&this._hass&&this._topology&&this._config))return;const t=jt(e),n=this._container.querySelector(`.list-cell[data-cell-uuid="${t}"]`);if(!n)return;const i=n.querySelector(`.list-row[data-row-uuid="${t}"]`),s=n.querySelector(`.list-expand-toggle[data-expand-uuid="${t}"]`);if(i){if(this._expandedUuids.has(e)){this._expandedUuids.delete(e);const o=n.querySelector(`.list-expanded-content[data-expanded-uuid="${t}"]`);o&&o.remove(),s&&s.classList.remove("expanded"),i.classList.remove("list-row-expanded")}else{this._expandedUuids.add(e);const t=this._topology.circuits[e];if(!t)return;const n=gt(this._monitoringStatus,un(t)),o=an(e,t,this._hass,this._config,n);i.insertAdjacentHTML("afterend",o),s&&s.classList.add("expanded"),i.classList.add("list-row-expanded"),this._ctrl.updateDOM(this._container)}this._dispatchFavoritesViewState()}}}function _n(e,t){return`${e}|${t}`}class fn{async build(e,t,n,i){const s=new Map;for(const e of n)s.set(e.id,e);const o=i?new We(i):null,r=[];for(const[n,i]of Object.entries(t)){if(!((i?.circuits?.length??0)>0||(i?.sub_devices?.length??0)>0))continue;const t=s.get(n);t&&r.push((async()=>{try{const i=await Ye(e,n,o);return{panelDeviceId:n,panel:t,topology:i.topology}}catch(e){return console.warn("SPAN Panel: favorites topology fetch failed",n,e),{panelDeviceId:n,panel:t,topology:null}}})())}const a=(await Promise.all(r)).filter(e=>null!==e.topology),l=a.length>1,c={},d={},h={},p=new Set,u=[];for(const{panelDeviceId:e,panel:n,topology:i}of a){if(!i)continue;const s=n.config_entries?.[0]??null;s&&p.add(s);const o=n.name_by_user??n.name??i.device_name??"";u.push({panelDeviceId:e,panelName:o,topology:i});const r=t[e],a=r?.circuits??[],g=r?.sub_devices??[];for(const t of a){const n=i.circuits?.[t];if(!n)continue;const r=_n(e,t),a=l&&o?`${o} · ${n.name}`:n.name;c[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"circuit",targetId:t,configEntryId:s}}for(const t of g){const n=i.sub_devices?.[t];if(!n)continue;const r=_n(e,t),a=l&&o&&n.name?`${o} · ${n.name}`:n.name??t;d[r]={...n,name:a},h[r]={panelDeviceId:e,kind:"sub_device",targetId:t,configEntryId:s}}}return{topology:{circuits:c,sub_devices:d,panel_entities:{},device_name:"",_favoriteRefs:h},entryIds:Array.from(p),perPanelStats:u}}}const vn="span_panel_favorites_view_state";function mn(e){try{localStorage.setItem(vn,JSON.stringify(e))}catch{}}var bn;const yn="favorites";let wn=bn=class extends Pe{constructor(){super(...arguments),this.narrow=!1,this._panels=[],this._selectedPanelId=null,this._activeTab="dashboard",this._discovered=!1,this._listColumns=qe(),this._favorites={},this._favoritesViewState={expanded:{activity:[],area:[]}},this._favoritesPanelStats=[],this._dashboardTab=new Jt,this._monitoringTab=new sn,this._listDashCtrl=new Qt,this._listCtrl=new gn(this._listDashCtrl),this._favCache=new Be,this._favCtrl=new fn,this._favoritesMonitoringTabs=new Map,this._errorStore=new Le,this._watchedPanelId=null,this._discovering=!1,this._refreshSeq=0,this._areaUnsub=null,this._areaSubscribing=!1,this._onVisibilityChange=null,this._onFavoritesChanged=null,this._deviceRegistryUnsub=null,this._pendingTabRender=!1,this._persistFavoritesViewStateTimer=null,this._tabRenderScheduler=function(e){let t=null,n=!1;return async function i(){if(t)return n=!0,void await t.catch(()=>{});const s=(async()=>{try{await e()}finally{t=null,n&&(n=!1,await i())}})();t=s,await s}}(async()=>this._renderTab()),this._beginRender=function(){let e=0;return()=>{e+=1;const t=e;return()=>e!==t}}()}get _root(){const e=this.shadowRoot;if(!e)throw new Error("span-panel: shadow root is not available");return e}connectedCallback(){super.connectedCallback(),this._dashboardTab.errorStore=this._errorStore,this._listDashCtrl.errorStore=this._errorStore,this._favCache.errorStore=this._errorStore,this._monitoringTab.errorStore=this._errorStore,this._onVisibilityChange=()=>{"visible"===document.visibilityState&&this._discovered&&this.hass&&this._scheduleTabRender()},document.addEventListener("visibilitychange",this._onVisibilityChange),this._onFavoritesChanged=()=>{this._refreshFavorites()},document.addEventListener(Ge,this._onFavoritesChanged),this._subscribeDeviceRegistry()}disconnectedCallback(){this._dashboardTab.stop(),this._monitoringTab.stop(),this._listCtrl.stop(),this._listDashCtrl.stopIntervals();for(const e of this._favoritesMonitoringTabs.values())e.stop();this._favoritesMonitoringTabs.clear(),this._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._onVisibilityChange&&(document.removeEventListener("visibilitychange",this._onVisibilityChange),this._onVisibilityChange=null),this._onFavoritesChanged&&(document.removeEventListener(Ge,this._onFavoritesChanged),this._onFavoritesChanged=null),this._unsubscribeDeviceRegistry(),this._persistFavoritesViewStateTimer&&(clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=null),this._errorStore.dispose(),super.disconnectedCallback()}firstUpdated(){this.hass&&!this._discovered&&this._discoverPanels()}updated(e){if(e.has("hass")){const t=e.get("hass");this._dashboardTab.hass=this.hass,this._listDashCtrl.hass=this.hass,this._errorStore.updateHass(this.hass),this._discovered?this._root.getElementById("tab-content")||this._scheduleTabRender():this._discoverPanels(),!t&&this.hass&&this._subscribeDeviceRegistry()}if(this._discovered&&(e.has("_discovered")||e.has("_activeTab")||e.has("_selectedPanelId")||e.has("_chartMetric")||e.has("_listColumns"))){if(this._isFavoritesView&&"dashboard"===this._activeTab)return void(this._activeTab="activity");this._scheduleTabRender()}if(e.has("_selectedPanelId")&&(this._selectedPanelId!==yn&&this._selectedPanelId?(this._updatePanelStatusWatch(),this._listDashCtrl.setFavoritesPerPanelInfo(null)):(this._errorStore.clearPanelStatusWatch(),this._watchedPanelId=null)),this._discovered&&(e.has("_panels")||e.has("_selectedPanelId"))){const e=this.shadowRoot?.getElementById("panel-select");e&&null!==this._selectedPanelId&&e.value!==this._selectedPanelId&&(e.value=this._selectedPanelId)}if(e.has("hass")&&this._discovered&&("activity"===this._activeTab||"area"===this._activeTab)){const e=this._root.getElementById("tab-content"),t=this._listDashCtrl.topology;if(e&&t){this._listCtrl.updateCollapsedRows(e,this.hass,t,this._buildDashboardConfig());const n=e.querySelector("span-side-panel");n&&(n.hass=this.hass,n.errorStore=this._errorStore)}}}setConfig(e){}render(){var i,s,o;if(i=this.hass?.language,e=i&&t[i]?i:"en",!this.hass)return le` +
+
+
Span Panel
+
+
+
+
${n("card.connecting")}
+
+ `;if(!this._discovered){const e=this._errorStore.hasPersistent("discovery-failed");return le` +
+
+ +
Span Panel
+
+
+
+ + ${e?de:le`
${n("card.connecting")}
`} +
+ `}return le`
- +
+
${Fe((s=this._buildTabList(),o=this._activeTab,`
${s.map(e=>``).join("")}
`))}
- -
- ${Te((o=[{id:"dashboard",label:n("tab.by_panel"),icon:"mdi:view-dashboard"},{id:"activity",label:n("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:n("tab.by_area"),icon:"mdi:home-group"},{id:"monitoring",label:n("tab.monitoring"),icon:"mdi:monitor-eye"}],s=this._activeTab,`
${o.map(e=>``).join("")}
`))} -
+
- `:se` -
-
- -
Span Panel
-
-
-
-
${this._discoveryError??"Loading…"}
-
- `}_onPanelChange(e){const t=e.target;this._selectedPanelId=t.value,localStorage.setItem("span_panel_selected",t.value),this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null),this._scheduleTabRender()}_onTabClick(e){const t=e.target.closest(".shared-tab");if(!t)return;const n=t.dataset.tab;n&&n!==this._activeTab&&(this._activeTab=n,this._scheduleTabRender())}_onTabContentClick(e){const t=e.target.closest(".unit-btn");if(t){const e=t.dataset.unit;if(!e||e===this._chartMetric)return;return this._chartMetric=e,localStorage.setItem("span_panel_metric",e),void("dashboard"===this._activeTab&&this._scheduleTabRender())}}_onSidePanelClosed(){if("dashboard"===this._activeTab){const e=this._dashboardTab._ctrl;e.monitoringCache.invalidate(),e.graphSettingsCache.invalidate()}}_onUnitChanged(e){const t=e.detail;t&&t!==this._chartMetric&&(this._chartMetric=t,localStorage.setItem("span_panel_metric",t),this._scheduleTabRender())}_onGraphSettingsChanged(){if("dashboard"===this._activeTab){const e=this.shadowRoot.getElementById("tab-content");if(e){this._dashboardTab._ctrl.onGraphSettingsChanged(e)}}}_onNavigateTab(e){const t=e.detail;t&&(this._activeTab=t,this._scheduleTabRender())}_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)))}async _discoverPanels(){if(!this.hass)return;try{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)}catch(e){return console.error("SPAN Panel: device discovery failed",e),void(this._discoveryError=`Discovery failed: ${e.message??e}`)}this._discoveryError=null,this._discovered=!0;const e=localStorage.getItem("span_panel_selected");e&&this._panels.some(t=>t.id===e)?this._selectedPanelId=e:this._panels.length>0&&(this._selectedPanelId=this._panels[0].id),this._chartMetric=localStorage.getItem("span_panel_metric")||"power"}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _scheduleTabRender(){await this.updateComplete,await this._renderTab()}async _renderTab(){this._dashboardTab.stop(),this._monitoringTab.stop(),this._listCtrl.stop(),this._listDashCtrl.stopIntervals();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"activity":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]??null;try{const t=await Oe(this.hass,this._selectedPanelId??void 0),i=this._buildDashboardConfig();this._listDashCtrl.init(t.topology,i,this.hass,n),await this._listDashCtrl.monitoringCache.fetch(this.hass,n),await this._listDashCtrl.fetchAndBuildHorizonMaps(),this._listCtrl.renderActivityView(e,this.hass,t.topology,i,this._listDashCtrl.monitoringCache.status),e.insertAdjacentHTML("afterbegin",``),await this._listDashCtrl.loadHistory(),this._listDashCtrl.updateDOM(e),this._listDashCtrl.startIntervals(e)}catch(t){const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t.message,e.appendChild(n)}break}case"area":{e.innerHTML="";const t=this._panels.find(e=>e.id===this._selectedPanelId),n=t?.config_entries?.[0]??null;try{const t=await Oe(this.hass,this._selectedPanelId??void 0),i=this._buildDashboardConfig();this._listDashCtrl.init(t.topology,i,this.hass,n),await this._listDashCtrl.monitoringCache.fetch(this.hass,n),await this._listDashCtrl.fetchAndBuildHorizonMaps(),this._listCtrl.renderAreaView(e,this.hass,t.topology,i,this._listDashCtrl.monitoringCache.status),e.insertAdjacentHTML("afterbegin",``),await this._listDashCtrl.loadHistory(),this._listDashCtrl.updateDOM(e),this._listDashCtrl.startIntervals(e),this._areaUnsub||async function(e,t,n){if(!e.connection)return()=>{};const i=async()=>{try{const i=new Map;for(const[e,n]of Object.entries(t.circuits))i.set(e,n.area);await Ie(e,t);for(const[e,o]of Object.entries(t.circuits))if(o.area!==i.get(e))return void n()}catch(e){console.warn("[span-panel] area registry update failed:",e)}},[o,s]=await Promise.all([e.connection.subscribeEvents(i,"entity_registry_updated"),e.connection.subscribeEvents(i,"area_registry_updated")]);return()=>{o(),s()}}(this.hass,t.topology,()=>{"area"===this._activeTab&&this._scheduleTabRender()}).then(e=>{this._areaUnsub=e}).catch(()=>{})}catch(t){const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t.message,e.appendChild(n)}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}}}};Wt.styles=((e,...t)=>{const n=1===e.length?e[0]:t.reduce((t,n,i)=>t+(e=>{if(!0===e._$cssResult$)return e.cssText;if("number"==typeof e)return e;throw Error("Value passed to 'css' function must be a 'css' function result: "+e+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+e[i+1],e[0]);return new x(n,e,y)})` + `}_onPanelChange(e){const t=e.target;this._selectedPanelId=t.value,localStorage.setItem("span_panel_selected",t.value),this._isFavoritesView&&"dashboard"===this._activeTab&&(this._activeTab="activity"),this._areaSubscribing=!1,this._areaUnsub&&(this._areaUnsub(),this._areaUnsub=null)}get _isFavoritesView(){return this._selectedPanelId===yn}_onTabClick(e){const t=e.target.closest(".shared-tab");if(!t)return;const n=t.dataset.tab;n&&n!==this._activeTab&&(this._activeTab=n,this._isFavoritesView&&"dashboard"!==n&&(this._favoritesViewState.activeTab=n,mn(this._favoritesViewState)))}_onTabContentClick(e){const t=e.target.closest(".unit-btn");if(t){const e=t.dataset.unit;if(!e||e===this._chartMetric)return;return this._chartMetric=e,void localStorage.setItem("span_panel_metric",e)}}_onSidePanelClosed(){if("dashboard"===this._activeTab){const e=this._dashboardTab._ctrl;e.monitoringCache.invalidate(),e.graphSettingsCache.invalidate()}this._listDashCtrl.monitoringMultiCache.invalidate(),this._pendingTabRender&&(this._pendingTabRender=!1,this._scheduleTabRender())}_onUnitChanged(e){const t=e.detail;t&&t!==this._chartMetric&&(this._chartMetric=t,localStorage.setItem("span_panel_metric",t))}_onListColumnsChanged(e){const t=e.detail;"number"!=typeof t||1!==t&&2!==t&&3!==t||t===this._listColumns||(this._listColumns=t,je(t))}_onGraphSettingsChanged(){if("dashboard"===this._activeTab){const e=this._root.getElementById("tab-content");if(e){this._dashboardTab._ctrl.onGraphSettingsChanged(e)}}}_onNavigateTab(e){const t=e.detail;t&&(this._activeTab=t)}_onFavoritesViewStateChangedEvent(e){if(!this._isFavoritesView)return;const t=e.detail;if(!t)return;const n=this._favoritesViewState;n.activeTab=t.view;const i=this._listDashCtrl.topology,s=i?.circuits;s&&Object.keys(s).length>0?n.expanded[t.view]=t.expanded.filter(e=>e in s):n.expanded[t.view]=t.expanded,n.searchQuery=t.searchQuery,this._persistFavoritesViewStateTimer&&clearTimeout(this._persistFavoritesViewStateTimer),this._persistFavoritesViewStateTimer=setTimeout(()=>{this._persistFavoritesViewStateTimer=null,mn(n)},250)}_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=this._panels.filter(e=>e.id!==yn),n=new Map(t.map(e=>[e.id,e])),i=new Set(e.map(e=>e.id)),s=n.size!==i.size||[...n.keys()].some(e=>!i.has(e)),o=!s&&e.some(e=>{const t=n.get(e.id);return!!t&&(t.name!==e.name||t.name_by_user!==e.name_by_user)});if((s||o)&&(this._panels=this._buildPanelList(e,this._favorites),!this._panels.some(e=>e.id===this._selectedPanelId)&&this._panels.length>0)){const t=e[0];t&&(this._selectedPanelId=t.id,localStorage.setItem("span_panel_selected",this._selectedPanelId))}}async _updatePanelStatusWatch(){if(!this.hass||!this._selectedPanelId)return;if(this._selectedPanelId===yn)return;if(this._watchedPanelId===this._selectedPanelId)return;const e=this._selectedPanelId;this._watchedPanelId=e;try{const t=new We(this._errorStore),n=await Ye(this.hass,e,t);if(this._selectedPanelId!==e)return;const i=n.topology?.panel_entities?.panel_status;i&&(this._errorStore.watchPanelStatus(i),this._errorStore.updateHass(this.hass))}catch(t){console.warn("SPAN Panel: unable to fetch topology for panel status watching",t),this._watchedPanelId===e&&(this._watchedPanelId=null)}}async _discoverPanels(){if(!this._discovering&&this.hass){this._discovering=!0;try{let e;try{const t=new We(this._errorStore);e=(await t.callWS(this.hass,{type:"config/device_registry/list"},{errorId:"fetch:topology"})).filter(e=>e.identifiers?.some(e=>e[0]===a)&&!e.via_device_id)}catch(e){return console.error("SPAN Panel: device discovery failed",e),void this._errorStore.add({key:"discovery-failed",level:"error",message:n("error.discovery_failed"),persistent:!0,retryFn:()=>{this._errorStore.remove("discovery-failed"),this._discoverPanels()}})}this._favorites=await this._loadFavorites(),this._panels=this._buildPanelList(e,this._favorites),this._favoritesViewState=function(){try{const e=localStorage.getItem(vn);if(!e)return{expanded:{activity:[],area:[]}};const t=JSON.parse(e);if(!t||"object"!=typeof t)return{expanded:{activity:[],area:[]}};const n=t.expanded??{activity:[],area:[]};return{activeTab:t.activeTab,expanded:{activity:Array.isArray(n.activity)?n.activity:[],area:Array.isArray(n.area)?n.area:[]},searchQuery:"string"==typeof t.searchQuery?t.searchQuery:void 0}}catch{return{expanded:{activity:[],area:[]}}}}(),this._discovered=!0;const t=localStorage.getItem("span_panel_selected");if(t&&this._panels.some(e=>e.id===t)?this._selectedPanelId=t:e.length>0&&(this._selectedPanelId=e[0].id),this._selectedPanelId===yn){const e=this._favoritesViewState.activeTab;"activity"===e||"area"===e||"monitoring"===e?this._activeTab=e:"dashboard"===this._activeTab&&(this._activeTab="activity")}this._chartMetric=localStorage.getItem("span_panel_metric")||"power"}finally{this._discovering=!1}}}_buildPanelList(e,t){if(!Qe(t))return e;return[{id:yn,name:n("panel.favorites"),model:"__favorites__"},...e]}async _loadFavorites(){return this.hass?this._favCache.fetch(this.hass):{}}async _refreshFavorites(){const e=++this._refreshSeq;this._favCache.invalidate();const t=await this._loadFavorites();if(e!==this._refreshSeq)return;const n=this._selectedPanelId===yn;this._favorites=t;const i=this._panels.filter(e=>e.id!==yn);if(this._panels=this._buildPanelList(i,t),n&&!Qe(t)){!function(){try{localStorage.removeItem(vn)}catch{}}(),this._favoritesViewState={expanded:{activity:[],area:[]}};const e=i[0];e?(this._selectedPanelId=e.id,localStorage.setItem("span_panel_selected",e.id)):this._selectedPanelId=null}else this._isFavoritesView?this._scheduleTabRender():this._applyPanelFavorites()}_buildTabList(){const e=[];return this._isFavoritesView||e.push({id:"dashboard",label:n("tab.by_panel"),icon:"mdi:view-dashboard"}),e.push({id:"activity",label:n("tab.by_activity"),icon:"mdi:sort-descending"},{id:"area",label:n("tab.by_area"),icon:"mdi:home-group"},{id:"monitoring",label:n("tab.monitoring"),icon:"mdi:monitor-eye"}),e}_buildFavoritesSummaryHTML(){return function(e){return`\n
\n \n
\n ${Oe(n("header.enable_switches"))}\n
\n \n
\n
\n
\n ${et()}\n
\n \n \n
\n
\n
\n `}("current"===(this._chartMetric||"power"))}_buildFavoritesPanelStatsGridHTML(e,t){if(0===e.length)return"";return`
${e.map(e=>`\n
\n
${Oe(e.panelName||e.topology.device_name||"")}
\n ${tt(e.topology,t,e.panelDeviceId)}\n
\n `).join("")}
`}_updateFavoritesPanelStats(e,t){if(this.hass&&0!==this._favoritesPanelStats.length)for(const n of this._favoritesPanelStats){const i=e.querySelector(`.panel-stats[data-stats-panel-id="${jt(n.panelDeviceId)}"]`);i&&Ut(i,this.hass,n.topology,t,0)}}_buildDashboardConfig(){return{chart_metric:this._chartMetric,history_minutes:5,show_panel:!0,show_battery:!0,show_evse:!0}}async _scheduleTabRender(){await this.updateComplete,this._sidePanelOpen()?this._pendingTabRender=!0:await this._tabRenderScheduler()}_sidePanelOpen(){const e=this.shadowRoot?.getElementById("tab-content");return!!e?.querySelector("span-side-panel[open]")}async _renderTab(){const e=this._beginRender();this._dashboardTab.stop(),this._monitoringTab.stop(),this._listCtrl.stop(),this._listDashCtrl.stopIntervals();for(const e of this._favoritesMonitoringTabs.values())e.stop();this._favoritesMonitoringTabs.clear(),this._favoritesPanelStats=[];const t=this._root.getElementById("tab-content");if(t)if(this._isFavoritesView)await this._renderFavoritesTab(t,e);else switch(this._listDashCtrl.clearFavoriteRefs(),this._listCtrl.setViewName(null),this._applyPanelFavorites(),this._activeTab){case"dashboard":{t.innerHTML="";const e=this._buildDashboardConfig(),n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;await this._dashboardTab.render(t,this.hass,this._selectedPanelId??"",e,i);break}case"activity":{t.innerHTML="";const n=this._panels.find(e=>e.id===this._selectedPanelId),i=n?.config_entries?.[0]??null;try{const n=new We(this._errorStore),s=await Ye(this.hass,this._selectedPanelId??void 0,n);if(e())return;const o=this._buildDashboardConfig();if(this._listDashCtrl.init(s.topology,o,this.hass,i),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.monitoringCache.fetch(this.hass,i),e())return;if(await this._listDashCtrl.fetchAndBuildHorizonMaps(),e())return;const r=s.topology?nt(s.topology,o):"";if(this._listCtrl.setColumns(this._listColumns),this._listCtrl.renderActivityView(t,this.hass,s.topology,o,this._listDashCtrl.monitoringCache.status,r),await this._listDashCtrl.loadHistory(),e())return;this._listDashCtrl.updateDOM(t),this._listDashCtrl.startIntervals(t)}catch(n){if(e())return;const i=document.createElement("p");i.style.color="var(--error-color)",i.textContent=n.message,t.appendChild(i)}break}case"area":{t.innerHTML="";const i=this._panels.find(e=>e.id===this._selectedPanelId),s=i?.config_entries?.[0]??null;try{const i=new We(this._errorStore),o=await Ye(this.hass,this._selectedPanelId??void 0,i);if(e())return;const r=this._buildDashboardConfig();if(this._listDashCtrl.init(o.topology,r,this.hass,s),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.monitoringCache.fetch(this.hass,s),e())return;if(await this._listDashCtrl.fetchAndBuildHorizonMaps(),e())return;const a=o.topology?nt(o.topology,r):"";if(this._listCtrl.setColumns(this._listColumns),this._listCtrl.renderAreaView(t,this.hass,o.topology,r,this._listDashCtrl.monitoringCache.status,a),await this._listDashCtrl.loadHistory(),e())return;this._listDashCtrl.updateDOM(t),this._listDashCtrl.startIntervals(t),this._areaUnsub||this._areaSubscribing||(this._areaSubscribing=!0,async function(e,t,i,s){if(!e.connection)return()=>{};const o=async()=>{try{const n=new Map;for(const[e,i]of Object.entries(t.circuits))n.set(e,i.area);await Ze(e,t);for(const[e,s]of Object.entries(t.circuits))if(s.area!==n.get(e))return void i()}catch(e){console.warn("[span-panel] area registry update failed:",e),s?.add({key:"fetch:areas",level:"warning",message:n("error.areas_failed"),persistent:!1})}},[r,a]=await Promise.all([e.connection.subscribeEvents(o,"entity_registry_updated"),e.connection.subscribeEvents(o,"area_registry_updated")]);return()=>{r(),a()}}(this.hass,o.topology,()=>{"area"===this._activeTab&&this._scheduleTabRender()},this._errorStore).then(e=>{this._areaSubscribing?this._areaUnsub=e:e()}).catch(e=>{this._areaSubscribing=!1,console.warn("SPAN Panel: area subscription failed",e),this._errorStore.add({key:"subscribe:area",level:"warning",message:n("error.areas_failed"),persistent:!1})}))}catch(e){const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=e instanceof Error?e.message:String(e),t.appendChild(n)}break}case"monitoring":{t.innerHTML="";const e=this._panels.find(e=>e.id===this._selectedPanelId),n=e?.config_entries?.[0]??null;await this._monitoringTab.render(t,this.hass,n??void 0);break}}}async _renderFavoritesTab(e,t){if(e.innerHTML="",!this.hass)return;const i=this._panels.filter(e=>e.id!==yn),s=await this._favCtrl.build(this.hass,this._favorites,i,this._errorStore);if(t())return;const o=s.perPanelStats.map(e=>{const t=e.topology.panel_entities?.panel_status;return"string"==typeof t?{entityId:t,panelName:e.panelName}:null}).filter(e=>null!==e);this._errorStore.watchPanelStatuses(o),this._errorStore.updateHass(this.hass);const r=new Map;for(const e of s.perPanelStats){const t=i.find(t=>t.id===e.panelDeviceId);r.set(e.panelDeviceId,{panelName:e.panelName,topology:e.topology,configEntryId:t?.config_entries?.[0]??null})}this._listDashCtrl.setFavoritesPerPanelInfo(r);const a=s.topology,l=s.entryIds[0]??null,c=Object.keys(a.circuits).length>0,d=Object.keys(a.sub_devices??{}).length>0;if(!c&&!d){const t=document.createElement("p");return t.style.color="var(--secondary-text-color)",t.style.padding="24px",t.textContent=n("list.no_results"),void e.appendChild(t)}if(this._listDashCtrl.setFavoriteRefs(a._favoriteRefs),this._listDashCtrl.setPanelFavorites(null),"monitoring"===this._activeTab)return this._listCtrl.setViewName(null),void await this._renderFavoritesMonitoring(e,s.entryIds,i);const h=this._activeTab,p=new Set(Object.keys(a.circuits)),u=this._favoritesViewState.expanded[h].filter(e=>p.has(e));this._listCtrl.setViewName(h),this._listCtrl.setInitialExpansion(u),this._listCtrl.setInitialSearchQuery(this._favoritesViewState.searchQuery??""),this._listCtrl.setColumns(this._listColumns);const g=this._buildDashboardConfig();if(this._listDashCtrl.init(a,g,this.hass,l),this._listDashCtrl.powerHistory.clear(),await this._listDashCtrl.fetchAndBuildHorizonMaps(),t())return;const _=await this._listDashCtrl.fetchMergedMonitoringStatus(s.entryIds);if(!t()){this._favoritesPanelStats=s.perPanelStats;try{if(await this._listDashCtrl.loadHistory(),t())return;const n=this._buildFavoritesSummaryHTML(),i=this._buildFavoritesPanelStatsGridHTML(s.perPanelStats,g),o=n+i+(d?`
\n
${Et(a,this.hass,g)}
\n
`:"");"activity"===h?this._listCtrl.renderActivityView(e,this.hass,a,g,_,o):this._listCtrl.renderAreaView(e,this.hass,a,g,_,o),this._updateFavoritesPanelStats(e,g),this._listDashCtrl.setupResizeObserver(e,e),this._listDashCtrl.startIntervals(e,()=>{this._updateFavoritesPanelStats(e,g)})}catch(n){if(t())return;const i=document.createElement("p");i.style.color="var(--error-color)",i.textContent=n.message,e.appendChild(i)}}}async _renderFavoritesMonitoring(e,t,n){if(!this.hass)return;const i=document.createElement("div");i.className="favorites-monitoring-stack",e.appendChild(i);const s=new Map;for(const e of n){const t=e.config_entries?.[0];t&&s.set(t,e)}const o=new Map;for(const e of t){const t=s.get(e),n=document.createElement("div");n.className="favorites-monitoring-block",n.style.marginBottom="24px";const r=document.createElement("h2");r.style.margin="8px 0 12px",r.style.fontSize="1em",r.textContent=t?.name_by_user??t?.name??e,n.appendChild(r);const a=document.createElement("div");n.appendChild(a),i.appendChild(n);const l=new sn;l.errorStore=this._errorStore,o.set(e,l);try{await l.render(a,this.hass,e)}catch(t){console.warn("SPAN Panel: favorites monitoring render failed",e,t);const n=document.createElement("p");n.style.color="var(--error-color)",n.textContent=t.message??String(t),a.appendChild(n)}}this._favoritesMonitoringTabs=o}_applyPanelFavorites(){if(!this._selectedPanelId||this._isFavoritesView)return this._listDashCtrl.setPanelFavorites(null),void this._dashboardTab.setPanelFavorites(null);const e=this._favorites[this._selectedPanelId],t={panelDeviceId:this._selectedPanelId,circuitUuids:new Set(e?.circuits??[]),subDeviceIds:new Set(e?.sub_devices??[])};this._listDashCtrl.setPanelFavorites(t),this._dashboardTab.setPanelFavorites(t)}};wn._shellStyles=C` :host { color: var(--primary-text-color); } @@ -109,6 +175,10 @@ const Ce={attribute:!0,type:String,converter:L,reflect:!1,hasChanged:H},Ee=(e=Ce margin: 0 0 0 24px; line-height: 20px; flex-grow: 1; + display: flex; + align-items: center; + gap: 16px; + min-width: 0; } .panel-selector select { color: inherit; @@ -125,10 +195,13 @@ const Ce={attribute:!0,type:String,converter:L,reflect:!1,hasChanged:H},Ee=(e=Ce 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; + overflow-x: auto; + scrollbar-width: none; + } + .panel-tabs::-webkit-scrollbar { + display: none; } .panel-tab { padding: 8px 20px; @@ -177,4 +250,4 @@ const Ce={attribute:!0,type:String,converter:L,reflect:!1,hasChanged:H},Ee=(e=Ce opacity: 1; border-bottom-color: var(--app-header-text-color, white); } - `,m([ke({attribute:!1})],Wt.prototype,"hass",void 0),m([ke({type:Boolean,reflect:!0})],Wt.prototype,"narrow",void 0),m([ze()],Wt.prototype,"_panels",void 0),m([ze()],Wt.prototype,"_selectedPanelId",void 0),m([ze()],Wt.prototype,"_activeTab",void 0),m([ze()],Wt.prototype,"_discovered",void 0),m([ze()],Wt.prototype,"_discoveryError",void 0),m([ze()],Wt.prototype,"_chartMetric",void 0),Wt=m([(e=>(t,n)=>{void 0!==n?n.addInitializer(()=>{customElements.define(e,t)}):customElements.define(e,t)})("span-panel")],Wt),console.warn("%c SPAN-PANEL %c v0.9.2 ","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;");let Bt=!1;const Vt=Wt.prototype.connectedCallback;Wt.prototype.connectedCallback=function(){Bt=!0,Vt.call(this)};const Qt=Wt.prototype.disconnectedCallback;Wt.prototype.disconnectedCallback=function(){Bt=!1,Qt.call(this)},document.addEventListener("visibilitychange",()=>{"visible"===document.visibilityState&&(Bt||window.location.pathname.includes("span-panel")&&setTimeout(()=>{Bt||location.reload()},200))}); + `,wn.styles=[bn._shellStyles,$(Kt)],m([Ne({attribute:!1})],wn.prototype,"hass",void 0),m([Ne({type:Boolean,reflect:!0})],wn.prototype,"narrow",void 0),m([Me()],wn.prototype,"_panels",void 0),m([Me()],wn.prototype,"_selectedPanelId",void 0),m([Me()],wn.prototype,"_activeTab",void 0),m([Me()],wn.prototype,"_discovered",void 0),m([Me()],wn.prototype,"_chartMetric",void 0),m([Me()],wn.prototype,"_listColumns",void 0),m([Me()],wn.prototype,"_favorites",void 0),wn=bn=m([Ee("span-panel")],wn),console.warn("%c SPAN-PANEL %c v0.9.4 ","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;");let xn=!1;const Sn=wn.prototype.connectedCallback;wn.prototype.connectedCallback=function(){xn=!0,Sn.call(this)};const $n=wn.prototype.disconnectedCallback;wn.prototype.disconnectedCallback=function(){xn=!1,$n.call(this)},document.addEventListener("visibilitychange",()=>{"visible"===document.visibilityState&&(xn||window.location.pathname.includes("span-panel")&&setTimeout(()=>{xn||location.reload()},200))}); diff --git a/custom_components/span_panel/grace_period.py b/custom_components/span_panel/grace_period.py index b4b5a906..aa499174 100644 --- a/custom_components/span_panel/grace_period.py +++ b/custom_components/span_panel/grace_period.py @@ -3,7 +3,8 @@ from __future__ import annotations from dataclasses import dataclass -from datetime import UTC, datetime, timedelta +from datetime import UTC, date, datetime, timedelta +from decimal import Decimal import logging from typing import Any, Self @@ -11,6 +12,11 @@ from homeassistant.core import State from homeassistant.helpers.restore_state import ExtraStoredData +# Matches SensorEntity.native_value: the broadest concrete union that the +# HA sensor platform can hand us. We accept it verbatim so sensor_base can +# pass `self._attr_native_value` through without extra narrowing. +type NativeSensorValue = str | int | float | date | Decimal | None + _LOGGER = logging.getLogger(__name__) @@ -96,7 +102,7 @@ def from_dict(cls, restored: dict[str, Any]) -> Self | None: return None -def coerce_grace_period_minutes(raw_value: Any) -> int: +def coerce_grace_period_minutes(raw_value: int | float | str | None) -> int: """Ensure grace period minutes is a non-negative integer. Args: @@ -106,6 +112,8 @@ def coerce_grace_period_minutes(raw_value: Any) -> int: Validated integer (defaults to 15 if invalid, clamps to 0 minimum). """ + if raw_value is None: + return 15 try: minutes = int(raw_value) except (TypeError, ValueError): @@ -120,9 +128,9 @@ def coerce_grace_period_minutes(raw_value: Any) -> int: def handle_offline_grace_period( last_valid_state: float | None, last_valid_changed: datetime | None, - current_native_value: Any, + current_native_value: NativeSensorValue, grace_minutes: int, -) -> tuple[Any, float | None, datetime | None]: +) -> tuple[NativeSensorValue, float | None, datetime | None]: """Handle grace period logic when panel is offline. Args: @@ -150,7 +158,11 @@ def handle_offline_grace_period( last_valid_changed = datetime.now(tz=UTC) try: - time_since_last_valid = datetime.now(tz=UTC) - last_valid_changed + raw_delta = datetime.now(tz=UTC) - last_valid_changed + # Clamp to zero to handle backward clock jumps (DST / NTP sync) that would + # otherwise produce a negative delta and silently extend the grace window + # indefinitely. + time_since_last_valid = max(raw_delta, timedelta(0)) grace_period_duration = timedelta(minutes=grace_minutes) except Exception as err: # noqa: BLE001 # pragma: no cover - defensive _LOGGER.debug("Grace period calculation failed: %s", err) diff --git a/custom_components/span_panel/graph_horizon.py b/custom_components/span_panel/graph_horizon.py index 7116ede2..5090ddc7 100644 --- a/custom_components/span_panel/graph_horizon.py +++ b/custom_components/span_panel/graph_horizon.py @@ -33,7 +33,7 @@ def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None: self._global_horizon: str = DEFAULT_GRAPH_HORIZON self._circuit_overrides: dict[str, str] = {} self._subdevice_overrides: dict[str, str] = {} - self._store: Store = Store( + self._store: Store[dict[str, Any]] = Store( hass, _STORAGE_VERSION, f"{_STORAGE_KEY_PREFIX}.{entry.entry_id}", diff --git a/custom_components/span_panel/helpers.py b/custom_components/span_panel/helpers.py index 017d5453..2620c82b 100644 --- a/custom_components/span_panel/helpers.py +++ b/custom_components/span_panel/helpers.py @@ -52,6 +52,55 @@ is_panel_level_sensor_key, ) +__all__ = [ + "ALL_SUFFIX_MAPPINGS", + "CIRCUIT_SUFFIX_MAPPING", + "PANEL_ENTITY_SUFFIX_MAPPING", + "PANEL_SUFFIX_MAPPING", + "async_create_span_notification", + "build_bess_unique_id", + "build_bess_unique_id_for_entry", + "build_binary_sensor_unique_id", + "build_binary_sensor_unique_id_for_entry", + "build_circuit_unique_id", + "build_evse_unique_id", + "build_evse_unique_id_for_entry", + "build_panel_unique_id", + "build_select_unique_id", + "build_select_unique_id_for_entry", + "build_switch_unique_id", + "build_switch_unique_id_for_entry", + "construct_binary_sensor_unique_id", + "construct_circuit_identifier_from_tabs", + "construct_circuit_unique_id", + "construct_circuit_unique_id_for_entry", + "construct_multi_circuit_entity_id", + "construct_panel_unique_id", + "construct_panel_unique_id_for_entry", + "construct_select_unique_id", + "construct_single_circuit_entity_id", + "construct_switch_unique_id", + "construct_synthetic_unique_id", + "construct_synthetic_unique_id_for_entry", + "construct_tabs_attribute", + "construct_unmapped_entity_id", + "construct_unmapped_friendly_name", + "construct_voltage_attribute", + "detect_capabilities", + "er", + "get_device_identifier_for_entry", + "get_panel_entity_suffix", + "get_suffix_from_sensor_key", + "get_unmapped_circuit_entity_id", + "get_user_friendly_suffix", + "has_bess", + "has_evse", + "has_power_flows", + "has_pv", + "is_panel_level_sensor_key", + "resolve_evse_display_suffix", +] + _LOGGER = logging.getLogger(__name__) diff --git a/custom_components/span_panel/icons.json b/custom_components/span_panel/icons.json index 7c410923..4a7e9445 100644 --- a/custom_components/span_panel/icons.json +++ b/custom_components/span_panel/icons.json @@ -115,9 +115,18 @@ "faulted": "mdi:alert-circle" } }, + "feedthrough_consumed_energy": { + "default": "mdi:transmission-tower-import" + }, + "feedthrough_net_energy": { + "default": "mdi:swap-horizontal-bold" + }, "feedthrough_power": { "default": "mdi:flash" }, + "feedthrough_produced_energy": { + "default": "mdi:transmission-tower-export" + }, "grid_forming_entity": { "default": "mdi:lightning-bolt-circle", "state": { @@ -143,6 +152,15 @@ "main_breaker_rating": { "default": "mdi:fuse" }, + "main_meter_consumed_energy": { + "default": "mdi:meter-electric" + }, + "main_meter_net_energy": { + "default": "mdi:swap-horizontal-bold" + }, + "main_meter_produced_energy": { + "default": "mdi:meter-electric-outline" + }, "main_relay_state": { "default": "mdi:electric-switch", "state": { diff --git a/custom_components/span_panel/id_builder.py b/custom_components/span_panel/id_builder.py index 57509984..b790883c 100644 --- a/custom_components/span_panel/id_builder.py +++ b/custom_components/span_panel/id_builder.py @@ -187,6 +187,52 @@ def get_panel_entity_suffix(description_key: str) -> str: return get_user_friendly_suffix(description_key) +def extract_circuit_uuid_from_unique_id(unique_id: str) -> str | None: + """Return the 32-char hex circuit UUID embedded in a SPAN entity unique_id. + + SPAN entity unique_ids follow ``span_{serial}_{circuit_uuid}_{suffix}``. + The circuit UUID is a 32-char lowercase hex segment. Skips ``parts[0]`` + (``span``) and ``parts[1]`` (the serial — never a circuit UUID) so a + serial that happens to be 32 hex chars cannot shadow the circuit id. + + Returns ``None`` for unique_ids with no circuit UUID segment (e.g. + panel-level sensors). + """ + if not unique_id: + return None + parts = unique_id.split("_") + for part in parts[2:]: + if len(part) == 32 and all(c in "0123456789abcdef" for c in part): + return part + return None + + +# --------------------------------------------------------------------------- +# build_*_unique_id — DO NOT "normalise" without a migration path. +# +# Historically, build_circuit_unique_id, build_panel_unique_id, and +# construct_synthetic_unique_id lower-case the serial; build_switch_unique_id, +# build_binary_sensor_unique_id, build_select_unique_id, build_bess_unique_id, +# and build_evse_unique_id do NOT. On panels with a mixed-case serial this +# means a single install has some unique_ids with the serial lower-cased and +# others with the serial preserved as-is. +# +# This is an inconsistency, but it is benign: HA's entity registry keys on +# string equality, not case-folded equality, so every deployed entity still +# matches itself on every restart. Unifying the casing would silently rewrite +# what these functions return and orphan every existing switch, binary_sensor, +# select, BESS, and EVSE entity on any live install whose serial contains +# upper-case characters. The registry would then recreate those entities under +# the new unique_ids with default names — breaking dashboards, automations, +# and statistics history. +# +# If you ever truly need to align these, do it together with a config-entry +# migration that walks the registry and renames stored unique_ids to match +# the new convention. Do not change a single one of these return strings in +# isolation. +# --------------------------------------------------------------------------- + + def build_circuit_unique_id(serial: str, circuit_id: str, description_key: str) -> str: """Build unique ID for circuit sensors using consistent pattern (pure function). diff --git a/custom_components/span_panel/manifest.json b/custom_components/span_panel/manifest.json index e1632f9e..ac5e6427 100644 --- a/custom_components/span_panel/manifest.json +++ b/custom_components/span_panel/manifest.json @@ -22,7 +22,7 @@ ], "quality_scale": "gold", "requirements": [ - "span-panel-api==2.5.4" + "span-panel-api==2.6.1" ], "version": "2.0.6", "zeroconf": [ diff --git a/custom_components/span_panel/select.py b/custom_components/span_panel/select.py index b115c91a..30f54042 100644 --- a/custom_components/span_panel/select.py +++ b/custom_components/span_panel/select.py @@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceNotFound from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.entity import EntityCategory # type: ignore[attr-defined] +from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from span_panel_api import SpanCircuitSnapshot, SpanPanelSnapshot from span_panel_api.exceptions import SpanPanelServerError diff --git a/custom_components/span_panel/sensor_definitions.py b/custom_components/span_panel/sensor_definitions.py index 3d3b3e7b..e94d3ac6 100644 --- a/custom_components/span_panel/sensor_definitions.py +++ b/custom_components/span_panel/sensor_definitions.py @@ -26,7 +26,7 @@ UnitOfEnergy, UnitOfPower, ) -from homeassistant.helpers.entity import EntityCategory # type: ignore[attr-defined] +from homeassistant.helpers.entity import EntityCategory from span_panel_api import ( SpanBatterySnapshot, SpanCircuitSnapshot, diff --git a/custom_components/span_panel/services.py b/custom_components/span_panel/services.py index 458beb4c..35699b25 100644 --- a/custom_components/span_panel/services.py +++ b/custom_components/span_panel/services.py @@ -8,13 +8,14 @@ from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant, ServiceCall, ServiceResponse, SupportsResponse from homeassistant.exceptions import ServiceValidationError -from homeassistant.helpers import entity_registry as er +from homeassistant.helpers import device_registry as dr, entity_registry as er import voluptuous as vol from .const import DEFAULT_GRAPH_HORIZON, DOMAIN, VALID_GRAPH_HORIZONS from .current_monitor import CurrentMonitor +from .frontend import FavoriteKind, async_get_favorites, async_set_favorite from .graph_horizon import GraphHorizonManager -from .id_builder import build_circuit_unique_id +from .id_builder import build_circuit_unique_id, extract_circuit_uuid_from_unique_id from .options import ( CONTINUOUS_THRESHOLD_PCT, COOLDOWN_DURATION_M, @@ -496,3 +497,145 @@ async def async_handle_get_graph_settings( schema=vol.Schema({vol.Optional("config_entry_id"): str}), supports_response=SupportsResponse.ONLY, ) + + +def _async_register_favorites_services(hass: HomeAssistant) -> None: + """Register cross-panel favorites services (domain-level). + + The public API takes ``entity_id`` — any sensor on a SPAN circuit or + sub-device — and resolves it server-side to the internal + ``(panel_device_id, kind, target_id)`` tuple used in storage. Circuit + UUIDs and HA device IDs are not part of the user-visible surface. + """ + + def _resolve_entity_to_favorite_target(entity_id: str) -> tuple[str, FavoriteKind, str]: + """Return ``(panel_device_id, kind, target_id)`` for a SPAN entity. + + ``kind`` is ``"circuits"`` or ``"sub_devices"``. For circuits, + ``target_id`` is the panel-local circuit uuid (extracted from the + entity's unique_id). For sub-devices, ``target_id`` is the HA + device id of the BESS/EVSE; the panel id walks up via ``via_device_id``. + + Failure paths use distinct translation keys so users see the + actual reason their pick was rejected. + """ + entity_reg = er.async_get(hass) + entry = entity_reg.async_get(entity_id) + if entry is None or entry.platform != DOMAIN: + raise ServiceValidationError( + f"Entity {entity_id} is not a SPAN Panel entity.", + translation_domain=DOMAIN, + translation_key="favorite_not_span_entity", + translation_placeholders={"entity_id": entity_id}, + ) + + if entry.device_id is None: + raise ServiceValidationError( + f"Entity {entity_id} is not attached to a device.", + translation_domain=DOMAIN, + translation_key="favorite_no_device", + translation_placeholders={"entity_id": entity_id}, + ) + + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get(entry.device_id) + if device_entry is None or not any( + domain == DOMAIN for domain, _ in device_entry.identifiers + ): + raise ServiceValidationError( + f"Entity {entity_id} does not belong to a SPAN Panel device.", + translation_domain=DOMAIN, + translation_key="favorite_not_span_entity", + translation_placeholders={"entity_id": entity_id}, + ) + + # Resolve the panel device id. Sub-devices register with + # via_device_id; main panels never do, so via_device_id presence is a + # reliable discriminator (BESS / EVSE today) and we walk up to the + # parent SPAN Panel here. + if device_entry.via_device_id is not None: + parent = device_registry.async_get(device_entry.via_device_id) + if parent is None or not any(domain == DOMAIN for domain, _ in parent.identifiers): + raise ServiceValidationError( + f"Sub-device {entity_id} has no SPAN Panel parent.", + translation_domain=DOMAIN, + translation_key="favorite_subdevice_no_span_parent", + translation_placeholders={"entity_id": entity_id}, + ) + panel_device_id = parent.id + else: + panel_device_id = device_entry.id + + # Circuit-favorite branch takes precedence over the sub-device branch. + # EVSE feed-circuit sensors are re-assigned to the EVSE sub-device via + # the device override in sensor.py, but their unique_id still encodes + # the underlying circuit. When the unique_id embeds a 32-char circuit + # UUID (``span_{serial}_{circuit_uuid}_{suffix}``), favorite the + # circuit keyed by the parent panel — not the sub-device it happens + # to be attached to. + circuit_uuid = ( + extract_circuit_uuid_from_unique_id(entry.unique_id) if entry.unique_id else None + ) + if circuit_uuid is not None: + return panel_device_id, "circuits", circuit_uuid + + # No circuit UUID — sub-device metadata sensor (BESS %, EVSE status, + # etc.). Favorite the sub-device itself. + if device_entry.via_device_id is not None: + return panel_device_id, "sub_devices", device_entry.id + + # Main-panel entity without a circuit UUID — not favoritable. + if not entry.unique_id: + raise ServiceValidationError( + f"Entity {entity_id} has no unique id to resolve.", + translation_domain=DOMAIN, + translation_key="favorite_no_unique_id", + translation_placeholders={"entity_id": entity_id}, + ) + raise ServiceValidationError( + f"Could not derive a favorite target from entity {entity_id}. " + "Pick a circuit sensor (current/power) or a sub-device sensor.", + translation_domain=DOMAIN, + translation_key="favorite_no_circuit_uuid", + translation_placeholders={"entity_id": entity_id}, + ) + + async def async_handle_get_favorites(_call: ServiceCall) -> ServiceResponse: + favorites = await async_get_favorites(hass) + return cast(ServiceResponse, {"favorites": favorites}) + + async def async_handle_add_favorite(call: ServiceCall) -> ServiceResponse: + entity_id = call.data["entity_id"] + panel_device_id, kind, target_id = _resolve_entity_to_favorite_target(entity_id) + favorites = await async_set_favorite(hass, panel_device_id, kind, target_id, True) + return cast(ServiceResponse, {"favorites": favorites}) + + async def async_handle_remove_favorite(call: ServiceCall) -> ServiceResponse: + entity_id = call.data["entity_id"] + panel_device_id, kind, target_id = _resolve_entity_to_favorite_target(entity_id) + favorites = await async_set_favorite(hass, panel_device_id, kind, target_id, False) + return cast(ServiceResponse, {"favorites": favorites}) + + _favorite_mutation_schema = vol.Schema({vol.Required("entity_id"): str}) + + hass.services.async_register( + DOMAIN, + "get_favorites", + async_handle_get_favorites, + schema=vol.Schema({}), + supports_response=SupportsResponse.ONLY, + ) + hass.services.async_register( + DOMAIN, + "add_favorite", + async_handle_add_favorite, + schema=_favorite_mutation_schema, + supports_response=SupportsResponse.OPTIONAL, + ) + hass.services.async_register( + DOMAIN, + "remove_favorite", + async_handle_remove_favorite, + schema=_favorite_mutation_schema, + supports_response=SupportsResponse.OPTIONAL, + ) diff --git a/custom_components/span_panel/services.yaml b/custom_components/span_panel/services.yaml index 65a068a4..530b9fcb 100644 --- a/custom_components/span_panel/services.yaml +++ b/custom_components/span_panel/services.yaml @@ -36,6 +36,10 @@ set_circuit_threshold: monitoring_enabled: selector: boolean: + config_entry_id: + selector: + config_entry: + integration: span_panel clear_circuit_threshold: fields: @@ -46,6 +50,10 @@ clear_circuit_threshold: domain: sensor integration: span_panel device_class: current + config_entry_id: + selector: + config_entry: + integration: span_panel set_mains_threshold: fields: @@ -83,6 +91,10 @@ set_mains_threshold: monitoring_enabled: selector: boolean: + config_entry_id: + selector: + config_entry: + integration: span_panel clear_mains_threshold: fields: @@ -93,8 +105,17 @@ clear_mains_threshold: domain: sensor integration: span_panel device_class: current + config_entry_id: + selector: + config_entry: + integration: span_panel get_monitoring_status: + fields: + config_entry_id: + selector: + config_entry: + integration: span_panel set_global_monitoring: fields: @@ -143,8 +164,17 @@ set_global_monitoring: - "active" - "time-sensitive" - "critical" + config_entry_id: + selector: + config_entry: + integration: span_panel test_notification: + fields: + config_entry_id: + selector: + config_entry: + integration: span_panel set_graph_time_horizon: fields: @@ -158,6 +188,10 @@ set_graph_time_horizon: - "1d" - "1w" - "1M" + config_entry_id: + selector: + config_entry: + integration: span_panel set_circuit_graph_horizon: fields: @@ -175,6 +209,10 @@ set_circuit_graph_horizon: - "1d" - "1w" - "1M" + config_entry_id: + selector: + config_entry: + integration: span_panel clear_circuit_graph_horizon: fields: @@ -182,6 +220,10 @@ clear_circuit_graph_horizon: required: true selector: text: + config_entry_id: + selector: + config_entry: + integration: span_panel set_subdevice_graph_horizon: fields: @@ -199,6 +241,10 @@ set_subdevice_graph_horizon: - "1d" - "1w" - "1M" + config_entry_id: + selector: + config_entry: + integration: span_panel clear_subdevice_graph_horizon: fields: @@ -206,5 +252,34 @@ clear_subdevice_graph_horizon: required: true selector: text: + config_entry_id: + selector: + config_entry: + integration: span_panel get_graph_settings: + fields: + config_entry_id: + selector: + config_entry: + integration: span_panel + +get_favorites: + +add_favorite: + fields: + entity_id: + required: true + selector: + entity: + domain: sensor + integration: span_panel + +remove_favorite: + fields: + entity_id: + required: true + selector: + entity: + domain: sensor + integration: span_panel diff --git a/custom_components/span_panel/strings.json b/custom_components/span_panel/strings.json index c8d70e19..e374babf 100644 --- a/custom_components/span_panel/strings.json +++ b/custom_components/span_panel/strings.json @@ -345,9 +345,30 @@ "export_manifest_no_entries": { "message": "No SPAN panel configuration entries are loaded. Add and configure a SPAN panel before calling this service." }, + "favorite_not_span_entity": { + "message": "Entity {entity_id} is not a SPAN Panel entity. Pick a circuit, BESS, or EVSE sensor." + }, + "favorite_no_device": { + "message": "Entity {entity_id} is not attached to a device. Pick a SPAN circuit or sub-device sensor." + }, + "favorite_subdevice_no_span_parent": { + "message": "Sub-device {entity_id} has no SPAN Panel parent device — its via_device_id does not point at a SPAN panel." + }, + "favorite_no_unique_id": { + "message": "Entity {entity_id} has no unique id and cannot be resolved to a SPAN target." + }, + "favorite_no_circuit_uuid": { + "message": "Could not derive a favorite target from entity {entity_id}. Pick a circuit sensor (current/power) or a sub-device sensor." + }, "gfe_override_failed": { "message": "Failed to send GFE override to the panel." }, + "graph_horizon_not_available": { + "message": "Graph horizon manager is not available for the selected SPAN panel." + }, + "monitoring_not_enabled": { + "message": "No SPAN panel with current monitoring enabled." + }, "panel_auth_failed": { "message": "Authentication with the SPAN Panel failed. Please reauthenticate." }, @@ -426,6 +447,10 @@ "monitoring_enabled": { "name": "Monitoring enabled", "description": "Enable or disable monitoring for this circuit." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -436,6 +461,10 @@ "circuit_id": { "name": "Circuit", "description": "The circuit power sensor entity." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -466,6 +495,10 @@ "monitoring_enabled": { "name": "Monitoring enabled", "description": "Enable or disable monitoring for this mains leg." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -476,17 +509,31 @@ "leg": { "name": "Mains leg", "description": "The mains current sensor entity." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, "get_monitoring_status": { "name": "Get monitoring status", - "description": "Returns current monitoring state for all tracked circuits and mains legs." + "description": "Returns current monitoring state for all tracked circuits and mains legs.", + "fields": { + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." + } + } }, "set_global_monitoring": { "name": "Set Global Monitoring", "description": "Update global monitoring thresholds and notification settings.", "fields": { + "enabled": { + "name": "Enabled", + "description": "Enable or disable current monitoring globally for this SPAN panel." + }, "continuous_threshold_pct": { "name": "Continuous Threshold", "description": "Percentage of breaker rating for continuous overload detection." @@ -518,12 +565,22 @@ "notification_priority": { "name": "Notification Priority", "description": "Push notification priority level. Controls interruption behavior on mobile devices (iOS: interruption-level, Android: priority/channel)." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, "test_notification": { "name": "Test Notification", - "description": "Send a test notification to all configured targets using sample values." + "description": "Send a test notification to all configured targets using sample values.", + "fields": { + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." + } + } }, "set_graph_time_horizon": { "name": "Set graph time horizon", @@ -532,6 +589,10 @@ "horizon": { "name": "Horizon", "description": "Time window preset for graph display." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -546,6 +607,10 @@ "horizon": { "name": "Horizon", "description": "Time window preset for graph display." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -556,6 +621,10 @@ "circuit_id": { "name": "Circuit ID", "description": "The circuit identifier to clear the override for." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -570,6 +639,10 @@ "horizon": { "name": "Horizon", "description": "Time window preset for graph display." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -580,12 +653,46 @@ "subdevice_id": { "name": "Sub-device ID", "description": "The sub-device identifier to clear the override for." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, "get_graph_settings": { "name": "Get graph settings", - "description": "Returns the current global and per-circuit graph horizon settings." + "description": "Returns the current global and per-circuit graph horizon settings.", + "fields": { + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." + } + } + }, + "get_favorites": { + "name": "Get favorites", + "description": "Returns the saved cross-panel favorites map keyed by SPAN panel device id." + }, + "add_favorite": { + "name": "Add favorite", + "description": "Mark a SPAN circuit, battery (BESS), or EV charger (EVSE) as a favorite so it appears in the dashboard's Favorites view.", + "fields": { + "entity_id": { + "name": "Entity", + "description": "Any sensor of the circuit or sub-device to favorite (current, power, SoC, etc.)." + } + } + }, + "remove_favorite": { + "name": "Remove favorite", + "description": "Remove a circuit or sub-device from the cross-panel Favorites view.", + "fields": { + "entity_id": { + "name": "Entity", + "description": "Any sensor of the circuit or sub-device to remove from favorites." + } + } } } } diff --git a/custom_components/span_panel/threshold_evaluator.py b/custom_components/span_panel/threshold_evaluator.py index 0289dd91..70c2509e 100644 --- a/custom_components/span_panel/threshold_evaluator.py +++ b/custom_components/span_panel/threshold_evaluator.py @@ -9,7 +9,7 @@ from dataclasses import dataclass from datetime import UTC, datetime, timedelta -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from .const import ( DEFAULT_CONTINUOUS_THRESHOLD_PCT, @@ -28,6 +28,16 @@ from .current_monitor import MonitoredPointState +# Monitoring settings / overrides hold only int, bool, and str values. +# Keyed by the named constants in options.py (`"continuous_threshold_pct"`, +# `"monitoring_enabled"`, …) rather than arbitrary strings; a TypedDict would +# express that more precisely but requires Literal-typed constants throughout +# the call chain, so we keep the lighter-weight alias and rely on the named +# keys for discoverability. +type MonitoringValue = int | bool | str +type MonitoringSettings = dict[str, MonitoringValue] + + @dataclass class AlertEvent: """Describes an alert to be dispatched.""" @@ -42,8 +52,8 @@ class AlertEvent: def resolve_thresholds( - override: dict[str, Any], - global_settings: dict[str, Any], + override: MonitoringSettings, + global_settings: MonitoringSettings, ) -> tuple[int, int, int, int]: """Return (continuous_pct, spike_pct, window_m, cooldown_m) for a monitored point. @@ -51,26 +61,34 @@ def resolve_thresholds( defaults when neither layer provides a value. """ return ( - override.get( - CONTINUOUS_THRESHOLD_PCT, - global_settings.get(CONTINUOUS_THRESHOLD_PCT, DEFAULT_CONTINUOUS_THRESHOLD_PCT), + int( + override.get( + CONTINUOUS_THRESHOLD_PCT, + global_settings.get(CONTINUOUS_THRESHOLD_PCT, DEFAULT_CONTINUOUS_THRESHOLD_PCT), + ) ), - override.get( - SPIKE_THRESHOLD_PCT, - global_settings.get(SPIKE_THRESHOLD_PCT, DEFAULT_SPIKE_THRESHOLD_PCT), + int( + override.get( + SPIKE_THRESHOLD_PCT, + global_settings.get(SPIKE_THRESHOLD_PCT, DEFAULT_SPIKE_THRESHOLD_PCT), + ) ), - override.get( - WINDOW_DURATION_M, - global_settings.get(WINDOW_DURATION_M, DEFAULT_WINDOW_DURATION_M), + int( + override.get( + WINDOW_DURATION_M, + global_settings.get(WINDOW_DURATION_M, DEFAULT_WINDOW_DURATION_M), + ) ), - override.get( - COOLDOWN_DURATION_M, - global_settings.get(COOLDOWN_DURATION_M, DEFAULT_COOLDOWN_DURATION_M), + int( + override.get( + COOLDOWN_DURATION_M, + global_settings.get(COOLDOWN_DURATION_M, DEFAULT_COOLDOWN_DURATION_M), + ) ), ) -def is_monitoring_disabled(override: dict[str, Any]) -> bool: +def is_monitoring_disabled(override: MonitoringSettings) -> bool: """Check if monitoring is disabled via per-point override.""" return override.get("monitoring_enabled") is False diff --git a/custom_components/span_panel/translations/en.json b/custom_components/span_panel/translations/en.json index 6c343ed5..040332a9 100644 --- a/custom_components/span_panel/translations/en.json +++ b/custom_components/span_panel/translations/en.json @@ -345,9 +345,30 @@ "export_manifest_no_entries": { "message": "No SPAN panel configuration entries are loaded. Add and configure a SPAN panel before calling this service." }, + "favorite_not_span_entity": { + "message": "Entity {entity_id} is not a SPAN Panel entity. Pick a circuit, BESS, or EVSE sensor." + }, + "favorite_no_device": { + "message": "Entity {entity_id} is not attached to a device. Pick a SPAN circuit or sub-device sensor." + }, + "favorite_subdevice_no_span_parent": { + "message": "Sub-device {entity_id} has no SPAN Panel parent device — its via_device_id does not point at a SPAN panel." + }, + "favorite_no_unique_id": { + "message": "Entity {entity_id} has no unique id and cannot be resolved to a SPAN target." + }, + "favorite_no_circuit_uuid": { + "message": "Could not derive a favorite target from entity {entity_id}. Pick a circuit sensor (current/power) or a sub-device sensor." + }, "gfe_override_failed": { "message": "Failed to send GFE override to the panel." }, + "graph_horizon_not_available": { + "message": "Graph horizon manager is not available for the selected SPAN panel." + }, + "monitoring_not_enabled": { + "message": "No SPAN panel with current monitoring enabled." + }, "panel_auth_failed": { "message": "Authentication with the SPAN Panel failed. Please reauthenticate." }, @@ -426,6 +447,10 @@ "monitoring_enabled": { "name": "Monitoring enabled", "description": "Enable or disable monitoring for this circuit." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -436,6 +461,10 @@ "circuit_id": { "name": "Circuit", "description": "The circuit power sensor entity." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -466,6 +495,10 @@ "monitoring_enabled": { "name": "Monitoring enabled", "description": "Enable or disable monitoring for this mains leg." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -476,17 +509,31 @@ "leg": { "name": "Mains leg", "description": "The mains current sensor entity." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, "get_monitoring_status": { "name": "Get monitoring status", - "description": "Returns current monitoring state for all tracked circuits and mains legs." + "description": "Returns current monitoring state for all tracked circuits and mains legs.", + "fields": { + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." + } + } }, "set_global_monitoring": { "name": "Set Global Monitoring", "description": "Update global monitoring thresholds and notification settings.", "fields": { + "enabled": { + "name": "Enabled", + "description": "Enable or disable current monitoring globally for this SPAN panel." + }, "continuous_threshold_pct": { "name": "Continuous Threshold", "description": "Percentage of breaker rating for continuous overload detection." @@ -518,12 +565,22 @@ "notification_priority": { "name": "Notification Priority", "description": "Push notification priority level. Controls interruption behavior on mobile devices (iOS: interruption-level, Android: priority/channel)." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, "test_notification": { "name": "Test Notification", - "description": "Send a test notification to all configured targets using sample values." + "description": "Send a test notification to all configured targets using sample values.", + "fields": { + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." + } + } }, "set_graph_time_horizon": { "name": "Set graph time horizon", @@ -532,6 +589,10 @@ "horizon": { "name": "Horizon", "description": "Time window preset for graph display." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -546,6 +607,10 @@ "horizon": { "name": "Horizon", "description": "Time window preset for graph display." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -556,6 +621,10 @@ "circuit_id": { "name": "Circuit ID", "description": "The circuit identifier to clear the override for." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -570,6 +639,10 @@ "horizon": { "name": "Horizon", "description": "Time window preset for graph display." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, @@ -580,12 +653,46 @@ "subdevice_id": { "name": "Sub-device ID", "description": "The sub-device identifier to clear the override for." + }, + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." } } }, "get_graph_settings": { "name": "Get graph settings", - "description": "Returns the current global and per-circuit graph horizon settings." + "description": "Returns the current global and per-circuit graph horizon settings.", + "fields": { + "config_entry_id": { + "name": "Config entry", + "description": "Optional config entry id. Only needed when more than one SPAN panel is configured." + } + } + }, + "get_favorites": { + "name": "Get favorites", + "description": "Returns the saved cross-panel favorites map keyed by SPAN panel device id." + }, + "add_favorite": { + "name": "Add favorite", + "description": "Mark a SPAN circuit, battery (BESS), or EV charger (EVSE) as a favorite so it appears in the dashboard's Favorites view.", + "fields": { + "entity_id": { + "name": "Entity", + "description": "Any sensor of the circuit or sub-device to favorite (current, power, SoC, etc.)." + } + } + }, + "remove_favorite": { + "name": "Remove favorite", + "description": "Remove a circuit or sub-device from the cross-panel Favorites view.", + "fields": { + "entity_id": { + "name": "Entity", + "description": "Any sensor of the circuit or sub-device to remove from favorites." + } + } } } } diff --git a/custom_components/span_panel/translations/es.json b/custom_components/span_panel/translations/es.json index 3b1168c8..3bb65e2e 100644 --- a/custom_components/span_panel/translations/es.json +++ b/custom_components/span_panel/translations/es.json @@ -345,9 +345,30 @@ "export_manifest_no_entries": { "message": "No hay entradas de configuración de SPAN Panel cargadas. Agregue y configure un SPAN Panel antes de llamar a este servicio." }, + "favorite_no_circuit_uuid": { + "message": "No se pudo derivar un objetivo favorito de la entidad {entity_id}. Seleccione un sensor de circuito (corriente/potencia) o un sensor de subdispositivo." + }, + "favorite_no_device": { + "message": "La entidad {entity_id} no está asociada a un dispositivo. Seleccione un sensor de circuito o subdispositivo SPAN." + }, + "favorite_no_unique_id": { + "message": "La entidad {entity_id} no tiene un id único y no se puede resolver a un objetivo SPAN." + }, + "favorite_not_span_entity": { + "message": "La entidad {entity_id} no es una entidad de SPAN Panel. Seleccione un sensor de circuito, batería (BESS) o cargador EV (EVSE)." + }, + "favorite_subdevice_no_span_parent": { + "message": "El subdispositivo {entity_id} no tiene un dispositivo SPAN Panel principal — su via_device_id no apunta a un panel SPAN." + }, "gfe_override_failed": { "message": "Error al enviar la anulación GFE al panel." }, + "graph_horizon_not_available": { + "message": "El gestor del horizonte del gráfico no está disponible para el SPAN Panel seleccionado." + }, + "monitoring_not_enabled": { + "message": "Ningún SPAN Panel tiene el monitoreo de corriente habilitado." + }, "panel_auth_failed": { "message": "La autenticación con el SPAN Panel falló. Por favor vuelva a autenticarse." }, @@ -426,6 +447,10 @@ "monitoring_enabled": { "name": "Monitoreo habilitado", "description": "Habilitar o deshabilitar el monitoreo para este circuito." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -436,6 +461,10 @@ "circuit_id": { "name": "Circuito", "description": "La entidad del sensor de potencia del circuito." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -466,6 +495,10 @@ "monitoring_enabled": { "name": "Monitoreo habilitado", "description": "Habilitar o deshabilitar el monitoreo para esta fase de alimentación principal." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -476,17 +509,31 @@ "leg": { "name": "Fase de alimentación principal", "description": "La entidad del sensor de corriente de alimentación principal." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, "get_monitoring_status": { "name": "Obtener estado de monitoreo", - "description": "Devuelve el estado actual de monitoreo para todos los circuitos y fases de alimentación principal rastreados." + "description": "Devuelve el estado actual de monitoreo para todos los circuitos y fases de alimentación principal rastreados.", + "fields": { + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." + } + } }, "set_global_monitoring": { "name": "Configurar monitoreo global", "description": "Actualizar los umbrales de monitoreo global y la configuración de notificaciones.", "fields": { + "enabled": { + "name": "Habilitado", + "description": "Habilitar o deshabilitar el monitoreo de corriente globalmente para este SPAN Panel." + }, "continuous_threshold_pct": { "name": "Umbral continuo", "description": "Porcentaje de la capacidad del interruptor para la detección de sobrecarga continua." @@ -518,12 +565,22 @@ "notification_priority": { "name": "Prioridad de notificación", "description": "Nivel de prioridad de la notificación push. Controla el comportamiento de interrupción en dispositivos móviles (iOS: interruption-level, Android: priority/canal)." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, "test_notification": { "name": "Notificación de prueba", - "description": "Enviar una notificación de prueba a todos los destinos configurados con valores de ejemplo." + "description": "Enviar una notificación de prueba a todos los destinos configurados con valores de ejemplo.", + "fields": { + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." + } + } }, "set_graph_time_horizon": { "name": "Establecer horizonte temporal del gráfico", @@ -532,6 +589,10 @@ "horizon": { "name": "Horizonte", "description": "Ventana de tiempo preestablecida para la visualización del gráfico." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -546,6 +607,10 @@ "horizon": { "name": "Horizonte", "description": "Ventana de tiempo preestablecida para la visualización del gráfico." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -556,6 +621,10 @@ "circuit_id": { "name": "ID del circuito", "description": "El identificador del circuito para borrar la anulación." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -570,6 +639,10 @@ "horizon": { "name": "Horizonte", "description": "Ventana de tiempo preestablecida para la visualización del gráfico." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, @@ -580,12 +653,46 @@ "subdevice_id": { "name": "ID del subdispositivo", "description": "El identificador del subdispositivo para borrar la anulación." + }, + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." } } }, "get_graph_settings": { "name": "Obtener configuración de gráficos", - "description": "Devuelve la configuración actual de horizonte de gráficos global y por circuito." + "description": "Devuelve la configuración actual de horizonte de gráficos global y por circuito.", + "fields": { + "config_entry_id": { + "name": "Entrada de configuración", + "description": "Id de entrada de configuración opcional. Solo necesario cuando hay más de un SPAN Panel configurado." + } + } + }, + "get_favorites": { + "name": "Obtener favoritos", + "description": "Devuelve el mapa de favoritos entre paneles, indexado por id de dispositivo del panel SPAN." + }, + "add_favorite": { + "name": "Añadir favorito", + "description": "Marca un circuito SPAN, batería (BESS) o cargador EV (EVSE) como favorito para que aparezca en la vista de Favoritos del dashboard.", + "fields": { + "entity_id": { + "name": "Entidad", + "description": "Cualquier sensor del circuito o subdispositivo a marcar como favorito (corriente, potencia, SoC, etc.)." + } + } + }, + "remove_favorite": { + "name": "Eliminar favorito", + "description": "Elimina un circuito o subdispositivo de la vista de Favoritos entre paneles.", + "fields": { + "entity_id": { + "name": "Entidad", + "description": "Cualquier sensor del circuito o subdispositivo a eliminar de favoritos." + } + } } } } diff --git a/custom_components/span_panel/translations/fr.json b/custom_components/span_panel/translations/fr.json index 0ed744d7..712bef06 100644 --- a/custom_components/span_panel/translations/fr.json +++ b/custom_components/span_panel/translations/fr.json @@ -345,9 +345,30 @@ "export_manifest_no_entries": { "message": "Aucune entrée de configuration SPAN Panel n'est chargée. Ajoutez et configurez un panneau SPAN avant d'appeler ce service." }, + "favorite_no_circuit_uuid": { + "message": "Impossible de dériver une cible favorite à partir de l'entité {entity_id}. Sélectionnez un capteur de circuit (courant/puissance) ou un capteur de sous-appareil." + }, + "favorite_no_device": { + "message": "L'entité {entity_id} n'est associée à aucun appareil. Sélectionnez un capteur de circuit ou de sous-appareil SPAN." + }, + "favorite_no_unique_id": { + "message": "L'entité {entity_id} n'a pas d'identifiant unique et ne peut pas être résolue vers une cible SPAN." + }, + "favorite_not_span_entity": { + "message": "L'entité {entity_id} n'est pas une entité SPAN Panel. Sélectionnez un capteur de circuit, de batterie (BESS) ou de chargeur EV (EVSE)." + }, + "favorite_subdevice_no_span_parent": { + "message": "Le sous-appareil {entity_id} n'a pas de panneau SPAN parent — son via_device_id ne pointe pas vers un panneau SPAN." + }, "gfe_override_failed": { "message": "Échec de l'envoi du forçage GFE au panneau." }, + "graph_horizon_not_available": { + "message": "Le gestionnaire d'horizon de graphique n'est pas disponible pour le panneau SPAN sélectionné." + }, + "monitoring_not_enabled": { + "message": "Aucun panneau SPAN n'a la surveillance de courant activée." + }, "panel_auth_failed": { "message": "L'authentification avec le SPAN Panel a échoué. Veuillez vous réauthentifier." }, @@ -426,6 +447,10 @@ "monitoring_enabled": { "name": "Surveillance activée", "description": "Activer ou désactiver la surveillance pour ce circuit." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -436,6 +461,10 @@ "circuit_id": { "name": "Circuit", "description": "L'entité capteur de puissance du circuit." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -466,6 +495,10 @@ "monitoring_enabled": { "name": "Surveillance activée", "description": "Activer ou désactiver la surveillance pour cette phase du réseau." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -476,17 +509,31 @@ "leg": { "name": "Phase du réseau", "description": "L'entité capteur de courant du réseau principal." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, "get_monitoring_status": { "name": "Obtenir l'état de surveillance", - "description": "Retourne l'état actuel de surveillance pour tous les circuits et phases du réseau suivis." + "description": "Retourne l'état actuel de surveillance pour tous les circuits et phases du réseau suivis.", + "fields": { + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." + } + } }, "set_global_monitoring": { "name": "Configurer la surveillance globale", "description": "Mettre à jour les seuils de surveillance globale et les paramètres de notification.", "fields": { + "enabled": { + "name": "Activé", + "description": "Activer ou désactiver la surveillance du courant globalement pour ce panneau SPAN." + }, "continuous_threshold_pct": { "name": "Seuil continu", "description": "Pourcentage de la capacité du disjoncteur pour la détection de surcharge continue." @@ -518,12 +565,22 @@ "notification_priority": { "name": "Priorité de notification", "description": "Niveau de priorité de la notification push. Contrôle le comportement d'interruption sur les appareils mobiles (iOS : interruption-level, Android : priority/canal)." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, "test_notification": { "name": "Notification de test", - "description": "Envoyer une notification de test à toutes les cibles configurées avec des valeurs d'exemple." + "description": "Envoyer une notification de test à toutes les cibles configurées avec des valeurs d'exemple.", + "fields": { + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." + } + } }, "set_graph_time_horizon": { "name": "Définir l'horizon temporel du graphique", @@ -532,6 +589,10 @@ "horizon": { "name": "Horizon", "description": "Fenêtre temporelle prédéfinie pour l'affichage du graphique." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -546,6 +607,10 @@ "horizon": { "name": "Horizon", "description": "Fenêtre temporelle prédéfinie pour l'affichage du graphique." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -556,6 +621,10 @@ "circuit_id": { "name": "ID du circuit", "description": "L'identifiant du circuit pour lequel effacer le remplacement." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -570,6 +639,10 @@ "horizon": { "name": "Horizon", "description": "Fenêtre temporelle prédéfinie pour l'affichage du graphique." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, @@ -580,12 +653,46 @@ "subdevice_id": { "name": "ID du sous-appareil", "description": "L'identifiant du sous-appareil pour lequel effacer le remplacement." + }, + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." } } }, "get_graph_settings": { "name": "Obtenir les paramètres de graphique", - "description": "Retourne les paramètres actuels d'horizon de graphique globaux et par circuit." + "description": "Retourne les paramètres actuels d'horizon de graphique globaux et par circuit.", + "fields": { + "config_entry_id": { + "name": "Entrée de configuration", + "description": "Id d'entrée de configuration optionnel. Nécessaire uniquement si plusieurs panneaux SPAN sont configurés." + } + } + }, + "get_favorites": { + "name": "Obtenir les favoris", + "description": "Retourne la carte des favoris inter-panneaux enregistrés pour les circuits et les sous-appareils, indexée par identifiant d'appareil de panneau SPAN." + }, + "add_favorite": { + "name": "Ajouter un favori", + "description": "Marque un circuit SPAN, une batterie (BESS) ou un chargeur EV (EVSE) comme favori pour qu'il apparaisse dans la vue Favoris du tableau de bord.", + "fields": { + "entity_id": { + "name": "Entité", + "description": "N'importe quel capteur du circuit ou du sous-appareil à marquer comme favori (courant, puissance, SoC, etc.)." + } + } + }, + "remove_favorite": { + "name": "Retirer un favori", + "description": "Retire un circuit ou un sous-appareil de la vue Favoris inter-panneaux.", + "fields": { + "entity_id": { + "name": "Entité", + "description": "N'importe quel capteur du circuit ou du sous-appareil à retirer des favoris." + } + } } } } diff --git a/custom_components/span_panel/translations/ja.json b/custom_components/span_panel/translations/ja.json index 2f4bc080..8879fc0e 100644 --- a/custom_components/span_panel/translations/ja.json +++ b/custom_components/span_panel/translations/ja.json @@ -345,9 +345,30 @@ "export_manifest_no_entries": { "message": "SPANパネルの設定エントリが読み込まれていません。このサービスを呼び出す前に、SPANパネルを追加して設定してください。" }, + "favorite_no_circuit_uuid": { + "message": "エンティティ {entity_id} からお気に入り対象を導出できませんでした。回路センサー (電流/電力) またはサブデバイスのセンサーを選択してください。" + }, + "favorite_no_device": { + "message": "エンティティ {entity_id} はデバイスに紐付いていません。SPANの回路またはサブデバイスのセンサーを選択してください。" + }, + "favorite_no_unique_id": { + "message": "エンティティ {entity_id} は unique_id を持たず、SPANの対象に解決できません。" + }, + "favorite_not_span_entity": { + "message": "エンティティ {entity_id} はSPAN Panelのエンティティではありません。回路、バッテリー (BESS)、またはEV充電器 (EVSE) のセンサーを選択してください。" + }, + "favorite_subdevice_no_span_parent": { + "message": "サブデバイス {entity_id} に親のSPAN Panelデバイスがありません。via_device_id がSPANパネルを指していません。" + }, "gfe_override_failed": { "message": "パネルへのGFEオーバーライドの送信に失敗しました。" }, + "graph_horizon_not_available": { + "message": "選択されたSPANパネルではグラフホライズンマネージャーが利用できません。" + }, + "monitoring_not_enabled": { + "message": "電流監視が有効なSPANパネルがありません。" + }, "panel_auth_failed": { "message": "SPAN Panelとの認証に失敗しました。再認証してください。" }, @@ -426,6 +447,10 @@ "monitoring_enabled": { "name": "モニタリング有効", "description": "この回路のモニタリングを有効または無効にします。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -436,6 +461,10 @@ "circuit_id": { "name": "回路", "description": "回路電力センサーエンティティ。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -466,6 +495,10 @@ "monitoring_enabled": { "name": "モニタリング有効", "description": "この主幹レグのモニタリングを有効または無効にします。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -476,17 +509,31 @@ "leg": { "name": "主幹レグ", "description": "主幹電流センサーエンティティ。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, "get_monitoring_status": { "name": "モニタリングステータスの取得", - "description": "すべての追跡対象回路と主幹レグの現在のモニタリング状態を返します。" + "description": "すべての追跡対象回路と主幹レグの現在のモニタリング状態を返します。", + "fields": { + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" + } + } }, "set_global_monitoring": { "name": "グローバル監視の設定", "description": "グローバル監視のしきい値と通知設定を更新します。", "fields": { + "enabled": { + "name": "有効", + "description": "このSPANパネルの電流監視をグローバルに有効または無効にします。" + }, "continuous_threshold_pct": { "name": "連続しきい値", "description": "連続過負荷検出のためのブレーカー定格に対する割合。" @@ -518,12 +565,22 @@ "notification_priority": { "name": "通知優先度", "description": "プッシュ通知の優先度レベル。モバイルデバイスでの割り込み動作を制御します(iOS: interruption-level、Android: priority/チャンネル)。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, "test_notification": { "name": "テスト通知", - "description": "サンプル値を使用して設定されたすべてのターゲットにテスト通知を送信します。" + "description": "サンプル値を使用して設定されたすべてのターゲットにテスト通知を送信します。", + "fields": { + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" + } + } }, "set_graph_time_horizon": { "name": "グラフの時間範囲を設定", @@ -532,6 +589,10 @@ "horizon": { "name": "時間範囲", "description": "グラフ表示用の時間ウィンドウプリセット。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -546,6 +607,10 @@ "horizon": { "name": "時間範囲", "description": "グラフ表示用の時間ウィンドウプリセット。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -556,6 +621,10 @@ "circuit_id": { "name": "回路ID", "description": "オーバーライドをクリアする回路の識別子。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -570,6 +639,10 @@ "horizon": { "name": "時間範囲", "description": "グラフ表示用の時間ウィンドウプリセット。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, @@ -580,12 +653,46 @@ "subdevice_id": { "name": "サブデバイスID", "description": "オーバーライドをクリアするサブデバイスの識別子。" + }, + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" } } }, "get_graph_settings": { "name": "グラフ設定を取得", - "description": "現在のグローバルおよび回路別のグラフ時間範囲設定を返します。" + "description": "現在のグローバルおよび回路別のグラフ時間範囲設定を返します。", + "fields": { + "config_entry_id": { + "name": "設定エントリ", + "description": "オプションの設定エントリID。複数のSPANパネルが設定されている場合にのみ必要です。" + } + } + }, + "get_favorites": { + "name": "お気に入りを取得", + "description": "SPANパネルのデバイスIDをキーとした、パネル横断のお気に入りマップを返します。" + }, + "add_favorite": { + "name": "お気に入りを追加", + "description": "SPANの回路、バッテリー (BESS)、またはEV充電器 (EVSE) をお気に入りとしてマークし、ダッシュボードのお気に入りビューに表示します。", + "fields": { + "entity_id": { + "name": "エンティティ", + "description": "お気に入りに追加する回路またはサブデバイスの任意のセンサー (電流、電力、SoCなど)。" + } + } + }, + "remove_favorite": { + "name": "お気に入りを削除", + "description": "パネル横断のお気に入りビューから回路またはサブデバイスを削除します。", + "fields": { + "entity_id": { + "name": "エンティティ", + "description": "お気に入りから削除する回路またはサブデバイスの任意のセンサー。" + } + } } } } diff --git a/custom_components/span_panel/translations/pt.json b/custom_components/span_panel/translations/pt.json index 417c6a29..3898d7f0 100644 --- a/custom_components/span_panel/translations/pt.json +++ b/custom_components/span_panel/translations/pt.json @@ -345,9 +345,30 @@ "export_manifest_no_entries": { "message": "Nenhuma entrada de configuração do painel SPAN está carregada. Adicione e configure um painel SPAN antes de chamar este serviço." }, + "favorite_no_circuit_uuid": { + "message": "Não foi possível derivar um destino favorito a partir da entidade {entity_id}. Selecione um sensor de circuito (corrente/potência) ou um sensor de sub-dispositivo." + }, + "favorite_no_device": { + "message": "A entidade {entity_id} não está associada a um dispositivo. Selecione um sensor de circuito ou sub-dispositivo SPAN." + }, + "favorite_no_unique_id": { + "message": "A entidade {entity_id} não tem um id único e não pode ser resolvida para um destino SPAN." + }, + "favorite_not_span_entity": { + "message": "A entidade {entity_id} não é uma entidade do SPAN Panel. Selecione um sensor de circuito, bateria (BESS) ou carregador EV (EVSE)." + }, + "favorite_subdevice_no_span_parent": { + "message": "O sub-dispositivo {entity_id} não tem um painel SPAN pai — o seu via_device_id não aponta para um painel SPAN." + }, "gfe_override_failed": { "message": "Falha ao enviar substituição GFE para o painel." }, + "graph_horizon_not_available": { + "message": "O gerenciador de horizonte do gráfico não está disponível para o painel SPAN selecionado." + }, + "monitoring_not_enabled": { + "message": "Nenhum painel SPAN com monitoramento de corrente ativado." + }, "panel_auth_failed": { "message": "A autenticação com o SPAN Panel falhou. Por favor, reautentique." }, @@ -426,6 +447,10 @@ "monitoring_enabled": { "name": "Monitorização ativada", "description": "Ativar ou desativar a monitorização para este circuito." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -436,6 +461,10 @@ "circuit_id": { "name": "Circuito", "description": "A entidade do sensor de potência do circuito." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -466,6 +495,10 @@ "monitoring_enabled": { "name": "Monitorização ativada", "description": "Ativar ou desativar a monitorização para esta fase da alimentação principal." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -476,17 +509,31 @@ "leg": { "name": "Fase da alimentação principal", "description": "A entidade do sensor de corrente da alimentação principal." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, "get_monitoring_status": { "name": "Obter estado da monitorização", - "description": "Retorna o estado atual de monitorização para todos os circuitos e fases da alimentação principal monitorizados." + "description": "Retorna o estado atual de monitorização para todos os circuitos e fases da alimentação principal monitorizados.", + "fields": { + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." + } + } }, "set_global_monitoring": { "name": "Configurar monitorização global", "description": "Atualizar os limiares de monitorização global e as definições de notificação.", "fields": { + "enabled": { + "name": "Ativado", + "description": "Ativar ou desativar o monitoramento de corrente globalmente para este painel SPAN." + }, "continuous_threshold_pct": { "name": "Limiar contínuo", "description": "Percentagem da capacidade do disjuntor para deteção de sobrecarga contínua." @@ -518,12 +565,22 @@ "notification_priority": { "name": "Prioridade de notificação", "description": "Nível de prioridade da notificação push. Controla o comportamento de interrupção em dispositivos móveis (iOS: interruption-level, Android: priority/canal)." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, "test_notification": { "name": "Notificação de teste", - "description": "Enviar uma notificação de teste para todos os destinos configurados com valores de exemplo." + "description": "Enviar uma notificação de teste para todos os destinos configurados com valores de exemplo.", + "fields": { + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." + } + } }, "set_graph_time_horizon": { "name": "Definir horizonte temporal do gráfico", @@ -532,6 +589,10 @@ "horizon": { "name": "Horizonte", "description": "Janela temporal predefinida para a exibição do gráfico." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -546,6 +607,10 @@ "horizon": { "name": "Horizonte", "description": "Janela temporal predefinida para a exibição do gráfico." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -556,6 +621,10 @@ "circuit_id": { "name": "ID do circuito", "description": "O identificador do circuito para limpar a substituição." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -570,6 +639,10 @@ "horizon": { "name": "Horizonte", "description": "Janela temporal predefinida para a exibição do gráfico." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, @@ -580,12 +653,46 @@ "subdevice_id": { "name": "ID do subdispositivo", "description": "O identificador do subdispositivo para limpar a substituição." + }, + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." } } }, "get_graph_settings": { "name": "Obter definições de gráfico", - "description": "Retorna as definições atuais de horizonte de gráfico globais e por circuito." + "description": "Retorna as definições atuais de horizonte de gráfico globais e por circuito.", + "fields": { + "config_entry_id": { + "name": "Entrada de configuração", + "description": "Id de entrada de configuração opcional. Necessário apenas quando há mais de um painel SPAN configurado." + } + } + }, + "get_favorites": { + "name": "Obter favoritos", + "description": "Retorna o mapa de favoritos entre painéis, indexado pelo id de dispositivo do painel SPAN." + }, + "add_favorite": { + "name": "Adicionar favorito", + "description": "Marca um circuito SPAN, bateria (BESS) ou carregador EV (EVSE) como favorito para que apareça na vista de Favoritos do painel de controlo.", + "fields": { + "entity_id": { + "name": "Entidade", + "description": "Qualquer sensor do circuito ou sub-dispositivo a marcar como favorito (corrente, potência, SoC, etc.)." + } + } + }, + "remove_favorite": { + "name": "Remover favorito", + "description": "Remove um circuito ou sub-dispositivo da vista de Favoritos entre painéis.", + "fields": { + "entity_id": { + "name": "Entidade", + "description": "Qualquer sensor do circuito ou sub-dispositivo a remover dos favoritos." + } + } } } } diff --git a/custom_components/span_panel/websocket.py b/custom_components/span_panel/websocket.py index 791d6715..aae65dbb 100644 --- a/custom_components/span_panel/websocket.py +++ b/custom_components/span_panel/websocket.py @@ -12,6 +12,7 @@ from .const import DOMAIN from .helpers import build_panel_unique_id +from .id_builder import build_binary_sensor_unique_id if TYPE_CHECKING: from . import SpanPanelRuntimeData @@ -134,6 +135,10 @@ async def handle_panel_topology( # Resolve panel-level sensor entity_ids via unique_id registry lookup. panel_entities = _build_panel_entity_map(snapshot.serial_number, entity_registry) + panel_status_entity = _resolve_panel_status_entity(snapshot.serial_number, entity_registry) + if panel_status_entity is not None: + panel_entities["panel_status"] = panel_status_entity + # Single pass over all entities to build circuit_id to role to entity_id # map. EVSE feed circuit sensors live on the EVSE sub-device, so we # search all entities for the config entry, not just panel-device ones. @@ -298,6 +303,20 @@ def _build_panel_entity_map( return result +def _resolve_panel_status_entity( + serial: str, + entity_registry: er.EntityRegistry, +) -> str | None: + """Resolve the panel_status binary sensor entity_id for the frontend. + + The frontend watches this entity to detect panel online/offline state. + The binary sensor is always available (see binary_sensor.py) so the + frontend can rely on its state regardless of coordinator offline status. + """ + unique_id = build_binary_sensor_unique_id(serial, "panel_status") + return entity_registry.async_get_entity_id("binary_sensor", DOMAIN, unique_id) + + def _classify_sensor_role(unique_id: str) -> str | None: """Classify a sensor's role from its unique_id suffix.""" for suffix, role in _SENSOR_ROLE_SUFFIXES.items(): diff --git a/frontend.md b/frontend.md index dc13b00f..25788d71 100644 --- a/frontend.md +++ b/frontend.md @@ -29,6 +29,28 @@ Use the **Enable Switches** toggle in the banner to globally enable or disable c ![Panel Dashboard](images/frontend.png) +## List Views — By Activity and By Area + +Beyond the panel grid, each panel (and the cross-panel Favorites view) exposes two list-oriented tabs: + +- **By Activity** — circuits sorted by live power (or current, when the unit toggle is on Amps), highest first. A search box filters rows by name. +- **By Area** — circuits grouped by their Home Assistant area (entity-level assignment first, then device-level fallback). Unassigned circuits land in a + dedicated group at the bottom. + +Each row shows breaker rating, live utilization %, circuit name, shedding-priority icon, an ON/OFF toggle (tappable once the **Enable Switches** slider is +armed), live power / current value, a gear icon that opens the side panel, and a chevron to expand the row's chart. + +### List view columns + +By default the list views stack circuits one per row. You can switch to a **2** or **3** column grid from the **Graph Settings** side panel (gear icon at the +top of the panel header) → **List View Columns**. + +- Rows flow left-to-right, top-to-bottom, so the existing sort order (value-desc for By Activity, alphabetical-within-area for By Area) becomes top-left through + bottom-right. +- Expanding a row shows its chart directly below, constrained to the same column as the row. +- Viewports narrower than 600 px force single-column regardless of the setting. +- The choice is stored per browser in `localStorage`, so you can keep a phone in single-column and a desktop in three columns. + ## Monitoring View The Monitoring tab provides current-based alerting for individual circuits. It detects sustained high utilization and transient spikes relative to each @@ -71,6 +93,53 @@ values. ![Monitoring Configuration](images/monitoring.png) -## Settings View +## Favorites View + +The dashboard supports a cross-panel **Favorites** view that lets you curate a single workspace from circuits and sub-devices (BESS, EVSE) belonging to any of +your configured SPAN panels. This is useful when you want a single place to keep an eye on a small set of important loads — say, the EV charger on Panel A and +the heat pump on Panel B — without switching panels in the dropdown. + +### Marking favorites + +Favorites are marked from the **side panels** that open via the gear icons in the dashboard. The standalone span-card (used in Lovelace dashboards) does not +expose hearts because it has no Favorites view. + +There are three places to toggle a favorite: + +- **Panel-level "Graph Settings" side panel** — opened from the gear icon at the top of the panel header. The per-circuit and per-sub-device lists each render a + heart icon next to the time-horizon dropdown. Click the heart to favorite or un-favorite that target without leaving the list. +- **Per-circuit side panel** — opened from a circuit's gear icon in the breaker grid (or in the By Activity / By Area rows). A "Favorite" section with a switch + sits between the relay control and the shedding priority. +- **Per-sub-device side panel** — opened from the gear icon on a BESS or EVSE tile. Same Favorite section as the per-circuit panel. + +A circuit or sub-device can be favorited on any panel. The integration stores the favorites under the configured SPAN integration's storage, so they sync across +browsers and devices. + +### The Favorites entry + +When you have at least one favorite configured, a synthetic **Favorites** entry appears at the top of the panel dropdown. Switching to it loads the aggregated +view. Removing the last favorite while the Favorites view is active automatically switches the dropdown and view back to the first real panel. + +### Tabs in the Favorites view + +The Favorites view exposes **By Activity**, **By Area**, and **Monitoring** tabs. **By Panel** is not available because the physical breaker grid is inherently +single-panel. + +- **By Activity / By Area** — Show the favorited circuits sorted by power (or grouped by area). Sub-device tiles render above the circuit list. When more than + one panel contributes favorites, circuit and sub-device names are prefixed with their panel name so you can tell them apart. +- **Monitoring** — Stacks one Monitoring block per contributing panel, since threshold and notification settings are per-panel. + +The Favorites view is **stateful**: the active tab, expanded rows, and search query are remembered in the browser and restored when you come back to it. + +### Editing from the Favorites view + +Opening a circuit's or sub-device's gear from the Favorites view targets the originating panel for any side-panel edits (graph horizon, monitoring thresholds, +relay state, etc.). Settings always go to the right panel even though the targets are aggregated in the Favorites view. + +### Services + +The favorites map is also exposed as services for automations and scripts: -The Settings tab provides access to integration configuration options without navigating through the Home Assistant settings menu. +- `span_panel.get_favorites` — Returns the current map. +- `span_panel.add_favorite` / `span_panel.remove_favorite` — Take a single `entity_id` (any sensor on the circuit or sub-device — current, power, SoC, etc.). + The integration resolves the entity to its panel and target, so you never need to know internal circuit UUIDs or HA device IDs. diff --git a/pyproject.toml b/pyproject.toml index 3246440f..e7ed7736 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "span" -version = "2.0.5" +version = "2.0.6" description = "Span Panel Custom Integration for Home Assistant" authors = [{name = "SpanPanel"}] license = {text = "MIT"} @@ -8,7 +8,7 @@ readme = "README.md" requires-python = ">=3.14.2,<3.15" dependencies = [ "homeassistant==2026.4.2", - "span-panel-api==2.5.4", + "span-panel-api==2.6.1", ] [dependency-groups] diff --git a/tests/test_coordinator.py b/tests/test_coordinator.py index 8ed1a3fa..48427766 100644 --- a/tests/test_coordinator.py +++ b/tests/test_coordinator.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +from typing import cast from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -16,6 +17,7 @@ from custom_components.span_panel.coordinator import SpanPanelCoordinator from homeassistant.core import HomeAssistant +from span_panel_api import SpanMqttClient from homeassistant.exceptions import ( ConfigEntryAuthFailed, ConfigEntryNotReady, @@ -34,13 +36,13 @@ def _create_coordinator( hass: HomeAssistant, *, - client: MagicMock | None = None, + client: object | None = None, options: dict | None = None, ) -> SpanPanelCoordinator: """Create a coordinator with mocked dependencies.""" return SpanPanelCoordinator( hass, - client or MagicMock(), + cast(SpanMqttClient, client or MagicMock()), MockConfigEntry( domain="span_panel", options=options or {}, @@ -187,8 +189,10 @@ async def test_fire_dip_notification_noops_without_events(hass: HomeAssistant) - async def test_async_setup_streaming_registers_callback_and_starts_client( hass: HomeAssistant, ) -> None: - """Streaming setup should register the callback and start the client.""" + """Streaming setup should register both callbacks and start the client.""" client = MagicMock() + unregister_connection = MagicMock() + client.register_connection_callback = MagicMock(return_value=unregister_connection) client.register_snapshot_callback = MagicMock(return_value=MagicMock()) client.start_streaming = AsyncMock() coordinator = _create_coordinator(hass, client=client) @@ -198,6 +202,8 @@ async def test_async_setup_streaming_registers_callback_and_starts_client( client.register_snapshot_callback.assert_called_once() client.start_streaming.assert_awaited_once() assert coordinator._unregister_streaming is not None + client.register_connection_callback.assert_called_once_with(coordinator._on_connection_change) + assert coordinator._unregister_connection is not None async def test_on_snapshot_push_updates_state_and_runs_post_tasks( @@ -386,3 +392,118 @@ async def test_async_reload_task_handles_expected_errors( await coordinator._async_reload_task() assert "Home Assistant error during reload: reload failed" in caplog.text + + +async def test_connection_callback_registered_and_unregistered_on_lifecycle( + hass: HomeAssistant, +) -> None: + """async_setup_streaming should register a connection callback; async_shutdown should unregister it.""" + client = MagicMock() + client.register_connection_callback = MagicMock() + client.register_snapshot_callback = MagicMock() + client.start_streaming = AsyncMock() + client.stop_streaming = AsyncMock() + client.close = AsyncMock() + + # register_connection_callback returns an unregister function + unregister_connection = MagicMock() + client.register_connection_callback.return_value = unregister_connection + client.register_snapshot_callback.return_value = MagicMock() + + coordinator = _create_coordinator(hass, client=client) + + await coordinator.async_setup_streaming() + + # Connection callback was registered exactly once with the coordinator's handler + client.register_connection_callback.assert_called_once_with(coordinator._on_connection_change) + assert coordinator._unregister_connection is unregister_connection + + await coordinator.async_shutdown() + + # Unregister was invoked and the field cleared + cast(MagicMock, unregister_connection).assert_called_once_with() + assert coordinator._unregister_connection is None + + +async def test_on_connection_change_false_flips_offline_and_notifies_listeners( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """A False edge must flip panel_offline True, log once, and push a listener update.""" + coordinator = _create_coordinator(hass) + assert coordinator.panel_offline is False + + with patch.object(coordinator, "async_update_listeners") as notify: + with caplog.at_level(logging.INFO): + coordinator._on_connection_change(False) + + assert coordinator.panel_offline is True + notify.assert_called_once_with() + assert any( + "is unavailable" in r.message and "MQTT broker disconnected" in r.message + for r in caplog.records + ) + + +async def test_on_connection_change_true_clears_offline_and_notifies_listeners( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """A True edge must flip panel_offline False, log once, and push a listener update.""" + coordinator = _create_coordinator(hass) + coordinator._panel_offline = True + + with patch.object(coordinator, "async_update_listeners") as notify: + with caplog.at_level(logging.INFO): + coordinator._on_connection_change(True) + + assert coordinator.panel_offline is False + notify.assert_called_once_with() + assert any("is back online" in r.message for r in caplog.records) + + +async def test_on_connection_change_noop_when_state_unchanged( + hass: HomeAssistant, +) -> None: + """When connected state matches current panel_offline, no listener fan-out.""" + coordinator = _create_coordinator(hass) + + # Already online (panel_offline=False); receiving another True edge is a no-op + with patch.object(coordinator, "async_update_listeners") as notify_online_case: + coordinator._on_connection_change(True) + notify_online_case.assert_not_called() + assert coordinator.panel_offline is False + + # Already offline; receiving another False edge is a no-op + coordinator._panel_offline = True + with patch.object(coordinator, "async_update_listeners") as notify_offline_case: + coordinator._on_connection_change(False) + notify_offline_case.assert_not_called() + assert coordinator.panel_offline is True + + +async def test_async_update_data_stale_data_error_marks_offline_and_returns_last_data( + hass: HomeAssistant, caplog: pytest.LogCaptureFixture +) -> None: + """A SpanPanelStaleDataError from get_snapshot() should be treated as an expected offline signal.""" + from span_panel_api.exceptions import SpanPanelStaleDataError + + last_snapshot = SpanPanelSnapshotFactory.create() + + client = MagicMock() + client.get_snapshot = AsyncMock( + side_effect=SpanPanelStaleDataError("MQTT broker disconnected") + ) + + coordinator = _create_coordinator(hass, client=client) + # Simulate a prior successful update + coordinator.data = last_snapshot + assert coordinator.panel_offline is False + + with caplog.at_level(logging.INFO): + result = await coordinator._async_update_data() + + assert result is last_snapshot + assert coordinator.panel_offline is True + assert any( + "is unavailable" in r.message and "MQTT broker disconnected" in r.message + for r in caplog.records + ) diff --git a/tests/test_favorites_service.py b/tests/test_favorites_service.py new file mode 100644 index 00000000..ab596ac3 --- /dev/null +++ b/tests/test_favorites_service.py @@ -0,0 +1,597 @@ +"""Tests for cross-panel favorites storage helpers and services.""" + +from __future__ import annotations + +from typing import Any +from unittest.mock import MagicMock, patch + +import pytest + +from homeassistant.core import SupportsResponse +from homeassistant.exceptions import ServiceValidationError + +from custom_components.span_panel.const import DOMAIN +from custom_components.span_panel.frontend import ( + async_get_favorites, + async_set_favorite, +) +from custom_components.span_panel.services import _async_register_favorites_services + + +class _FakeStore: + """In-memory stand-in for homeassistant.helpers.storage.Store. + + One shared backing dict keyed by storage key, so multiple Store(...) calls + in the same test see a consistent view of the data. + """ + + _shared_state: dict[str, Any] = {} + + def __init__(self, _hass: Any, _version: int, key: str) -> None: + self._key = key + + async def async_load(self) -> Any: + return _FakeStore._shared_state.get(self._key) + + async def async_save(self, data: Any) -> None: + _FakeStore._shared_state[self._key] = data + + @classmethod + def reset(cls) -> None: + cls._shared_state = {} + + @classmethod + def preload(cls, key: str, data: Any) -> None: + cls._shared_state[key] = data + + +@pytest.fixture(autouse=True) +def _reset_store() -> None: + _FakeStore.reset() + + +@pytest.fixture +def _patched_store() -> Any: + with patch( + "custom_components.span_panel.frontend.Store", + _FakeStore, + ): + yield + + +def _panel_entry(circuits: list[str] | None = None, sub_devices: list[str] | None = None) -> dict[str, list[str]]: + return {"circuits": circuits or [], "sub_devices": sub_devices or []} + + +class TestAsyncGetFavorites: + """Tests for ``async_get_favorites`` helper.""" + + @pytest.mark.asyncio + async def test_empty_storage_returns_empty_dict(self, _patched_store: Any) -> None: + hass = MagicMock() + result = await async_get_favorites(hass) + assert result == {} + + @pytest.mark.asyncio + async def test_returns_stored_favorites_new_shape(self, _patched_store: Any) -> None: + _FakeStore.preload( + "span_panel_settings", + { + "show_panel": True, + "favorites": { + "panel_a": {"circuits": ["c1", "c2"], "sub_devices": ["bess1"]}, + "panel_b": {"circuits": ["c3"], "sub_devices": []}, + }, + }, + ) + hass = MagicMock() + result = await async_get_favorites(hass) + assert result == { + "panel_a": _panel_entry(["c1", "c2"], ["bess1"]), + "panel_b": _panel_entry(["c3"], []), + } + + @pytest.mark.asyncio + async def test_legacy_list_shape_is_circuits_only(self, _patched_store: Any) -> None: + """Pre-existing favorites stored as flat lists migrate to circuits-only entries.""" + _FakeStore.preload( + "span_panel_settings", + {"favorites": {"panel_a": ["c1", "c2"], "panel_b": ["c3"]}}, + ) + hass = MagicMock() + result = await async_get_favorites(hass) + assert result == { + "panel_a": _panel_entry(["c1", "c2"]), + "panel_b": _panel_entry(["c3"]), + } + + @pytest.mark.asyncio + async def test_filters_invalid_shapes(self, _patched_store: Any) -> None: + """Malformed entries (non-str values, missing kinds, empties) are dropped.""" + _FakeStore.preload( + "span_panel_settings", + { + "favorites": { + "panel_a": {"circuits": ["c1", "", 42, "c2"], "sub_devices": "bad"}, + "panel_b": "not-a-dict", + 123: {"circuits": ["c3"]}, # type: ignore[dict-item] + "panel_empty": {"circuits": [], "sub_devices": []}, + } + }, + ) + hass = MagicMock() + result = await async_get_favorites(hass) + assert result == {"panel_a": _panel_entry(["c1", "c2"], [])} + + @pytest.mark.asyncio + async def test_tolerates_missing_favorites_key(self, _patched_store: Any) -> None: + _FakeStore.preload( + "span_panel_settings", {"show_panel": False, "panel_admin_only": True} + ) + hass = MagicMock() + result = await async_get_favorites(hass) + assert result == {} + + +class TestAsyncSetFavorite: + """Tests for ``async_set_favorite`` helper.""" + + @pytest.mark.asyncio + async def test_add_circuit_creates_panel_entry(self, _patched_store: Any) -> None: + hass = MagicMock() + result = await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + assert result == {"panel_a": _panel_entry(["c1"])} + persisted = _FakeStore._shared_state["span_panel_settings"] + assert persisted["favorites"] == {"panel_a": _panel_entry(["c1"])} + + @pytest.mark.asyncio + async def test_add_subdevice_coexists_with_circuits(self, _patched_store: Any) -> None: + hass = MagicMock() + await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + result = await async_set_favorite(hass, "panel_a", "sub_devices", "bess1", True) + assert result == {"panel_a": _panel_entry(["c1"], ["bess1"])} + + @pytest.mark.asyncio + async def test_add_dedupes(self, _patched_store: Any) -> None: + hass = MagicMock() + await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + result = await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + assert result == {"panel_a": _panel_entry(["c1"])} + + @pytest.mark.asyncio + async def test_remove_drops_empty_panel_key(self, _patched_store: Any) -> None: + hass = MagicMock() + await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + result = await async_set_favorite(hass, "panel_a", "circuits", "c1", False) + assert result == {} + persisted = _FakeStore._shared_state["span_panel_settings"] + assert persisted["favorites"] == {} + + @pytest.mark.asyncio + async def test_remove_keeps_other_kind_entries(self, _patched_store: Any) -> None: + hass = MagicMock() + await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + await async_set_favorite(hass, "panel_a", "sub_devices", "bess1", True) + result = await async_set_favorite(hass, "panel_a", "circuits", "c1", False) + assert result == {"panel_a": _panel_entry([], ["bess1"])} + + @pytest.mark.asyncio + async def test_remove_of_unknown_is_noop(self, _patched_store: Any) -> None: + hass = MagicMock() + result = await async_set_favorite(hass, "panel_a", "circuits", "missing", False) + assert result == {} + + @pytest.mark.asyncio + async def test_preserves_sibling_settings(self, _patched_store: Any) -> None: + """Favorites writes must not trample ``show_panel`` or ``panel_admin_only``.""" + _FakeStore.preload( + "span_panel_settings", {"show_panel": False, "panel_admin_only": True} + ) + hass = MagicMock() + await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + persisted = _FakeStore._shared_state["span_panel_settings"] + assert persisted["show_panel"] is False + assert persisted["panel_admin_only"] is True + assert persisted["favorites"] == {"panel_a": _panel_entry(["c1"])} + + @pytest.mark.asyncio + async def test_unknown_kind_raises(self, _patched_store: Any) -> None: + hass = MagicMock() + with pytest.raises(ValueError): + await async_set_favorite(hass, "panel_a", "bogus", "c1", True) + + @pytest.mark.asyncio + async def test_set_migrates_legacy_shape_in_place(self, _patched_store: Any) -> None: + """Touching a panel with legacy ``[uuid]`` storage rewrites it as the canonical dict.""" + _FakeStore.preload( + "span_panel_settings", + {"favorites": {"panel_a": ["c1", "c2"]}}, + ) + hass = MagicMock() + # No new circuit added; the same uuid we already had. + result = await async_set_favorite(hass, "panel_a", "circuits", "c1", True) + assert result == {"panel_a": _panel_entry(["c1", "c2"])} + persisted = _FakeStore._shared_state["span_panel_settings"]["favorites"] + # Persisted shape is now the nested dict, not the legacy list. + assert persisted == {"panel_a": _panel_entry(["c1", "c2"])} + + @pytest.mark.asyncio + async def test_concurrent_set_favorites_does_not_drop_writes( + self, _patched_store: Any + ) -> None: + """Two parallel adds must both end up in storage (lock prevents lost writes).""" + import asyncio + + hass = MagicMock() + # Run both adds concurrently; each call goes through the lock so the + # second write picks up the first's mutation. + results = await asyncio.gather( + async_set_favorite(hass, "panel_a", "circuits", "c1", True), + async_set_favorite(hass, "panel_a", "circuits", "c2", True), + ) + # Both calls return the favorites map at the time they wrote; + # the FINAL persisted state must contain both circuits. + persisted = _FakeStore._shared_state["span_panel_settings"]["favorites"] + assert sorted(persisted["panel_a"]["circuits"]) == ["c1", "c2"] + # Each call's returned map is at least non-empty. + for r in results: + assert "panel_a" in r + + +def _capture_registered_handlers(hass: MagicMock) -> dict[str, Any]: + """Run ``_async_register_favorites_services`` and return a name->handler map.""" + handlers: dict[str, Any] = {} + schemas: dict[str, Any] = {} + responses: dict[str, Any] = {} + + def _register( + domain: str, + service: str, + handler: Any, + schema: Any | None = None, + supports_response: Any = SupportsResponse.NONE, + ) -> None: + assert domain == DOMAIN + handlers[service] = handler + schemas[service] = schema + responses[service] = supports_response + + hass.services = MagicMock() + hass.services.async_register = MagicMock(side_effect=_register) + _async_register_favorites_services(hass) + return {"handlers": handlers, "schemas": schemas, "responses": responses} + + +def _make_service_call(data: dict[str, Any]) -> MagicMock: + call = MagicMock() + call.data = data + return call + + +def _make_entity_entry( + *, + platform: str = DOMAIN, + unique_id: str = "span_sp3-242424_abcdef0123456789abcdef0123456789_power", + device_id: str | None = "d_main", +) -> MagicMock: + entry = MagicMock() + entry.platform = platform + entry.unique_id = unique_id + entry.device_id = device_id + return entry + + +def _make_device_entry( + *, + device_id: str = "d_main", + identifiers: set[tuple[str, str]] | None = None, + via_device_id: str | None = None, +) -> MagicMock: + device = MagicMock() + device.id = device_id + device.identifiers = identifiers if identifiers is not None else {(DOMAIN, "serial_a")} + device.via_device_id = via_device_id + return device + + +def _patch_registries(entity: MagicMock | None, device: MagicMock | None) -> Any: + """Patch services.er.async_get and services.dr.async_get for a single call. + + Either registry returns the given object from ``async_get`` regardless of id. + """ + entity_reg = MagicMock() + entity_reg.async_get = MagicMock(return_value=entity) + device_reg = MagicMock() + device_reg.async_get = MagicMock(return_value=device) + + return patch.multiple( + "custom_components.span_panel.services", + er=MagicMock(async_get=MagicMock(return_value=entity_reg)), + dr=MagicMock(async_get=MagicMock(return_value=device_reg)), + ) + + +def _patch_registries_for_subdevice( + entity: MagicMock, + sub_device: MagicMock, + parent_panel: MagicMock, +) -> Any: + """Stub registries so device_registry.async_get returns sub_device for the + entity's device_id and parent_panel for the via_device_id lookup.""" + entity_reg = MagicMock() + entity_reg.async_get = MagicMock(return_value=entity) + + device_reg = MagicMock() + def _device_lookup(device_id: str) -> MagicMock | None: + if device_id == sub_device.id: + return sub_device + if device_id == parent_panel.id: + return parent_panel + return None + device_reg.async_get = MagicMock(side_effect=_device_lookup) + + return patch.multiple( + "custom_components.span_panel.services", + er=MagicMock(async_get=MagicMock(return_value=entity_reg)), + dr=MagicMock(async_get=MagicMock(return_value=device_reg)), + ) + + +class TestFavoritesServiceHandlers: + """Tests for the ``get_favorites`` / ``add_favorite`` / ``remove_favorite`` service handlers.""" + + @pytest.mark.asyncio + async def test_get_favorites_returns_current_map(self, _patched_store: Any) -> None: + _FakeStore.preload( + "span_panel_settings", + {"favorites": {"panel_a": {"circuits": ["c1"], "sub_devices": []}}}, + ) + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["get_favorites"] + + result = await handler(_make_service_call({})) + assert result == { + "favorites": {"panel_a": _panel_entry(["c1"])}, + } + assert registered["responses"]["get_favorites"] is SupportsResponse.ONLY + + @pytest.mark.asyncio + async def test_add_favorite_rejects_unknown_entity( + self, _patched_store: Any + ) -> None: + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + with _patch_registries(entity=None, device=None): + with pytest.raises(ServiceValidationError): + await handler(_make_service_call({"entity_id": "sensor.unknown"})) + + @pytest.mark.asyncio + async def test_add_favorite_rejects_non_span_platform( + self, _patched_store: Any + ) -> None: + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + foreign_entity = _make_entity_entry(platform="other_domain") + with _patch_registries(entity=foreign_entity, device=None): + with pytest.raises(ServiceValidationError): + await handler(_make_service_call({"entity_id": "sensor.other_power"})) + + @pytest.mark.asyncio + async def test_add_favorite_accepts_sub_device_entity( + self, _patched_store: Any + ) -> None: + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + entity = _make_entity_entry( + unique_id="span_sp3-242424_storage_battery_percentage", + device_id="d_bess", + ) + sub_device = _make_device_entry( + device_id="d_bess", + identifiers={(DOMAIN, "serial_a_bess")}, + via_device_id="d_main", + ) + parent = _make_device_entry( + device_id="d_main", + identifiers={(DOMAIN, "serial_a")}, + via_device_id=None, + ) + + with _patch_registries_for_subdevice(entity, sub_device, parent): + result = await handler( + _make_service_call({"entity_id": "sensor.battery_level"}) + ) + + assert result == {"favorites": {"d_main": _panel_entry([], ["d_bess"])}} + + @pytest.mark.asyncio + async def test_add_favorite_on_evse_feed_circuit_sensor_favorites_circuit( + self, _patched_store: Any + ) -> None: + """EVSE feed-circuit sensors are re-assigned to the EVSE sub-device via + a device override, but their unique_id still carries the circuit UUID. + Favoriting such an entity must store a *circuit* favorite keyed by the + parent panel, not a sub-device favorite keyed by the EVSE.""" + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + circuit_uuid = "abcdef0123456789abcdef0123456789" + entity = _make_entity_entry( + unique_id=f"span_sp3-242424_{circuit_uuid}_power", + device_id="d_evse", + ) + evse_sub_device = _make_device_entry( + device_id="d_evse", + identifiers={(DOMAIN, "serial_a_evse")}, + via_device_id="d_main", + ) + parent = _make_device_entry( + device_id="d_main", + identifiers={(DOMAIN, "serial_a")}, + via_device_id=None, + ) + + with _patch_registries_for_subdevice(entity, evse_sub_device, parent): + result = await handler( + _make_service_call({"entity_id": "sensor.evse_feed_power"}) + ) + + assert result == {"favorites": {"d_main": _panel_entry([circuit_uuid])}} + + @pytest.mark.asyncio + async def test_add_favorite_rejects_entity_without_uuid_in_unique_id( + self, _patched_store: Any + ) -> None: + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + # Panel-level sensor (no circuit uuid segment in unique_id). + entity = _make_entity_entry(unique_id="span_sp3-242424_instantGridPowerW") + device = _make_device_entry() + + with _patch_registries(entity=entity, device=device): + with pytest.raises(ServiceValidationError): + await handler(_make_service_call({"entity_id": "sensor.panel_power"})) + + @pytest.mark.asyncio + async def test_add_favorite_persists_and_returns_map( + self, _patched_store: Any + ) -> None: + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + circuit_uuid = "abcdef0123456789abcdef0123456789" + entity = _make_entity_entry( + unique_id=f"span_sp3-242424_{circuit_uuid}_power", + device_id="d_main", + ) + device = _make_device_entry(device_id="d_main") + + with _patch_registries(entity=entity, device=device): + result = await handler( + _make_service_call({"entity_id": "sensor.kitchen_power"}) + ) + + assert result == {"favorites": {"d_main": _panel_entry([circuit_uuid])}} + assert _FakeStore._shared_state["span_panel_settings"]["favorites"] == { + "d_main": _panel_entry([circuit_uuid]) + } + + @pytest.mark.asyncio + async def test_remove_favorite_resolves_via_entity_registry( + self, _patched_store: Any + ) -> None: + circuit_uuid = "abcdef0123456789abcdef0123456789" + _FakeStore.preload( + "span_panel_settings", + {"favorites": {"d_main": _panel_entry([circuit_uuid])}}, + ) + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["remove_favorite"] + + entity = _make_entity_entry( + unique_id=f"span_sp3-242424_{circuit_uuid}_power", + device_id="d_main", + ) + device = _make_device_entry(device_id="d_main") + + with _patch_registries(entity=entity, device=device): + result = await handler( + _make_service_call({"entity_id": "sensor.kitchen_power"}) + ) + + assert result == {"favorites": {}} + + def test_mutation_responses_are_optional(self, _patched_store: Any) -> None: + hass = MagicMock() + registered = _capture_registered_handlers(hass) + assert registered["responses"]["add_favorite"] is SupportsResponse.OPTIONAL + assert registered["responses"]["remove_favorite"] is SupportsResponse.OPTIONAL + + @pytest.mark.asyncio + async def test_add_favorite_rejects_entity_with_no_device_id( + self, _patched_store: Any + ) -> None: + """Entities with no ``device_id`` are not favoritable.""" + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + orphan = _make_entity_entry(device_id=None) + with _patch_registries(entity=orphan, device=None): + with pytest.raises(ServiceValidationError): + await handler(_make_service_call({"entity_id": "sensor.orphan"})) + + @pytest.mark.asyncio + async def test_add_favorite_rejects_subdevice_with_non_span_parent( + self, _patched_store: Any + ) -> None: + """Sub-device whose ``via_device_id`` does NOT point at a SPAN panel.""" + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + entity = _make_entity_entry(device_id="d_sub") + sub_device = _make_device_entry( + device_id="d_sub", + identifiers={(DOMAIN, "serial_a_bess")}, + via_device_id="d_foreign", + ) + # Parent exists but isn't a SPAN device (different domain identifier). + foreign_parent = _make_device_entry( + device_id="d_foreign", + identifiers={("other_domain", "xyz")}, + via_device_id=None, + ) + + with _patch_registries_for_subdevice(entity, sub_device, foreign_parent): + with pytest.raises(ServiceValidationError): + await handler( + _make_service_call({"entity_id": "sensor.bess_under_foreign"}) + ) + + @pytest.mark.asyncio + async def test_add_favorite_rejects_subdevice_with_missing_parent( + self, _patched_store: Any + ) -> None: + """Sub-device whose ``via_device_id`` references a missing device.""" + hass = MagicMock() + registered = _capture_registered_handlers(hass) + handler = registered["handlers"]["add_favorite"] + + entity = _make_entity_entry(device_id="d_sub") + sub_device = _make_device_entry( + device_id="d_sub", + identifiers={(DOMAIN, "serial_a_bess")}, + via_device_id="d_missing", + ) + + # Stub a registry where the parent lookup returns None. + entity_reg = MagicMock() + entity_reg.async_get = MagicMock(return_value=entity) + + device_reg = MagicMock() + def _device_lookup(device_id: str) -> MagicMock | None: + return sub_device if device_id == "d_sub" else None + device_reg.async_get = MagicMock(side_effect=_device_lookup) + + with patch.multiple( + "custom_components.span_panel.services", + er=MagicMock(async_get=MagicMock(return_value=entity_reg)), + dr=MagicMock(async_get=MagicMock(return_value=device_reg)), + ): + with pytest.raises(ServiceValidationError): + await handler( + _make_service_call({"entity_id": "sensor.bess_orphaned"}) + ) diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 8b0c984c..ea45eb5a 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -745,6 +745,76 @@ async def test_topology_includes_always_on_and_priority(self, hass: HomeAssistan assert hvac_data["priority"] == "SOC_THRESHOLD" @pytest.mark.asyncio + async def test_topology_includes_panel_status_entity(self, hass: HomeAssistant): + """panel_status binary sensor entity_id is included in the topology panel_entities map.""" + snapshot = SpanPanelSnapshotFactory.create(serial_number="sp3-242424-001") + + entry = MockConfigEntry( + domain=DOMAIN, + data={}, + entry_id="span_entry", + unique_id="sp3-242424-001", + ) + entry.add_to_hass(hass) + entry.mock_state(hass, ConfigEntryState.LOADED) + entry.runtime_data = SpanPanelRuntimeData( + coordinator=_make_coordinator(snapshot) + ) + + panel_device = _register_panel_device(hass, "span_entry", serial="sp3-242424-001") + + _register_entity( + hass, + "span_entry", + panel_device.id, + "binary_sensor", + "span_sp3-242424-001_panel_status", + "binary_sensor.span_panel_test_panel_status", + ) + + connection = _make_mock_connection() + msg = {"id": 1, "type": "span_panel/panel_topology", "device_id": panel_device.id} + + await _handle_panel_topology_inner(hass, connection, msg) + + connection.send_error.assert_not_called() + connection.send_result.assert_called_once() + + result = connection.send_result.call_args[0][1] + assert result["panel_entities"]["panel_status"] == "binary_sensor.span_panel_test_panel_status" + + @pytest.mark.asyncio + async def test_topology_omits_panel_status_when_entity_missing(self, hass: HomeAssistant): + """panel_status is absent from the topology map when no binary_sensor entity exists.""" + snapshot = SpanPanelSnapshotFactory.create(serial_number="sp3-242424-001") + + entry = MockConfigEntry( + domain=DOMAIN, + data={}, + entry_id="span_entry", + unique_id="sp3-242424-001", + ) + entry.add_to_hass(hass) + entry.mock_state(hass, ConfigEntryState.LOADED) + entry.runtime_data = SpanPanelRuntimeData( + coordinator=_make_coordinator(snapshot) + ) + + panel_device = _register_panel_device(hass, "span_entry", serial="sp3-242424-001") + + # Intentionally do NOT register a binary_sensor.*_panel_status entity + + connection = _make_mock_connection() + msg = {"id": 1, "type": "span_panel/panel_topology", "device_id": panel_device.id} + + await _handle_panel_topology_inner(hass, connection, msg) + + connection.send_error.assert_not_called() + connection.send_result.assert_called_once() + + result = connection.send_result.call_args[0][1] + assert "panel_status" not in result["panel_entities"] + @pytest.mark.asyncio async def test_registration(self, hass: HomeAssistant): """WebSocket commands can be registered without error.""" diff --git a/uv.lock b/uv.lock index c2e14e94..55e72965 100644 --- a/uv.lock +++ b/uv.lock @@ -2352,7 +2352,7 @@ wheels = [ [[package]] name = "span" -version = "2.0.5" +version = "2.0.6" source = { virtual = "." } dependencies = [ { name = "homeassistant" }, @@ -2407,7 +2407,7 @@ dev = [ [[package]] name = "span-panel-api" -version = "2.5.4" +version = "2.6.1" source = { editable = "../span-panel-api" } dependencies = [ { name = "httpx" },