Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/chips/ha-filter-chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export class HaFilterChip extends FilterChip {
);
--_label-text-font: var(--ha-font-family-body);
}
:host([no-leading-icon]) {
--_with-leading-icon-leading-space: var(--_leading-space);
}
Comment on lines +35 to +37
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added this to fix an issue where the left spacing was wrong when the button was selected

Image

`,
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,8 @@ export class HomeOverviewViewStrategy extends ReactiveElement {
tap_action: {
action: "navigate",
navigation_path: config.home_panel
? "/maintenance?historyBack=1&backPath=/home"
: "/maintenance?historyBack=1",
? "/maintenance?historyBack=1&backPath=/home&issues"
: "/maintenance?historyBack=1&issues",
},
} satisfies HomeSummaryCard)
: undefined,
Expand Down
86 changes: 78 additions & 8 deletions src/panels/maintenance/ha-panel-maintenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@ import type { CSSResultGroup, PropertyValues } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import {
findEntities,
generateEntityFilter,
} from "../../common/entity/entity_filter";
import { goBack } from "../../common/navigate";
import { debounce } from "../../common/util/debounce";
import { deepEqual } from "../../common/util/deep-equal";
import "../../components/chips/ha-filter-chip";
import "../../components/ha-icon-button-arrow-prev";
import "../../components/ha-menu-button";
import type { LovelaceStrategyViewConfig } from "../../data/lovelace/config/view";
Expand All @@ -15,12 +20,10 @@ import type { Lovelace } from "../lovelace/types";
import "../lovelace/views/hui-view";
import "../lovelace/views/hui-view-background";
import "../lovelace/views/hui-view-container";

const MAINTENANCE_LOVELACE_VIEW_CONFIG: LovelaceStrategyViewConfig = {
strategy: {
type: "maintenance",
},
};
import {
filterProblematicBatteryEntities,
maintenanceEntityFilters,
} from "./strategies/maintenance-view-strategy";

@customElement("ha-panel-maintenance")
class PanelMaintenance extends LitElement {
Expand All @@ -34,6 +37,19 @@ class PanelMaintenance extends LitElement {

@state() private _searchParams = new URLSearchParams(window.location.search);

private get _issuesOnly(): boolean {
return this._searchParams.has("issues");
}

private get _issueCount(): number {
const allEntities = Object.keys(this.hass.states);
const batteryFilters = maintenanceEntityFilters.map((filter) =>
generateEntityFilter(this.hass, filter)
);
const batteryEntities = findEntities(allEntities, batteryFilters);
return filterProblematicBatteryEntities(this.hass, batteryEntities).length;
}

public willUpdate(changedProps: PropertyValues<this>) {
super.willUpdate(changedProps);
// Initial setup
Expand Down Expand Up @@ -95,6 +111,19 @@ class PanelMaintenance extends LitElement {
goBack();
}

private _toggleIssuesOnly() {
const params = new URLSearchParams(window.location.search);
if (params.has("issues")) {
params.delete("issues");
} else {
params.set("issues", "");
}
const newUrl = `${window.location.pathname}?${params.toString()}`;
history.replaceState(null, "", newUrl);
this._searchParams = params;
this._setLovelace();
}

protected render() {
return html`
<div class="header ${classMap({ narrow: this.narrow })}">
Expand All @@ -116,6 +145,20 @@ class PanelMaintenance extends LitElement {
<div class="main-title">
${this.hass.localize("panel.maintenance")}
</div>
<div class="relative">
<ha-filter-chip
no-leading-icon
.selected=${this._issuesOnly}
@click=${this._toggleIssuesOnly}
>
${this.hass.localize(
"ui.panel.lovelace.strategy.maintenance.issues_only"
)}
</ha-filter-chip>
${this._issueCount > 0
? html`<div class="badge">${this._issueCount}</div>`
: nothing}
</div>
</div>
</div>
${this._lovelace
Expand All @@ -135,13 +178,20 @@ class PanelMaintenance extends LitElement {
}

private async _setLovelace() {
const strategyConfig: LovelaceStrategyViewConfig = {
strategy: {
type: "maintenance",
issues_only: this._issuesOnly,
},
};

const viewConfig = await generateLovelaceViewStrategy(
MAINTENANCE_LOVELACE_VIEW_CONFIG,
strategyConfig,
this.hass
);

const config = { views: [viewConfig] };
const rawConfig = { views: [MAINTENANCE_LOVELACE_VIEW_CONFIG] };
const rawConfig = { views: [strategyConfig] };

if (deepEqual(config, this._lovelace?.config)) {
return;
Expand Down Expand Up @@ -229,6 +279,26 @@ class PanelMaintenance extends LitElement {
.narrow .main-title {
margin-inline-start: var(--ha-space-2);
}
.relative {
position: relative;
}
.badge {
position: absolute;
top: -4px;
right: -4px;
inset-inline-end: -4px;
inset-inline-start: initial;
min-width: 16px;
box-sizing: border-box;
border-radius: var(--ha-border-radius-circle);
font-size: var(--ha-font-size-xs);
font-weight: var(--ha-font-weight-normal);
background-color: var(--primary-color);
line-height: var(--ha-line-height-normal);
text-align: center;
padding: 0px 2px;
color: var(--text-primary-color);
}
Comment on lines +285 to +301
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

copied from src/layouts/hass-tabs-subpage-data-table.ts

hui-view-container {
position: relative;
display: flex;
Expand Down
26 changes: 22 additions & 4 deletions src/panels/maintenance/strategies/maintenance-view-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { TileCardConfig } from "../../lovelace/cards/types";

export interface MaintenanceViewStrategyConfig {
type: "maintenance";
issues_only?: boolean;
}

export const maintenanceEntityFilters: EntityFilter[] = [
Expand Down Expand Up @@ -48,6 +49,15 @@ export const filterUnavailableBatteryEntities = (
return hass.states[entityId]?.state === "unavailable";
});

export const filterProblematicBatteryEntities = (
hass: HomeAssistant,
entityIds: string[]
): string[] => {
const low = filterLowBatteryEntities(hass, entityIds);
const unavailable = filterUnavailableBatteryEntities(hass, entityIds);
return [...new Set([...low, ...unavailable])];
};

const computeBatteryTileCard = (entityId: string): TileCardConfig => ({
type: "tile",
entity: entityId,
Expand Down Expand Up @@ -114,7 +124,7 @@ const processUnassignedEntities = (
@customElement("maintenance-view-strategy")
export class MaintenanceViewStrategy extends ReactiveElement {
static async generate(
_config: MaintenanceViewStrategyConfig,
config: MaintenanceViewStrategyConfig,
hass: HomeAssistant
): Promise<LovelaceViewConfig> {
const areas = Object.values(hass.areas);
Expand All @@ -131,6 +141,10 @@ export class MaintenanceViewStrategy extends ReactiveElement {

const entities = findEntities(allEntities, batteryFilters);

const filteredEntities = config.issues_only
? filterProblematicBatteryEntities(hass, entities)
: entities;

const floorCount =
hierarchy.floors.length + (hierarchy.areas.length ? 1 : 0);

Expand All @@ -155,7 +169,7 @@ export class MaintenanceViewStrategy extends ReactiveElement {
],
};

const areaCards = processAreasForBattery(areaIds, hass, entities);
const areaCards = processAreasForBattery(areaIds, hass, filteredEntities);

if (areaCards.length > 0) {
section.cards!.push(...areaCards);
Expand All @@ -181,7 +195,11 @@ export class MaintenanceViewStrategy extends ReactiveElement {
],
};

const areaCards = processAreasForBattery(hierarchy.areas, hass, entities);
const areaCards = processAreasForBattery(
hierarchy.areas,
hass,
filteredEntities
);

if (areaCards.length > 0) {
section.cards!.push(...areaCards);
Expand All @@ -190,7 +208,7 @@ export class MaintenanceViewStrategy extends ReactiveElement {
}

// Process unassigned entities
const unassignedCards = processUnassignedEntities(hass, entities);
const unassignedCards = processUnassignedEntities(hass, filteredEntities);

if (unassignedCards.length > 0) {
const section: LovelaceSectionRawConfig = {
Expand Down
3 changes: 2 additions & 1 deletion src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -8444,7 +8444,8 @@
},
"maintenance": {
"devices": "Devices",
"other_devices": "Other devices"
"other_devices": "Other devices",
"issues_only": "Issues"
Copy link
Copy Markdown
Contributor Author

@Brookke Brookke May 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I, in a previous PR, swapped the maintenance summary card to say:

X low batteries, X unavailable devices

Here I have used issues. I could add two separate filter buttons one for low batteries one for unavailable devices. But longer term I wonder if just a single issues button is better.

},
"home_media_players": {
"media_players": "Media players",
Expand Down
Loading