121 files
This commit is contained in:
@@ -211,6 +211,14 @@ export class MaintenanceSupporterCardEditor extends LitElement {
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
|
||||
<ha-formfield label="${t("responsible_user", L)}">
|
||||
<ha-switch
|
||||
.checked=${this._config.show_assignee !== false}
|
||||
@change=${(e: Event) =>
|
||||
this._valueChanged("show_assignee", (e.target as HTMLInputElement).checked)}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
|
||||
<ha-formfield label="${t("card_compact", L)}">
|
||||
<ha-switch
|
||||
.checked=${this._config.compact || false}
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
SavedView,
|
||||
SavedViewFilters,
|
||||
} from "./types";
|
||||
import { UserService } from "./user-service";
|
||||
import "./maintenance-card-editor";
|
||||
import "./components/complete-dialog";
|
||||
import {
|
||||
@@ -33,6 +34,11 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
@state() private _stats: StatisticsResponse | null = null;
|
||||
@state() private _unsub: (() => void) | null = null;
|
||||
@state() private _viewFilters: SavedViewFilters | null = null;
|
||||
/** id → display name for the assignee badge. Empty until users/list
|
||||
* resolves; the badge stays hidden rather than showing a raw uuid. */
|
||||
@state() private _userNames: Record<string, string> = {};
|
||||
private _userService: UserService | null = null;
|
||||
private _userNamesLoaded = false;
|
||||
|
||||
private get _lang(): string {
|
||||
return this.hass?.language || "en";
|
||||
@@ -95,6 +101,19 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
if (lang && !isLocaleLoaded(lang)) {
|
||||
ensureLocale(lang).then(() => this.requestUpdate());
|
||||
}
|
||||
// Assignee names: resolved once, only when the config allows it AND some
|
||||
// task actually carries a responsible user. The condition is checked
|
||||
// synchronously here — an unconditional call whose promise triggered a
|
||||
// re-render looped the element forever. Assigning `_userNames` (@state)
|
||||
// is what re-renders once the list arrives.
|
||||
if (
|
||||
this.hass &&
|
||||
!this._userNamesLoaded &&
|
||||
this._config.show_assignee !== false &&
|
||||
this._objects.some((o) => o.tasks.some((tk) => tk.responsible_user_id))
|
||||
) {
|
||||
this._loadUserNames();
|
||||
}
|
||||
if (changedProps.has("hass") && this.hass) {
|
||||
if (!this._dataLoaded) {
|
||||
this._dataLoaded = true;
|
||||
@@ -127,6 +146,35 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
await this._loadViewFilters();
|
||||
}
|
||||
|
||||
/** Resolve display names for the assignee badge (best-effort).
|
||||
*
|
||||
* `users/list` is a READ-tier command, so the household members this card
|
||||
* is built for can call it without admin rights. A failure (or a task
|
||||
* whose user was deleted) leaves the name unresolved and the badge simply
|
||||
* does not render — never a raw user id. */
|
||||
/** Display name of the task's responsible user, or "" when the badge must
|
||||
* stay hidden (feature off, nobody assigned, or the name not resolved).
|
||||
* With a rotation this is whoever is up next — the pointer the engine
|
||||
* advances on every completion. */
|
||||
private _assigneeName(task: MaintenanceTask): string {
|
||||
if (this._config.show_assignee === false) return "";
|
||||
const id = task.responsible_user_id;
|
||||
if (!id) return "";
|
||||
return this._userNames[id] || "";
|
||||
}
|
||||
|
||||
private async _loadUserNames(): Promise<void> {
|
||||
this._userNamesLoaded = true;
|
||||
if (!this._userService) this._userService = new UserService(this.hass);
|
||||
else this._userService.updateHass(this.hass);
|
||||
try {
|
||||
const users = await this._userService.getUsers();
|
||||
this._userNames = Object.fromEntries(users.map((u) => [u.id, u.name]));
|
||||
} catch {
|
||||
// leave whatever we had; the badge hides for unresolved ids
|
||||
}
|
||||
}
|
||||
|
||||
/** Resolve the configured saved view's filters (best-effort). A missing or
|
||||
* deleted view degrades to "no view filter" — same fallback semantics as
|
||||
* the backend's notification routing, never an inexplicably empty card. */
|
||||
@@ -322,7 +370,19 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
></ha-icon>`
|
||||
: nothing}
|
||||
</div>
|
||||
${!compact ? html`<div class="task-meta">${object_name} · ${t(task.type, L)}</div>` : nothing}
|
||||
${!compact
|
||||
? html`<div class="task-meta">
|
||||
${object_name} · ${t(task.type, L)}${this._assigneeName(task)
|
||||
? html` · <span class="assignee"
|
||||
><ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(task)}</span
|
||||
>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: this._assigneeName(task)
|
||||
? html`<div class="task-meta compact-assignee" title="${this._assigneeName(task)}">
|
||||
<ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(task)}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
<div class="task-due">
|
||||
${task.days_until_due !== null && task.days_until_due !== undefined
|
||||
@@ -455,6 +515,24 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
.task-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.postponed-icon { --mdc-icon-size: 14px; color: var(--secondary-text-color); vertical-align: text-bottom; margin-inline-start: 4px; }
|
||||
.task-meta { font-size: 12px; color: var(--secondary-text-color); }
|
||||
.assignee, .compact-assignee {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.assignee ha-icon, .compact-assignee ha-icon {
|
||||
--mdc-icon-size: 13px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
/* Compact rows have no meta line of their own — keep the name from
|
||||
pushing the due column off a narrow phone card. */
|
||||
.compact-assignee {
|
||||
max-width: 11ch;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.task-due { font-size: 13px; color: var(--secondary-text-color); min-width: 40px; text-align: right; }
|
||||
.overdue-text { color: var(--error-color); font-weight: 500; }
|
||||
|
||||
@@ -324,6 +324,10 @@ export interface CardConfig {
|
||||
filter_due_max_days?: number;
|
||||
compact?: boolean;
|
||||
show_actions?: boolean;
|
||||
// Whose turn is it (v2.43): show the task's responsible user on each row.
|
||||
// Defaults to ON — with rotations the row is the only place a household
|
||||
// sees who is up next. Rows without an assignee simply render nothing.
|
||||
show_assignee?: boolean;
|
||||
// Saved-view scope (v2.26): apply a saved view's task-selecting filters
|
||||
// (status / user / label) ON TOP of the card's own filters. The view's
|
||||
// sort/group dimensions are panel display state and are not applied here.
|
||||
|
||||
Reference in New Issue
Block a user