ha-frontend-cdce8p/src/panels/lovelace/cards/energy/hui-energy-sources-table-ca...

604 lines
22 KiB
TypeScript

// @ts-ignore
import dataTableStyles from "@material/data-table/dist/mdc.data-table.min.css";
import { UnsubscribeFunc } from "home-assistant-js-websocket";
import {
css,
CSSResultGroup,
html,
LitElement,
TemplateResult,
unsafeCSS,
} from "lit";
import { customElement, property, state } from "lit/decorators";
import { styleMap } from "lit/directives/style-map";
import {
rgb2hex,
lab2rgb,
rgb2lab,
hex2rgb,
} from "../../../../common/color/convert-color";
import { labDarken } from "../../../../common/color/lab";
import { computeStateName } from "../../../../common/entity/compute_state_name";
import { formatNumber } from "../../../../common/number/format_number";
import "../../../../components/chart/statistics-chart";
import "../../../../components/ha-card";
import {
EnergyData,
energySourcesByType,
getEnergyDataCollection,
getEnergyGasUnit,
} from "../../../../data/energy";
import { calculateStatisticSumGrowth } from "../../../../data/history";
import { SubscribeMixin } from "../../../../mixins/subscribe-mixin";
import { HomeAssistant } from "../../../../types";
import { LovelaceCard } from "../../types";
import { EnergySourcesTableCardConfig } from "../types";
@customElement("hui-energy-sources-table-card")
export class HuiEnergySourcesTableCard
extends SubscribeMixin(LitElement)
implements LovelaceCard
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _config?: EnergySourcesTableCardConfig;
@state() private _data?: EnergyData;
public hassSubscribe(): UnsubscribeFunc[] {
return [
getEnergyDataCollection(this.hass, {
key: this._config?.collection_key,
}).subscribe((data) => {
this._data = data;
}),
];
}
public getCardSize(): Promise<number> | number {
return 3;
}
public setConfig(config: EnergySourcesTableCardConfig): void {
this._config = config;
}
protected render(): TemplateResult {
if (!this.hass || !this._config) {
return html``;
}
if (!this._data) {
return html`Loading...`;
}
let totalGrid = 0;
let totalGridCost = 0;
let totalSolar = 0;
let totalBattery = 0;
let totalGas = 0;
let totalGasCost = 0;
const types = energySourcesByType(this._data.prefs);
const computedStyles = getComputedStyle(this);
const solarColor = computedStyles
.getPropertyValue("--energy-solar-color")
.trim();
const batteryFromColor = computedStyles
.getPropertyValue("--energy-battery-out-color")
.trim();
const batteryToColor = computedStyles
.getPropertyValue("--energy-battery-in-color")
.trim();
const returnColor = computedStyles
.getPropertyValue("--energy-grid-return-color")
.trim();
const consumptionColor = computedStyles
.getPropertyValue("--energy-grid-consumption-color")
.trim();
const gasColor = computedStyles
.getPropertyValue("--energy-gas-color")
.trim();
const showCosts =
types.grid?.[0].flow_from.some(
(flow) =>
flow.stat_cost || flow.entity_energy_price || flow.number_energy_price
) ||
types.grid?.[0].flow_to.some(
(flow) =>
flow.stat_compensation ||
flow.entity_energy_price ||
flow.number_energy_price
) ||
types.gas?.some(
(flow) =>
flow.stat_cost || flow.entity_energy_price || flow.number_energy_price
);
const gasUnit = getEnergyGasUnit(this.hass, this._data.prefs) || "";
return html` <ha-card>
${this._config.title
? html`<h1 class="card-header">${this._config.title}</h1>`
: ""}
<div class="mdc-data-table">
<div class="mdc-data-table__table-container">
<table class="mdc-data-table__table" aria-label="Energy sources">
<thead>
<tr class="mdc-data-table__header-row">
<th class="mdc-data-table__header-cell"></th>
<th
class="mdc-data-table__header-cell"
role="columnheader"
scope="col"
>
Source
</th>
<th
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
role="columnheader"
scope="col"
>
Energy
</th>
${showCosts
? html` <th
class="mdc-data-table__header-cell mdc-data-table__header-cell--numeric"
role="columnheader"
scope="col"
>
Cost
</th>`
: ""}
</tr>
</thead>
<tbody class="mdc-data-table__content">
${types.solar?.map((source, idx) => {
const entity = this.hass.states[source.stat_energy_from];
const energy =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
totalSolar += energy;
const color =
idx > 0
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(solarColor)), idx))
)
: solarColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: color,
backgroundColor: color + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entity
? computeStateName(entity)
: source.stat_energy_from}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energy, this.hass.locale)} kWh
</td>
${showCosts
? html`<td class="mdc-data-table__cell"></td>`
: ""}
</tr>`;
})}
${types.solar
? html`<tr class="mdc-data-table__row total">
<td class="mdc-data-table__cell"></td>
<th class="mdc-data-table__cell" scope="row">
Solar total
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(totalSolar, this.hass.locale)} kWh
</td>
${showCosts
? html`<td class="mdc-data-table__cell"></td>`
: ""}
</tr>`
: ""}
${types.battery?.map((source, idx) => {
const entityFrom = this.hass.states[source.stat_energy_from];
const entityTo = this.hass.states[source.stat_energy_to];
const energyFrom =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
const energyTo =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_to]
) || 0;
totalBattery += energyFrom - energyTo;
const fromColor =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(batteryFromColor)), idx)
)
)
: batteryFromColor;
const toColor =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(batteryToColor)), idx)
)
)
: batteryToColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: fromColor,
backgroundColor: fromColor + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entityFrom
? computeStateName(entityFrom)
: source.stat_energy_from}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energyFrom, this.hass.locale)} kWh
</td>
${showCosts
? html`<td class="mdc-data-table__cell"></td>`
: ""}
</tr>
<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: toColor,
backgroundColor: toColor + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entityTo
? computeStateName(entityTo)
: source.stat_energy_from}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energyTo * -1, this.hass.locale)} kWh
</td>
${showCosts
? html`<td class="mdc-data-table__cell"></td>`
: ""}
</tr>`;
})}
${types.battery
? html`<tr class="mdc-data-table__row total">
<td class="mdc-data-table__cell"></td>
<th class="mdc-data-table__cell" scope="row">
Battery total
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(totalBattery, this.hass.locale)} kWh
</td>
${showCosts
? html`<td class="mdc-data-table__cell"></td>`
: ""}
</tr>`
: ""}
${types.grid?.map(
(source) => html`${source.flow_from.map((flow, idx) => {
const entity = this.hass.states[flow.stat_energy_from];
const energy =
calculateStatisticSumGrowth(
this._data!.stats[flow.stat_energy_from]
) || 0;
totalGrid += energy;
const cost_stat =
flow.stat_cost ||
this._data!.info.cost_sensors[flow.stat_energy_from];
const cost = cost_stat
? calculateStatisticSumGrowth(
this._data!.stats[cost_stat]
) || 0
: null;
if (cost !== null) {
totalGridCost += cost;
}
const color =
idx > 0
? rgb2hex(
lab2rgb(
labDarken(rgb2lab(hex2rgb(consumptionColor)), idx)
)
)
: consumptionColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: color,
backgroundColor: color + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entity
? computeStateName(entity)
: flow.stat_energy_from}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energy, this.hass.locale)} kWh
</td>
${showCosts
? html` <td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${cost !== null
? formatNumber(cost, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})
: ""}
</td>`
: ""}
</tr>`;
})}
${source.flow_to.map((flow, idx) => {
const entity = this.hass.states[flow.stat_energy_to];
const energy =
(calculateStatisticSumGrowth(
this._data!.stats[flow.stat_energy_to]
) || 0) * -1;
totalGrid += energy;
const cost_stat =
flow.stat_compensation ||
this._data!.info.cost_sensors[flow.stat_energy_to];
const cost = cost_stat
? (calculateStatisticSumGrowth(
this._data!.stats[cost_stat]
) || 0) * -1
: null;
if (cost !== null) {
totalGridCost += cost;
}
const color =
idx > 0
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(returnColor)), idx))
)
: returnColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: color,
backgroundColor: color + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entity ? computeStateName(entity) : flow.stat_energy_to}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energy, this.hass.locale)} kWh
</td>
${showCosts
? html` <td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${cost !== null
? formatNumber(cost, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})
: ""}
</td>`
: ""}
</tr>`;
})}`
)}
${types.grid
? html` <tr class="mdc-data-table__row total">
<td class="mdc-data-table__cell"></td>
<th class="mdc-data-table__cell" scope="row">Grid total</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(totalGrid, this.hass.locale)} kWh
</td>
${showCosts
? html`<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(totalGridCost, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})}
</td>`
: ""}
</tr>`
: ""}
${types.gas?.map((source, idx) => {
const entity = this.hass.states[source.stat_energy_from];
const energy =
calculateStatisticSumGrowth(
this._data!.stats[source.stat_energy_from]
) || 0;
totalGas += energy;
const cost_stat =
source.stat_cost ||
this._data!.info.cost_sensors[source.stat_energy_from];
const cost = cost_stat
? calculateStatisticSumGrowth(this._data!.stats[cost_stat]) ||
0
: null;
if (cost !== null) {
totalGasCost += cost;
}
const color =
idx > 0
? rgb2hex(
lab2rgb(labDarken(rgb2lab(hex2rgb(gasColor)), idx))
)
: gasColor;
return html`<tr class="mdc-data-table__row">
<td class="mdc-data-table__cell cell-bullet">
<div
class="bullet"
style=${styleMap({
borderColor: color,
backgroundColor: color + "7F",
})}
></div>
</td>
<th class="mdc-data-table__cell" scope="row">
${entity
? computeStateName(entity)
: source.stat_energy_from}
</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(energy, this.hass.locale)} ${gasUnit}
</td>
${showCosts
? html`<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${cost !== null
? formatNumber(cost, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})
: ""}
</td>`
: ""}
</tr>`;
})}
${types.gas
? html`<tr class="mdc-data-table__row total">
<td class="mdc-data-table__cell"></td>
<th class="mdc-data-table__cell" scope="row">Gas total</th>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(totalGas, this.hass.locale)} ${gasUnit}
</td>
${showCosts
? html`<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(totalGasCost, this.hass.locale, {
style: "currency",
currency: this.hass.config.currency!,
})}
</td>`
: ""}
</tr>`
: ""}
${totalGasCost && totalGridCost
? html`<tr class="mdc-data-table__row total">
<td class="mdc-data-table__cell"></td>
<th class="mdc-data-table__cell" scope="row">
Total costs
</th>
<td class="mdc-data-table__cell"></td>
<td
class="mdc-data-table__cell mdc-data-table__cell--numeric"
>
${formatNumber(
totalGasCost + totalGridCost,
this.hass.locale,
{
style: "currency",
currency: this.hass.config.currency!,
}
)}
</td>
</tr>`
: ""}
</tbody>
</table>
</div>
</div>
</ha-card>`;
}
static get styles(): CSSResultGroup {
return css`
${unsafeCSS(dataTableStyles)}
.mdc-data-table {
width: 100%;
border: 0;
}
.mdc-data-table__header-cell,
.mdc-data-table__cell {
color: var(--primary-text-color);
border-bottom-color: var(--divider-color);
}
.mdc-data-table__row:not(.mdc-data-table__row--selected):hover {
background-color: rgba(var(--rgb-primary-text-color), 0.04);
}
.total {
--mdc-typography-body2-font-weight: 500;
}
.total .mdc-data-table__cell {
border-top: 1px solid var(--divider-color);
}
ha-card {
height: 100%;
}
.card-header {
padding-bottom: 0;
}
.content {
padding: 16px;
}
.has-header {
padding-top: 0;
}
.cell-bullet {
width: 32px;
padding-right: 0;
}
.bullet {
border-width: 1px;
border-style: solid;
border-radius: 4px;
height: 16px;
width: 32px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"hui-energy-sources-table-card": HuiEnergySourcesTableCard;
}
}