diff --git a/src/panels/config/areas/ha-config-area-page.ts b/src/panels/config/areas/ha-config-area-page.ts index 3cddc9ba5d..527552a3ff 100644 --- a/src/panels/config/areas/ha-config-area-page.ts +++ b/src/panels/config/areas/ha-config-area-page.ts @@ -22,12 +22,16 @@ import { import { computeDeviceName, DeviceRegistryEntry, - devicesInArea, } from "../../../data/device_registry"; +import { + computeEntityRegistryName, + EntityRegistryEntry, +} from "../../../data/entity_registry"; import { findRelated, RelatedResult } from "../../../data/search"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../resources/styles"; import { HomeAssistant, Route } from "../../../types"; +import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor"; import { configSections } from "../ha-panel-config"; import { loadAreaRegistryDetailDialog, @@ -44,6 +48,8 @@ class HaConfigAreaPage extends LitElement { @property() public devices!: DeviceRegistryEntry[]; + @property() public entities!: EntityRegistryEntry[]; + @property({ type: Boolean, reflect: true }) public narrow!: boolean; @property() public isWide!: boolean; @@ -58,9 +64,39 @@ class HaConfigAreaPage extends LitElement { | AreaRegistryEntry | undefined => areas.find((area) => area.area_id === areaId)); - private _devices = memoizeOne( - (areaId: string, devices: DeviceRegistryEntry[]): DeviceRegistryEntry[] => - devicesInArea(devices, areaId) + private _memberships = memoizeOne( + ( + areaId: string, + registryDevices: DeviceRegistryEntry[], + registryEntities: EntityRegistryEntry[] + ) => { + const devices = new Map(); + + for (const device of registryDevices) { + if (device.area_id === areaId) { + devices.set(device.id, device); + } + } + + const entities: EntityRegistryEntry[] = []; + const indirectEntities: EntityRegistryEntry[] = []; + + for (const entity of registryEntities) { + if (entity.area_id) { + if (entity.area_id === areaId) { + entities.push(entity); + } + } else if (devices.has(entity.device_id)) { + indirectEntities.push(entity); + } + } + + return { + devices: Array.from(devices.values()), + entities, + indirectEntities, + }; + } ); protected firstUpdated(changedProps) { @@ -87,7 +123,11 @@ class HaConfigAreaPage extends LitElement { `; } - const devices = this._devices(this.areaId, this.devices); + const { devices, entities } = this._memberships( + this.areaId, + this.devices, + this.entities + ); return html` `} + ${entities.length + ? entities.map( + (entity) => + html` + + + ${computeEntityRegistryName(this.hass, entity)} + + + + ` + ) + : html` + ${this.hass.localize( + "ui.panel.config.areas.editor.no_linked_entities" + )} + `} +
${isComponentLoaded(this.hass, "automation") @@ -299,6 +366,14 @@ class HaConfigAreaPage extends LitElement { this._openDialog(entry); } + private _openEntity(ev) { + const entry: EntityRegistryEntry = (ev.currentTarget as any).entity; + showEntityEditorDialog(this, { + entity_id: entry.entity_id, + entry, + }); + } + private _openDialog(entry?: AreaRegistryEntry) { showAreaRegistryDetailDialog(this, { entry, diff --git a/src/panels/config/areas/ha-config-areas-dashboard.ts b/src/panels/config/areas/ha-config-areas-dashboard.ts index ad00f8e871..e07661054a 100644 --- a/src/panels/config/areas/ha-config-areas-dashboard.ts +++ b/src/panels/config/areas/ha-config-areas-dashboard.ts @@ -24,10 +24,8 @@ import { AreaRegistryEntry, createAreaRegistryEntry, } from "../../../data/area_registry"; -import { - DeviceRegistryEntry, - devicesInArea, -} from "../../../data/device_registry"; +import type { DeviceRegistryEntry } from "../../../data/device_registry"; +import type { EntityRegistryEntry } from "../../../data/entity_registry"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-loading-screen"; import "../../../layouts/hass-tabs-subpage-data-table"; @@ -53,12 +51,39 @@ export class HaConfigAreasDashboard extends LitElement { @property() public devices!: DeviceRegistryEntry[]; + @property() public entities!: EntityRegistryEntry[]; + private _areas = memoizeOne( - (areas: AreaRegistryEntry[], devices: DeviceRegistryEntry[]) => { + ( + areas: AreaRegistryEntry[], + devices: DeviceRegistryEntry[], + entities: EntityRegistryEntry[] + ) => { return areas.map((area) => { + const devicesInArea = new Set(); + + for (const device of devices) { + if (device.area_id === area.area_id) { + devicesInArea.add(device.id); + } + } + + let entitiesInArea = 0; + + for (const entity of entities) { + if ( + entity.area_id + ? entity.area_id === area.area_id + : devicesInArea.has(entity.device_id) + ) { + entitiesInArea++; + } + } + return { ...area, - devices: devicesInArea(devices, area.area_id).length, + devices: devicesInArea.size, + entities: entitiesInArea, }; }); } @@ -97,6 +122,15 @@ export class HaConfigAreasDashboard extends LitElement { width: "20%", direction: "asc", }, + entities: { + title: this.hass.localize( + "ui.panel.config.areas.data_table.entities" + ), + sortable: true, + type: "numeric", + width: "20%", + direction: "asc", + }, } ); @@ -110,7 +144,7 @@ export class HaConfigAreasDashboard extends LitElement { .tabs=${configSections.integrations} .route=${this.route} .columns=${this._columns(this.narrow)} - .data=${this._areas(this.areas, this.devices)} + .data=${this._areas(this.areas, this.devices, this.entities)} @row-click=${this._handleRowClicked} .noDataText=${this.hass.localize( "ui.panel.config.areas.picker.no_areas" diff --git a/src/panels/config/areas/ha-config-areas.ts b/src/panels/config/areas/ha-config-areas.ts index fc9ead4062..c742b304d0 100644 --- a/src/panels/config/areas/ha-config-areas.ts +++ b/src/panels/config/areas/ha-config-areas.ts @@ -15,6 +15,10 @@ import { DeviceRegistryEntry, subscribeDeviceRegistry, } from "../../../data/device_registry"; +import { + EntityRegistryEntry, + subscribeEntityRegistry, +} from "../../../data/entity_registry"; import { HassRouterPage, RouterOptions, @@ -51,6 +55,9 @@ class HaConfigAreas extends HassRouterPage { @internalProperty() private _deviceRegistryEntries: DeviceRegistryEntry[] = []; + @internalProperty() + private _entityRegistryEntries: EntityRegistryEntry[] = []; + @internalProperty() private _areas: AreaRegistryEntry[] = []; private _unsubs?: UnsubscribeFunc[]; @@ -90,6 +97,7 @@ class HaConfigAreas extends HassRouterPage { pageEl.entries = this._configEntries; pageEl.devices = this._deviceRegistryEntries; + pageEl.entities = this._entityRegistryEntries; pageEl.areas = this._areas; pageEl.narrow = this.narrow; pageEl.isWide = this.isWide; @@ -113,6 +121,9 @@ class HaConfigAreas extends HassRouterPage { subscribeDeviceRegistry(this.hass.connection, (entries) => { this._deviceRegistryEntries = entries; }), + subscribeEntityRegistry(this.hass.connection, (entries) => { + this._entityRegistryEntries = entries; + }), ]; } } diff --git a/src/translations/en.json b/src/translations/en.json index 0f1cb8de63..cf4a17345d 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -905,7 +905,8 @@ "description": "Group devices and entities into areas", "data_table": { "area": "Area", - "devices": "Devices" + "devices": "Devices", + "entities": "Entities" }, "picker": { "header": "Areas", @@ -923,7 +924,9 @@ "name": "Name", "name_required": "Name is required", "area_id": "Area ID", - "unknown_error": "Unknown error" + "unknown_error": "Unknown error", + "linked_entities_caption": "Entities", + "no_linked_entities": "There are no entities linked to this area." }, "delete": { "confirmation_title": "Are you sure you want to delete this area?",