1
0
mirror of https://github.com/home-assistant/frontend.git synced 2026-04-02 08:33:31 +01:00

Convert energy panel to use a dashboard strategy (#30170)

* Convert energy panel to use a dashboard strategy

Move the view-selection logic from ha-panel-energy into a dedicated
energy dashboard strategy, consistent with how home/areas/map dashboards
work. The strategy decides which view strategies (electricity, gas,
water, power, overview) to show based on energy preferences.

Extract shared constants into a separate module to avoid circular
dependencies between the panel and view strategies.

* Remove unused energy collection constants from ha-panel-energy
This commit is contained in:
Petar Petrov
2026-03-19 16:16:56 +01:00
committed by GitHub
parent 1bbfb79ddb
commit eb43d85439
9 changed files with 188 additions and 169 deletions

View File

@@ -0,0 +1,2 @@
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
export const DEFAULT_POWER_COLLECTION_KEY = "energy_dashboard_now";

View File

@@ -1,78 +1,20 @@
import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../common/navigate";
import type { LocalizeKeys } from "../../common/translations/localize";
import "../../components/ha-alert";
import "../../components/ha-icon-button-arrow-prev";
import "../../components/ha-menu-button";
import "../../components/ha-top-app-bar-fixed";
import type { EnergyPreferences } from "../../data/energy";
import { getEnergyDataCollection } from "../../data/energy";
import type { LovelaceConfig } from "../../data/lovelace/config/types";
import type { LovelaceViewConfig } from "../../data/lovelace/config/view";
import { haStyle } from "../../resources/styles";
import type { HomeAssistant, PanelInfo } from "../../types";
import { generateLovelaceDashboardStrategy } from "../lovelace/strategies/get-strategy";
import "../lovelace/hui-root";
import type { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view";
import "../lovelace/views/hui-view-container";
export const DEFAULT_ENERGY_COLLECTION_KEY = "energy_dashboard";
export const DEFAULT_POWER_COLLECTION_KEY = "energy_dashboard_now";
const EMPTY_PREFERENCES: EnergyPreferences = {
energy_sources: [],
device_consumption: [],
device_consumption_water: [],
};
const OVERVIEW_VIEW = {
path: "overview",
strategy: {
type: "energy-overview",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const ENERGY_VIEW = {
path: "electricity",
strategy: {
type: "energy",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const WATER_VIEW = {
path: "water",
strategy: {
type: "water",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const GAS_VIEW = {
path: "gas",
strategy: {
type: "gas",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const POWER_VIEW = {
path: "now",
strategy: {
type: "power",
collection_key: DEFAULT_POWER_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const WIZARD_VIEW = {
type: "panel",
path: "setup",
cards: [{ type: "custom:energy-setup-wizard-card" }],
};
@customElement("ha-panel-energy")
class PanelEnergy extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -88,9 +30,6 @@ class PanelEnergy extends LitElement {
prefix: string;
};
@state()
private _prefs?: EnergyPreferences;
@state() private _searchParms = new URLSearchParams(window.location.search);
@state()
@@ -115,35 +54,16 @@ class PanelEnergy extends LitElement {
}
}
private _fetchEnergyPrefs = async (): Promise<
EnergyPreferences | undefined
> => {
const collection = getEnergyDataCollection(this.hass, {
key: DEFAULT_ENERGY_COLLECTION_KEY,
});
try {
await collection.refresh();
} catch (err: any) {
if (err.code === "not_found") {
return undefined;
}
throw err;
}
return collection.prefs;
};
private async _loadConfig() {
try {
this._error = undefined;
const prefs = await this._fetchEnergyPrefs();
this._prefs = prefs || EMPTY_PREFERENCES;
await this._setLovelace();
} catch (err) {
// eslint-disable-next-line no-console
console.error("Failed to load prefs:", err);
this._prefs = EMPTY_PREFERENCES;
console.error("Failed to load energy config:", err);
this._error = (err as Error).message || "Unknown error";
return;
}
await this._setLovelace();
// Check if current path is valid, navigate to first view if not
const views = this._lovelace!.config?.views || [];
@@ -158,7 +78,10 @@ class PanelEnergy extends LitElement {
}
private async _setLovelace() {
const config = await this._generateLovelaceConfig();
const config: LovelaceConfig = await generateLovelaceDashboardStrategy(
{ strategy: { type: "energy" } },
this.hass
);
this._lovelace = {
config: config,
@@ -188,8 +111,7 @@ class PanelEnergy extends LitElement {
`;
}
if (!this._prefs) {
// Still loading
if (!this._lovelace) {
return html`
<div class="centered">
<ha-spinner size="large"></ha-spinner>
@@ -197,10 +119,6 @@ class PanelEnergy extends LitElement {
`;
}
if (!this._lovelace) {
return nothing;
}
return html`
<hui-root
.hass=${this.hass}
@@ -216,78 +134,6 @@ class PanelEnergy extends LitElement {
`;
}
private async _generateLovelaceConfig(): Promise<LovelaceConfig> {
if (
!this._prefs ||
(this._prefs.device_consumption.length === 0 &&
this._prefs.energy_sources.length === 0)
) {
await import("./cards/energy-setup-wizard-card");
return {
views: [WIZARD_VIEW],
};
}
const hasEnergy = this._prefs.energy_sources.some((source) =>
["grid", "solar", "battery"].includes(source.type)
);
const hasPowerSource = this._prefs.energy_sources.some((source) => {
if (source.type === "solar" && source.stat_rate) return true;
if (source.type === "battery" && source.stat_rate) return true;
if (source.type === "grid") {
return !!source.stat_rate || !!source.power_config;
}
return false;
});
const hasDevicePower = this._prefs.device_consumption.some(
(device) => device.stat_rate
);
const hasPower = hasPowerSource || hasDevicePower;
const hasWater =
this._prefs.energy_sources.some((source) => source.type === "water") ||
this._prefs.device_consumption_water?.length > 0;
const hasGas = this._prefs.energy_sources.some(
(source) => source.type === "gas"
);
const hasDeviceConsumption = this._prefs.device_consumption.length > 0;
const views: LovelaceViewConfig[] = [];
if (hasEnergy || hasDeviceConsumption) {
views.push(ENERGY_VIEW);
}
if (hasGas) {
views.push(GAS_VIEW);
}
if (hasWater) {
views.push(WATER_VIEW);
}
if (hasPower) {
views.push(POWER_VIEW);
}
if (
hasPowerSource ||
[hasEnergy, hasGas, hasWater].filter(Boolean).length > 1
) {
views.unshift(OVERVIEW_VIEW);
}
return {
views: views.map((view) => ({
...view,
title:
view.title ||
this.hass.localize(
`ui.panel.energy.title.${view.path}` as LocalizeKeys
),
})),
};
}
private _navigateConfig(ev?: Event) {
ev?.stopPropagation();
const viewPath = this.route?.path?.split("/")[1] || "";

View File

@@ -0,0 +1,170 @@
import { ReactiveElement } from "lit";
import { customElement } from "lit/decorators";
import { getEnergyDataCollection } from "../../../data/energy";
import type { EnergyPreferences } from "../../../data/energy";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceConfig } from "../../../data/lovelace/config/types";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { LocalizeKeys } from "../../../common/translations/localize";
import type { HomeAssistant } from "../../../types";
import {
DEFAULT_ENERGY_COLLECTION_KEY,
DEFAULT_POWER_COLLECTION_KEY,
} from "../constants";
const OVERVIEW_VIEW = {
path: "overview",
strategy: {
type: "energy-overview",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const ENERGY_VIEW = {
path: "electricity",
strategy: {
type: "energy",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const WATER_VIEW = {
path: "water",
strategy: {
type: "water",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const GAS_VIEW = {
path: "gas",
strategy: {
type: "gas",
collection_key: DEFAULT_ENERGY_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const POWER_VIEW = {
path: "now",
strategy: {
type: "power",
collection_key: DEFAULT_POWER_COLLECTION_KEY,
},
} as LovelaceViewConfig;
const WIZARD_VIEW = {
type: "panel",
path: "setup",
cards: [{ type: "custom:energy-setup-wizard-card" }],
};
const EMPTY_PREFERENCES: EnergyPreferences = {
energy_sources: [],
device_consumption: [],
device_consumption_water: [],
};
export interface EnergyDashboardStrategyConfig extends LovelaceStrategyConfig {
type: "energy";
}
@customElement("energy-dashboard-strategy")
export class EnergyDashboardStrategy extends ReactiveElement {
static async generate(
_config: EnergyDashboardStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceConfig> {
const prefs = await fetchEnergyPrefs(hass);
if (
!prefs ||
(prefs.device_consumption.length === 0 &&
prefs.energy_sources.length === 0)
) {
await import("../cards/energy-setup-wizard-card");
return {
views: [WIZARD_VIEW],
};
}
const hasEnergy = prefs.energy_sources.some((source) =>
["grid", "solar", "battery"].includes(source.type)
);
const hasPowerSource = prefs.energy_sources.some((source) => {
if (source.type === "solar" && source.stat_rate) return true;
if (source.type === "battery" && source.stat_rate) return true;
if (source.type === "grid") {
return !!source.stat_rate || !!source.power_config;
}
return false;
});
const hasDevicePower = prefs.device_consumption.some(
(device) => device.stat_rate
);
const hasPower = hasPowerSource || hasDevicePower;
const hasWater =
prefs.energy_sources.some((source) => source.type === "water") ||
prefs.device_consumption_water?.length > 0;
const hasGas = prefs.energy_sources.some((source) => source.type === "gas");
const hasDeviceConsumption = prefs.device_consumption.length > 0;
const views: LovelaceViewConfig[] = [];
if (hasEnergy || hasDeviceConsumption) {
views.push(ENERGY_VIEW);
}
if (hasGas) {
views.push(GAS_VIEW);
}
if (hasWater) {
views.push(WATER_VIEW);
}
if (hasPower) {
views.push(POWER_VIEW);
}
if (
hasPowerSource ||
[hasEnergy, hasGas, hasWater].filter(Boolean).length > 1
) {
views.unshift(OVERVIEW_VIEW);
}
return {
views: views.map((view) => ({
...view,
title:
view.title ||
hass.localize(`ui.panel.energy.title.${view.path}` as LocalizeKeys),
})),
};
}
static noEditor = true;
}
async function fetchEnergyPrefs(
hass: HomeAssistant
): Promise<EnergyPreferences> {
const collection = getEnergyDataCollection(hass, {
key: DEFAULT_ENERGY_COLLECTION_KEY,
});
try {
await collection.refresh();
} catch (err: any) {
if (err.code === "not_found") {
return EMPTY_PREFERENCES;
}
throw err;
}
return collection.prefs || EMPTY_PREFERENCES;
}
declare global {
interface HTMLElementTagNameMap {
"energy-dashboard-strategy": EnergyDashboardStrategy;
}
}

View File

@@ -5,7 +5,7 @@ import { getEnergyDataCollection } from "../../../data/energy";
import type { HomeAssistant } from "../../../types";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
@customElement("energy-overview-view-strategy")
export class EnergyOverviewViewStrategy extends ReactiveElement {

View File

@@ -6,7 +6,7 @@ import type { LovelaceCardConfig } from "../../../data/lovelace/config/card";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
import {
LARGE_SCREEN_CONDITION,

View File

@@ -4,7 +4,7 @@ import { getEnergyDataCollection } from "../../../data/energy";
import type { HomeAssistant } from "../../../types";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
@customElement("gas-view-strategy")

View File

@@ -4,7 +4,7 @@ import { getEnergyDataCollection } from "../../../data/energy";
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
import type { LovelaceSectionConfig } from "../../../data/lovelace/config/section";
import type { LovelaceBadgeConfig } from "../../../data/lovelace/config/badge";

View File

@@ -5,7 +5,7 @@ import type { LovelaceSectionConfig } from "../../../data/lovelace/config/sectio
import type { LovelaceStrategyConfig } from "../../../data/lovelace/config/strategy";
import type { LovelaceViewConfig } from "../../../data/lovelace/config/view";
import type { HomeAssistant } from "../../../types";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../ha-panel-energy";
import { DEFAULT_ENERGY_COLLECTION_KEY } from "../constants";
import { shouldShowFloorsAndAreas } from "./show-floors-and-areas";
@customElement("water-view-strategy")

View File

@@ -34,6 +34,7 @@ const STRATEGIES: Record<LovelaceStrategyConfigType, Record<string, any>> = {
iframe: () => import("./iframe/iframe-dashboard-strategy"),
areas: () => import("./areas/areas-dashboard-strategy"),
home: () => import("./home/home-dashboard-strategy"),
energy: () => import("../../energy/strategies/energy-dashboard-strategy"),
},
view: {
"original-states": () =>