Data tables: standardize columns (#29155)

* Create data-table-columns.ts

* Update data-table-columns.ts

* move a code for columns into separate functions

* move a code for columns into separate functions

* move a code for columns into separate functions

* move a code for columns into separate functions

* move a code for columns into separate functions

* move a code for columns into separate functions

* move a code for columns into separate functions

* move a code for columns into separate functions

* fix a translation key

* move commonly used translations to generic

* remove a translation for another PR

* restore "domain" translation for while

* resolving conflicts

* resolve conflicts

* resolving conflicts

* resolving conflicts

* resolving conflicts

* resolving conflicts

* fix conflicts

* fix conflicts

* fix import

* fix import
This commit is contained in:
ildar170975 2026-02-03 23:37:53 +03:00 committed by GitHub
parent a1c3a6c662
commit 2ab867986a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 335 additions and 449 deletions

View File

@ -19,7 +19,6 @@ import {
mdiToggleSwitchOffOutline,
mdiTransitConnection,
} from "@mdi/js";
import { differenceInDays } from "date-fns";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
@ -28,14 +27,11 @@ import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import type { LocalizeFunc } from "../../../common/translations/localize";
import {
hasRejectedItems,
@ -115,6 +111,13 @@ import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route, ServiceCallResponse } from "../../../types";
import { documentationUrl } from "../../../util/documentation-url";
import { turnOnOffEntity } from "../../lovelace/common/entity/turn-on-off-entity";
import {
getEntityIdHiddenTableColumn,
getAreaTableColumn,
getCategoryTableColumn,
getLabelsTableColumn,
getTriggeredAtTableColumn,
} from "../common/data-table-columns";
import { showAreaRegistryDetailDialog } from "../areas/show-dialog-area-registry-detail";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
@ -134,7 +137,7 @@ type AutomationItem = AutomationEntity & {
last_triggered: string | undefined;
formatted_state: string;
category: string | undefined;
labels: LabelRegistryEntry[];
label_entries: LabelRegistryEntry[];
assistants: string[];
assistants_sortable_key: string | undefined;
};
@ -269,7 +272,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,
labels: (labels || []).map(
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
assistants,
@ -284,7 +287,6 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
(
narrow: boolean,
localize: LocalizeFunc,
locale: HomeAssistant["locale"],
entitiesToCheck?: any[]
): DataTableColumnContainer<AutomationItem> => {
const columns: DataTableColumnContainer<AutomationItem> = {
@ -306,11 +308,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
})}
></ha-state-icon>`,
},
entity_id: {
title: "",
hidden: true,
filterable: true,
},
entity_id: getEntityIdHiddenTableColumn(),
name: {
title: localize("ui.panel.config.automation.picker.headers.name"),
main: true,
@ -319,59 +317,17 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
direction: "asc",
flex: 2,
extraTemplate: (automation) =>
automation.labels.length
automation.label_entries.length
? html`<ha-data-table-labels
@label-clicked=${narrow ? undefined : this._labelClicked}
.labels=${automation.labels}
.labels=${automation.label_entries}
></ha-data-table-labels>`
: nothing,
},
area: {
title: localize("ui.panel.config.automation.picker.headers.area"),
groupable: true,
filterable: true,
sortable: true,
},
category: {
title: localize("ui.panel.config.automation.picker.headers.category"),
defaultHidden: true,
groupable: true,
filterable: true,
sortable: true,
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (automation) =>
automation.labels.map((lbl) => lbl.name).join(" "),
},
last_triggered: {
sortable: true,
title: localize("ui.card.automation.last_triggered"),
template: (automation) => {
if (!automation.last_triggered) {
return this.hass.localize("ui.components.relative_time.never");
}
const date = new Date(automation.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
);
const elementId = "last-triggered-" + slugify(automation.entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}>${relativeTime(date, locale)}</span>
`}
`;
},
},
area: getAreaTableColumn(localize),
category: getCategoryTableColumn(localize),
labels: getLabelsTableColumn(),
last_triggered: getTriggeredAtTableColumn(localize, this.hass),
formatted_state: {
minWidth: "82px",
maxWidth: "82px",
@ -485,12 +441,7 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
Array.isArray(val) ? val.length : val
)
).length}
.columns=${this._columns(
this.narrow,
this.hass.localize,
this.hass.locale,
automations
)}
.columns=${this._columns(this.narrow, this.hass.localize, automations)}
.initialGroupColumn=${this._activeGrouping ?? "category"}
.initialCollapsedGroups=${this._activeCollapsed}
.initialSorting=${this._activeSorting}

View File

@ -0,0 +1,204 @@
import { html, nothing } from "lit";
import { differenceInDays } from "date-fns";
import { mdiPencilOff } from "@mdi/js";
import type { HomeAssistant } from "../../../types";
import type { LocalizeFunc } from "../../../common/translations/localize";
import type { DataTableColumnData } from "../../../components/data-table/ha-data-table";
import { slugify } from "../../../common/string/slugify";
import { relativeTime } from "../../../common/datetime/relative_time";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { isUnavailableState } from "../../../data/entity/entity";
import "../../../components/ha-tooltip";
import "../../../components/ha-svg-icon";
export function getEntityIdHiddenTableColumn<T>(): DataTableColumnData<T> {
return {
title: "",
hidden: true,
filterable: true,
};
}
export function getEntityIdTableColumn<T>(
localize: LocalizeFunc,
defaultHidden?: boolean
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.entity_id"),
defaultHidden: defaultHidden,
filterable: true,
sortable: true,
};
}
export function getDomainTableColumn<T>(
localize: LocalizeFunc
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.domain"),
hidden: true,
groupable: true,
filterable: true,
sortable: false,
};
}
export function getAreaTableColumn<T>(
localize: LocalizeFunc
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.area"),
groupable: true,
filterable: true,
sortable: true,
minWidth: "120px",
};
}
export function getFloorTableColumn<T>(
localize: LocalizeFunc
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.floor"),
defaultHidden: true,
groupable: true,
filterable: true,
sortable: true,
minWidth: "120px",
};
}
export function getCategoryTableColumn<T>(
localize: LocalizeFunc
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.category"),
defaultHidden: true,
groupable: true,
filterable: true,
sortable: true,
};
}
export function getEditableTableColumn<T>(
localize: LocalizeFunc,
tooltip: string
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.editable"),
type: "icon",
showNarrow: true,
sortable: true,
minWidth: "88px",
maxWidth: "88px",
template: (entry: any) => html`
${!entry.editable
? html`
<ha-svg-icon
.id="icon-edit-${slugify(entry.entity_id)}"
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
<ha-tooltip
.for="icon-edit-${slugify(entry.entity_id)}"
placement="left"
>
${tooltip}
</ha-tooltip>
`
: nothing}
`,
};
}
export function getLabelsTableColumn<T>(): DataTableColumnData<T> {
return {
title: "",
hidden: true,
filterable: true,
template: (entry: any) =>
entry.label_entries.map((lbl) => lbl.name).join(" "),
};
}
export function getTriggeredAtTableColumn<T>(
localize: LocalizeFunc,
hass: HomeAssistant
): DataTableColumnData<T> {
return {
title: localize("ui.card.automation.last_triggered"),
sortable: true,
template: (entry: any) =>
renderRelativeTimeColumn(
entry.last_triggered,
"last-triggered",
entry.entity_id,
localize,
hass
),
};
}
export const renderRelativeTimeColumn = (
valueRelativeTime: string,
valueName: string,
entity_id: string,
localize: LocalizeFunc,
hass: HomeAssistant
) => {
if (!valueRelativeTime || isUnavailableState(valueRelativeTime)) {
return localize("ui.components.relative_time.never");
}
const date = new Date(valueRelativeTime);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
hass.locale,
hass.config
);
const elementId = valueName + "-" + slugify(entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}>${relativeTime(date, hass.locale)}</span>
`}
`;
};
export function getCreatedAtTableColumn<T>(
localize: LocalizeFunc,
hass: HomeAssistant
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.created_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (entry: any) => renderDateTimeColumn(entry.created_at, hass),
};
}
export function getModifiedAtTableColumn<T>(
localize: LocalizeFunc,
hass: HomeAssistant
): DataTableColumnData<T> {
return {
title: localize("ui.panel.config.generic.headers.modified_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (entry: any) => renderDateTimeColumn(entry.modified_at, hass),
};
}
const renderDateTimeColumn = (valueDateTime: number, hass: HomeAssistant) =>
html`${valueDateTime
? formatShortDateTimeWithConditionalYear(
new Date(valueDateTime * 1000),
hass.locale,
hass.config
)
: nothing}`;

View File

@ -16,7 +16,6 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
@ -96,6 +95,13 @@ import { configSections } from "../ha-panel-config";
import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
getAreaTableColumn,
getFloorTableColumn,
getLabelsTableColumn,
getCreatedAtTableColumn,
getModifiedAtTableColumn,
} from "../common/data-table-columns";
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
interface DeviceRowData extends DeviceRegistryEntry {
@ -103,7 +109,7 @@ interface DeviceRowData extends DeviceRegistryEntry {
area?: string;
integration?: string;
battery_entity?: [string | undefined, string | undefined];
label_entries: EntityRegistryEntry[];
label_entries: LabelRegistryEntry[];
}
@customElement("ha-config-devices-dashboard")
@ -450,7 +456,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
);
let floorName = "—";
let floorName;
if (
device.area_id &&
areas[device.area_id]?.floor_id &&
@ -556,22 +562,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
: nothing}
`,
},
area: {
title: localize("ui.panel.config.devices.data_table.area"),
sortable: true,
filterable: true,
groupable: true,
minWidth: "120px",
template: (device) => device.area || "—",
},
floor: {
title: localize("ui.panel.config.devices.data_table.floor"),
sortable: true,
filterable: true,
groupable: true,
minWidth: "120px",
defaultHidden: true,
},
area: getAreaTableColumn(localize),
floor: getFloorTableColumn(localize),
integration: {
title: localize("ui.panel.config.devices.data_table.integration"),
sortable: true,
@ -629,34 +621,8 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
: "—";
},
},
created_at: {
title: localize("ui.panel.config.generic.headers.created_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (entry) =>
entry.created_at
? formatShortDateTime(
new Date(entry.created_at * 1000),
this.hass.locale,
this.hass.config
)
: "—",
},
modified_at: {
title: localize("ui.panel.config.generic.headers.modified_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (entry) =>
entry.modified_at
? formatShortDateTime(
new Date(entry.modified_at * 1000),
this.hass.locale,
this.hass.config
)
: "—",
},
created_at: getCreatedAtTableColumn(localize, this.hass),
modified_at: getModifiedAtTableColumn(localize, this.hass),
disabled_by: {
title: localize("ui.panel.config.devices.picker.state"),
type: "icon",
@ -685,13 +651,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
`
: "—",
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (device) =>
device.label_entries.map((lbl) => lbl.name).join(" "),
},
labels: getLabelsTableColumn(),
} as DataTableColumnContainer<DeviceItem>;
});

View File

@ -24,7 +24,6 @@ import { ifDefined } from "lit/directives/if-defined";
import { styleMap } from "lit/directives/style-map";
import memoize from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeAreaName } from "../../../common/entity/compute_area_name";
@ -120,6 +119,14 @@ import { isHelperDomain } from "../helpers/const";
import "../integrations/ha-integration-overflow-menu";
import { showAddIntegrationDialog } from "../integrations/show-add-integration-dialog";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
getEntityIdTableColumn,
getDomainTableColumn,
getAreaTableColumn,
getLabelsTableColumn,
getCreatedAtTableColumn,
getModifiedAtTableColumn,
} from "../common/data-table-columns";
import {
getAssistantsSortableKey,
getAssistantsTableColumn,
@ -376,32 +383,15 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
groupable: true,
hidden: true,
},
area: {
title: localize("ui.panel.config.entities.picker.headers.area"),
sortable: true,
filterable: true,
groupable: true,
template: (entry) => entry.area || "—",
},
entity_id: {
title: localize("ui.panel.config.entities.picker.headers.entity_id"),
sortable: true,
filterable: true,
defaultHidden: true,
},
area: getAreaTableColumn(localize),
entity_id: getEntityIdTableColumn(localize, true),
localized_platform: {
title: localize("ui.panel.config.entities.picker.headers.integration"),
sortable: true,
groupable: true,
filterable: true,
},
domain: {
title: localize("ui.panel.config.entities.picker.headers.domain"),
sortable: false,
hidden: true,
filterable: true,
groupable: true,
},
domain: getDomainTableColumn(localize),
disabled_by: {
title: localize("ui.panel.config.entities.picker.headers.disabled_by"),
hidden: true,
@ -475,34 +465,8 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
`
: "—",
},
created_at: {
title: localize("ui.panel.config.generic.headers.created_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (entry) =>
entry.created_at
? formatShortDateTimeWithConditionalYear(
new Date(entry.created_at * 1000),
this.hass.locale,
this.hass.config
)
: "—",
},
modified_at: {
title: localize("ui.panel.config.generic.headers.modified_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (entry) =>
entry.modified_at
? formatShortDateTimeWithConditionalYear(
new Date(entry.modified_at * 1000),
this.hass.locale,
this.hass.config
)
: "—",
},
created_at: getCreatedAtTableColumn(localize, this.hass),
modified_at: getModifiedAtTableColumn(localize, this.hass),
available: {
title: localize("ui.panel.config.entities.picker.headers.availability"),
sortable: true,
@ -521,13 +485,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
groupable: true,
hidden: true,
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (entry) =>
entry.label_entries.map((lbl) => lbl.name).join(" "),
},
labels: getLabelsTableColumn(),
assistants: getAssistantsTableColumn(
localize,
this.hass,

View File

@ -9,7 +9,6 @@ import {
mdiDotsVertical,
mdiDownload,
mdiMenuDown,
mdiPencilOff,
mdiPlus,
mdiProgressHelper,
mdiTag,
@ -27,7 +26,6 @@ import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { computeAreaName } from "../../../common/entity/compute_area_name";
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import type {
LocalizeFunc,
LocalizeKeys,
@ -118,6 +116,13 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import { fileDownload } from "../../../util/file_download";
import {
getEntityIdTableColumn,
getAreaTableColumn,
getCategoryTableColumn,
getLabelsTableColumn,
getEditableTableColumn,
} from "../common/data-table-columns";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config";
@ -360,68 +365,20 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
`
: nothing,
},
entity_id: {
title: localize("ui.panel.config.helpers.picker.headers.entity_id"),
sortable: true,
filterable: true,
},
category: {
title: localize("ui.panel.config.helpers.picker.headers.category"),
hidden: true,
groupable: true,
filterable: true,
sortable: true,
},
area: {
title: localize("ui.panel.config.helpers.picker.headers.area"),
sortable: true,
filterable: true,
groupable: true,
template: (helper) => helper.area || "—",
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (helper) =>
helper.label_entries.map((lbl) => lbl.name).join(" "),
},
entity_id: getEntityIdTableColumn(localize),
category: getCategoryTableColumn(localize),
area: getAreaTableColumn(localize),
labels: getLabelsTableColumn(),
localized_type: {
title: localize("ui.panel.config.helpers.picker.headers.type"),
sortable: true,
filterable: true,
groupable: true,
},
editable: {
title: localize("ui.panel.config.helpers.picker.headers.editable"),
type: "icon",
sortable: true,
minWidth: "88px",
maxWidth: "88px",
showNarrow: true,
template: (helper) => html`
${!helper.editable
? html`
<div
tabindex="0"
style="display:inline-block; position: relative;"
>
<ha-svg-icon
.id="icon-edit-${slugify(helper.entity_id)}"
.path=${mdiPencilOff}
></ha-svg-icon>
<ha-tooltip
.for="icon-edit-${slugify(helper.entity_id)}"
placement="left"
>${this.hass.localize(
"ui.panel.config.entities.picker.status.unmanageable"
)}
</ha-tooltip>
</div>
`
: ""}
`,
},
editable: getEditableTableColumn(
localize,
localize("ui.panel.config.entities.picker.status.unmanageable")
),
actions: {
title: "",
label: this.hass.localize("ui.panel.config.generic.headers.actions"),

View File

@ -12,7 +12,6 @@ import type { PropertyValues } from "lit";
import { LitElement, html, nothing } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { formatShortDateTime } from "../../../common/datetime/format_date_time";
import { storage } from "../../../common/decorators/storage";
import { navigate } from "../../../common/navigate";
import type { LocalizeFunc } from "../../../common/translations/localize";
@ -47,6 +46,10 @@ import "../../../layouts/hass-tabs-subpage-data-table";
import type { HomeAssistant, Route } from "../../../types";
import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "./show-dialog-label-detail";
import {
getCreatedAtTableColumn,
getModifiedAtTableColumn,
} from "../common/data-table-columns";
@customElement("ha-config-labels")
export class HaConfigLabels extends LitElement {
@ -135,34 +138,8 @@ export class HaConfigLabels extends LitElement {
filterable: true,
hideable: true,
},
created_at: {
title: localize("ui.panel.config.generic.headers.created_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (label) =>
label.created_at
? formatShortDateTime(
new Date(label.created_at * 1000),
this.hass.locale,
this.hass.config
)
: "—",
},
modified_at: {
title: localize("ui.panel.config.generic.headers.modified_at"),
defaultHidden: true,
sortable: true,
minWidth: "128px",
template: (label) =>
label.modified_at
? formatShortDateTime(
new Date(label.modified_at * 1000),
this.hass.locale,
this.hass.config
)
: "—",
},
created_at: getCreatedAtTableColumn(localize, this.hass),
modified_at: getModifiedAtTableColumn(localize, this.hass),
actions: {
title: "",
label: localize("ui.panel.config.generic.headers.actions"),

View File

@ -12,27 +12,22 @@ import {
mdiOpenInNew,
mdiPalette,
mdiPencil,
mdiPencilOff,
mdiPlay,
mdiPlus,
mdiTag,
mdiTextureBox,
} from "@mdi/js";
import { differenceInDays } from "date-fns";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import type { LocalizeFunc } from "../../../common/translations/localize";
import {
hasRejectedItems,
@ -79,7 +74,6 @@ import {
isRelatedItemsFilterUsed,
serializeFilters,
} from "../../../data/data_table_filters";
import { isUnavailableState } from "../../../data/entity/entity";
import type {
EntityRegistryEntry,
UpdateEntityRegistryEntryResult,
@ -116,6 +110,13 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
getAreaTableColumn,
getCategoryTableColumn,
getLabelsTableColumn,
getEditableTableColumn,
renderRelativeTimeColumn,
} from "../common/data-table-columns";
import {
getAssistantsSortableKey,
getAssistantsTableColumn,
@ -128,9 +129,10 @@ type SceneItem = SceneEntity & {
name: string;
area: string | undefined;
category: string | undefined;
labels: LabelRegistryEntry[];
label_entries: LabelRegistryEntry[];
assistants: string[];
assistants_sortable_key: string | undefined;
editable: boolean;
};
@customElement("ha-scene-dashboard")
@ -257,12 +259,13 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,
labels: (labels || []).map(
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
assistants,
assistants_sortable_key: getAssistantsSortableKey(assistants),
selectable: entityRegEntry !== undefined,
editable: Boolean(scene.attributes.id),
};
});
}
@ -295,89 +298,34 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
direction: "asc",
flex: 2,
extraTemplate: (scene) =>
scene.labels.length
scene.label_entries.length
? html`<ha-data-table-labels
@label-clicked=${this._labelClicked}
.labels=${scene.labels}
.labels=${scene.label_entries}
></ha-data-table-labels>`
: nothing,
},
area: {
title: localize("ui.panel.config.scene.picker.headers.area"),
groupable: true,
filterable: true,
sortable: true,
},
category: {
title: localize("ui.panel.config.scene.picker.headers.category"),
defaultHidden: true,
groupable: true,
filterable: true,
sortable: true,
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (scene) => scene.labels.map((lbl) => lbl.name).join(" "),
},
area: getAreaTableColumn(localize),
category: getCategoryTableColumn(localize),
labels: getLabelsTableColumn(),
state: {
title: localize(
"ui.panel.config.scene.picker.headers.last_activated"
),
sortable: true,
template: (scene) => {
const lastActivated = scene.state;
if (!lastActivated || isUnavailableState(lastActivated)) {
return localize("ui.components.relative_time.never");
}
const date = new Date(scene.state);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
);
const elementId = "last-activated-" + slugify(scene.entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}
>${relativeTime(date, this.hass.locale)}</span
>
`}
`;
},
},
only_editable: {
title: "",
label: this.hass.localize(
"ui.panel.config.scene.picker.headers.editable"
),
type: "icon",
showNarrow: true,
template: (scene) =>
!scene.attributes.id
? html`
<ha-svg-icon
.id="svg-icon-${slugify(scene.entity_id)}"
.path=${mdiPencilOff}
style="color: var(--secondary-text-color)"
></ha-svg-icon>
<ha-tooltip
.for="svg-icon-${slugify(scene.entity_id)}"
placement="left"
>
${this.hass.localize(
"ui.panel.config.scene.picker.only_editable"
)}
</ha-tooltip>
`
: nothing,
renderRelativeTimeColumn(
scene.state,
"last-activated",
scene.entity_id,
localize,
this.hass
),
},
only_editable: getEditableTableColumn(
localize,
localize("ui.panel.config.scene.picker.only_editable")
),
actions: {
title: "",
label: this.hass.localize("ui.panel.config.generic.headers.actions"),
@ -424,7 +372,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
"ui.panel.config.scene.editor.rename"
),
action: () => this._rename(scene),
disabled: !scene.attributes.id,
disabled: !scene.editable,
},
{
divider: true,
@ -435,7 +383,7 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
"ui.panel.config.scene.picker.duplicate"
),
action: () => this._duplicate(scene),
disabled: !scene.attributes.id,
disabled: !scene.editable,
},
{
label: this.hass.localize(
@ -443,8 +391,8 @@ class HaSceneDashboard extends SubscribeMixin(LitElement) {
),
path: mdiDelete,
action: () => this._deleteConfirm(scene),
warning: scene.attributes.id,
disabled: !scene.attributes.id,
warning: scene.editable,
disabled: !scene.editable,
},
]}
>
@ -1066,7 +1014,7 @@ ${rejected
}
}
private async _duplicate(scene) {
private async _duplicate(scene: SceneEntity) {
if (scene.attributes.id) {
const config = await getSceneConfig(this.hass, scene.attributes.id);
const entityRegEntry = this._entityReg.find(

View File

@ -17,7 +17,6 @@ import {
mdiTextureBox,
mdiTransitConnection,
} from "@mdi/js";
import { differenceInDays } from "date-fns";
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
import { LitElement, css, html, nothing } from "lit";
@ -26,14 +25,11 @@ import { styleMap } from "lit/directives/style-map";
import memoizeOne from "memoize-one";
import { computeCssColor } from "../../../common/color/compute-color";
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
import { formatShortDateTimeWithConditionalYear } from "../../../common/datetime/format_date_time";
import { relativeTime } from "../../../common/datetime/relative_time";
import { storage } from "../../../common/decorators/storage";
import type { HASSDomEvent } from "../../../common/dom/fire_event";
import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { navigate } from "../../../common/navigate";
import { slugify } from "../../../common/string/slugify";
import type { LocalizeFunc } from "../../../common/translations/localize";
import {
hasRejectedItems,
@ -116,6 +112,13 @@ import { showAreaRegistryDetailDialog } from "../areas/show-dialog-area-registry
import { showNewAutomationDialog } from "../automation/show-dialog-new-automation";
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
import { showCategoryRegistryDetailDialog } from "../category/show-dialog-category-registry-detail";
import {
getEntityIdHiddenTableColumn,
getAreaTableColumn,
getCategoryTableColumn,
getLabelsTableColumn,
getTriggeredAtTableColumn,
} from "../common/data-table-columns";
import { configSections } from "../ha-panel-config";
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
import {
@ -130,7 +133,7 @@ type ScriptItem = ScriptEntity & {
area: string | undefined;
last_triggered: string | undefined;
category: string | undefined;
labels: LabelRegistryEntry[];
label_entries: LabelRegistryEntry[];
assistants: string[];
assistants_sortable_key: string | undefined;
};
@ -264,7 +267,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
category: category
? categoryReg?.find((cat) => cat.category_id === category)?.name
: undefined,
labels: (labels || []).map(
label_entries: (labels || []).map(
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
),
assistants,
@ -297,6 +300,7 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
})}
></ha-state-icon>`,
},
entity_id: getEntityIdHiddenTableColumn(),
name: {
title: localize("ui.panel.config.script.picker.headers.name"),
main: true,
@ -305,60 +309,17 @@ class HaScriptPicker extends SubscribeMixin(LitElement) {
direction: "asc",
flex: 2,
extraTemplate: (script) =>
script.labels.length
script.label_entries.length
? html`<ha-data-table-labels
@label-clicked=${this._labelClicked}
.labels=${script.labels}
.labels=${script.label_entries}
></ha-data-table-labels>`
: nothing,
},
area: {
title: localize("ui.panel.config.script.picker.headers.area"),
groupable: true,
filterable: true,
sortable: true,
},
category: {
title: localize("ui.panel.config.script.picker.headers.category"),
defaultHidden: true,
groupable: true,
filterable: true,
sortable: true,
},
labels: {
title: "",
hidden: true,
filterable: true,
template: (script) => script.labels.map((lbl) => lbl.name).join(" "),
},
last_triggered: {
sortable: true,
title: localize("ui.card.automation.last_triggered"),
template: (script) => {
if (!script.last_triggered) {
return this.hass.localize("ui.components.relative_time.never");
}
const date = new Date(script.last_triggered);
const now = new Date();
const dayDifference = differenceInDays(now, date);
const formattedTime = formatShortDateTimeWithConditionalYear(
date,
this.hass.locale,
this.hass.config
);
const elementId = "last-triggered-" + slugify(script.entity_id);
return html`
${dayDifference > 3
? formattedTime
: html`
<ha-tooltip for=${elementId}>${formattedTime}</ha-tooltip>
<span id=${elementId}
>${relativeTime(date, this.hass.locale)}</span
>
`}
`;
},
},
area: getAreaTableColumn(localize),
category: getCategoryTableColumn(localize),
labels: getLabelsTableColumn(),
last_triggered: getTriggeredAtTableColumn(localize, this.hass),
actions: {
title: "",
label: this.hass.localize("ui.panel.config.generic.headers.actions"),

View File

@ -16,9 +16,7 @@ export function getAssistantsTableColumn<T>(
visible?: boolean
): DataTableColumnData<T> {
return {
title: localize(
"ui.panel.config.voice_assistants.expose.headers.assistants"
),
title: localize("ui.panel.config.generic.headers.assistants"),
type: "flex",
defaultHidden: !visible,
sortable: true,

View File

@ -49,6 +49,11 @@ import "../../../layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../resources/styles";
import type { HomeAssistant, Route } from "../../../types";
import {
getEntityIdTableColumn,
getDomainTableColumn,
getAreaTableColumn,
} from "../common/data-table-columns";
import "./expose/expose-assistant-icon";
import {
getAssistantsTableColumn,
@ -178,30 +183,9 @@ export class VoiceAssistantsExpose extends LitElement {
direction: "asc",
flex: 2,
},
area: {
title: localize("ui.panel.config.voice_assistants.expose.headers.area"),
sortable: true,
groupable: true,
filterable: true,
template: (entry) => entry.area || "—",
},
entity_id: {
title: localize(
"ui.panel.config.voice_assistants.expose.headers.entity_id"
),
sortable: true,
filterable: true,
defaultHidden: true,
},
domain: {
title: localize(
"ui.panel.config.voice_assistants.expose.headers.domain"
),
sortable: false,
hidden: true,
filterable: true,
groupable: true,
},
area: getAreaTableColumn(localize),
entity_id: getEntityIdTableColumn(localize, true),
domain: getDomainTableColumn(localize),
assistants: getAssistantsTableColumn(
localize,
this.hass,

View File

@ -2329,6 +2329,13 @@
"config": {
"generic": {
"headers": {
"entity_id": "Entity ID",
"domain": "Domain",
"area": "Area",
"floor": "Floor",
"category": "Category",
"assistants": "Assistants",
"editable": "Editable",
"modified_at": "Modified",
"created_at": "Created",
"actions": "Actions"
@ -3964,11 +3971,7 @@
"headers": {
"icon": "Icon",
"name": "Name",
"entity_id": "Entity ID",
"type": "Type",
"editable": "Editable",
"category": "Category",
"area": "Area"
"type": "Type"
},
"create_helper": "Create helper",
"no_helpers": "Looks like you don't have any helpers yet!",
@ -4425,10 +4428,6 @@
"headers": {
"icon": "Icon",
"name": "Name",
"entity_id": "Entity ID",
"area": "Area",
"domain": "Domain",
"assistants": "Assistants",
"aliases": "Aliases",
"remove": "[%key:ui::common::remove%]"
},
@ -4598,8 +4597,6 @@
"trigger": "Trigger",
"actions": "Actions",
"state": "State",
"category": "Category",
"area": "Area",
"icon": "Icon"
},
"bulk_action": "Action",
@ -5619,8 +5616,6 @@
"headers": {
"name": "Name",
"state": "State",
"category": "Category",
"area": "Area",
"icon": "Icon"
},
"edit_category": "[%key:ui::panel::config::automation::picker::edit_category%]",
@ -5746,9 +5741,6 @@
"state": "State",
"name": "Name",
"last_activated": "Last activated",
"category": "Category",
"editable": "[%key:ui::panel::config::helpers::picker::headers::editable%]",
"area": "Area",
"icon": "Icon"
},
"edit_category": "[%key:ui::panel::config::automation::picker::edit_category%]",
@ -6142,8 +6134,6 @@
"device": "Device",
"manufacturer": "Manufacturer",
"model": "Model",
"area": "Area",
"floor": "Floor",
"integration": "Integration",
"battery": "Battery",
"disabled_by": "Disabled",
@ -6201,10 +6191,8 @@
"headers": {
"state_icon": "State icon",
"entity": "Entity",
"entity_id": "Entity ID",
"device": "Device",
"integration": "Integration",
"area": "Area",
"disabled_by": "Disabled by",
"status": "Status",
"domain": "Domain",