291 files

This commit is contained in:
Home Assistant Version Control
2026-10-14 18:10:18 +00:00
parent c4e0178d8a
commit 280501d2e8
291 changed files with 32540 additions and 3280 deletions
@@ -11,6 +11,12 @@
* under another name starts unticked with that task's name; a summary says
* per object how many tasks it has before and after. Typing another object
* asks the server again (problem_sensors/preview).
*
* #211 (54 problem sensors of 18 devices, on a phone): the summary grew a
* line per object and squeezed the list, the only part that could shrink,
* to nothing. The dialog now sits in the shared modal frame: the title and
* the buttons stay, everything between them scrolls as one, and the summary
* folds to its totals once it names more than three objects.
*/
import { css, html, LitElement, nothing } from "lit";
@@ -18,6 +24,7 @@ import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf } from "../styles";
import { runWs } from "../helpers/ws-run";
import { focusModalShell, modalShellStyles, renderModalShell } from "../helpers/modal-shell";
import { TRIGGER_FOR_MINUTES_RANGE } from "../helpers/setting-ranges";
import { UserService } from "../user-service";
import type { HAUser, HomeAssistant } from "../types";
@@ -52,6 +59,16 @@ interface AdoptResponse {
errors?: string[];
}
/** One object of the summary: the tasks it gains (entry null = a new object). */
interface SummaryGroup {
name: string;
entry: string | null;
adding: number;
}
/** Up to this many objects the summary opens with its lines shown. */
const SUMMARY_LINES_OPEN = 3;
export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -74,6 +91,9 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
// #136: minutes the problem must persist before the created task triggers
// (one field applied to every selection). "0" = react to the first flicker.
@state() private _forMinutes = "0";
/** #211: the summary's lines shown; decided when the list arrives (folded
* beyond three objects), then the user's. */
@state() private _summaryOpen = true;
private _localeReady = false;
private _userService: UserService | null = null;
@@ -87,6 +107,7 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
this._localeReady = true;
ensureLocale(this._lang).then(() => this.requestUpdate());
}
if (changed.has("_open") && this._open) focusModalShell(this.shadowRoot);
}
public async open(): Promise<void> {
@@ -125,6 +146,7 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
// What the target probably already watches starts unticked.
this._selected = new Set(this._sensors.filter((s) => !s.covered_by).map((s) => s.entity_id));
this._users = users;
this._summaryOpen = this._summaryGroups().length <= SUMMARY_LINES_OPEN;
}
/** The default object: the one linked to the device, else the one the
@@ -169,9 +191,9 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
if (resp?.covered) this._covered = { ...this._covered, ...resp.covered };
}
/** Per target object: tasks before → after (only ticked rows count). */
private _summary(L: string) {
const groups = new Map<string, { name: string; entry: string | null; adding: number }>();
/** The objects the ticked rows go to, each with the tasks it gains. */
private _summaryGroups(): SummaryGroup[] {
const groups = new Map<string, SummaryGroup>();
for (const s of this._sensors) {
if (!this._selected.has(s.entity_id)) continue;
const entry = this._existingEntryFor(s);
@@ -181,11 +203,30 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
g.adding += 1;
groups.set(key, g);
}
if (groups.size === 0) return nothing;
return [...groups.values()];
}
/** Per target object: tasks before → after (only ticked rows count), under
* a line with the totals that stays when the lines are folded (#211). */
private _summary(L: string) {
const groups = this._summaryGroups();
if (groups.length === 0) return nothing;
const created = groups.filter((g) => !g.entry).length;
const totals = t(created ? "adopt_summary_totals_new" : "adopt_summary_totals", L)
.replace("{tasks}", String(groups.reduce((n, g) => n + g.adding, 0)))
.replace("{objects}", String(groups.length))
.replace("{new}", String(created));
return html`
<div class="summary">
<div class="summary-title">${t("adopt_summary_title", L)}</div>
${[...groups.values()].map((g) => {
<details
class="summary"
.open=${this._summaryOpen}
@toggle=${(e: Event) => { this._summaryOpen = (e.target as HTMLDetailsElement).open; }}
>
<summary>
<span class="summary-title">${t("adopt_summary_title", L)}</span>
<span class="summary-totals">${totals}</span>
</summary>
${groups.map((g) => {
if (!g.entry) {
return html`<div class="summary-line new">
${t("setups_new_object_count", L).replace("{name}", g.name).replace("{count}", String(g.adding))}
@@ -199,7 +240,7 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
.replace("{after}", String(before + g.adding))}
</div>`;
})}
</div>
</details>
`;
}
@@ -269,13 +310,12 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
const allSelected =
this._sensors.length > 0 && this._selected.size === this._sensors.length;
return html`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${(e: Event) => e.stopPropagation()}>
<div class="title">${t("adopt_problem_title", L)}</div>
<div class="hint">${t("adopt_problem_hint", L)}</div>
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
return renderModalShell(() => this._close(), html`
<div class="title">${t("adopt_problem_title", L)}</div>
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
<div class="dialog-scroll">
<div class="hint">${t("adopt_problem_hint", L)}</div>
${this._loading
? html`<div class="loading">…</div>`
: this._sensors.length === 0
@@ -392,47 +432,28 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
</label>
`
: nothing}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel", L)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size === 0 || this._adopting}
>
${t("adopt_problem_adopt", L)}
</ha-button>
</div>
</div>
</div>
`;
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel", L)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size === 0 || this._adopting}
>
${t("adopt_problem_adopt", L)}
</ha-button>
</div>
`);
}
static styles = css`
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.card {
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: min(360px, calc(100vw - 24px));
max-width: 560px;
width: 90vw;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
static styles = [modalShellStyles, css`
:host {
--ms-modal-max-width: 560px;
--ms-modal-max-height: 90dvh;
--ms-modal-gap: 12px;
--ms-modal-z: 1000;
}
.title {
font-size: 18px;
@@ -452,6 +473,9 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
font-size: 14px;
padding: 12px 0;
}
/* Stays at the top of the scrolling middle: the count while ticking.
top: -1px + 1px more padding: on a 3x screen a fractional offset let a
line of the text scrolled under it show above it. */
.select-all {
display: flex;
align-items: center;
@@ -459,6 +483,11 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
font-size: 12px;
color: var(--secondary-text-color);
cursor: pointer;
position: sticky;
top: -1px;
z-index: 1;
padding: 5px 0 4px;
background: var(--card-background-color, var(--ha-card-background, #1c1c1c));
}
.select-all input {
cursor: pointer;
@@ -467,8 +496,6 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
display: flex;
flex-direction: column;
gap: 6px;
overflow-y: auto;
max-height: 50vh;
}
.row {
display: flex;
@@ -536,9 +563,15 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
border-top: 1px dashed var(--divider-color);
padding-top: 6px;
}
.summary > summary {
cursor: pointer;
margin-bottom: 2px;
}
.summary-title {
font-weight: 500;
margin-bottom: 2px;
}
.summary-totals {
color: var(--secondary-text-color);
}
.summary-line {
color: var(--primary-color);
@@ -594,7 +627,7 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
gap: 8px;
padding-top: 8px;
}
`;
`];
}
if (!customElements.get("maintenance-adopt-problem-sensors-dialog")) {
@@ -7,6 +7,9 @@
*
* Uses a native <input> for the name — <ha-textfield> is not registered in the
* custom-panel context, so it renders without its field (the documented trap).
*
* In the shared modal frame (#211): the list of views scrolls, the name
* field and the Close button stay.
*/
import { css, html, LitElement, nothing } from "lit";
@@ -14,6 +17,7 @@ import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf } from "../styles";
import { runWs } from "../helpers/ws-run";
import { focusModalShell, modalShellStyles, renderModalShell } from "../helpers/modal-shell";
import type { HomeAssistant, SavedView, SavedViewFilters } from "../types";
interface ViewsResponse {
@@ -41,6 +45,7 @@ export class MaintenanceSavedViewsDialog extends LitElement {
this._localeReady = true;
ensureLocale(this._lang).then(() => this.requestUpdate());
}
if (changed.has("_open") && this._open) focusModalShell(this.shadowRoot);
}
public async open(currentFilters: SavedViewFilters, views: SavedView[]): Promise<void> {
@@ -90,81 +95,58 @@ export class MaintenanceSavedViewsDialog extends LitElement {
render() {
if (!this._open) return html``;
const L = this._lang;
return html`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${(e: Event) => e.stopPropagation()}>
<div class="title">${t("views_dialog_title", L)}</div>
<div class="hint">${t("views_dialog_hint", L)}</div>
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
return renderModalShell(() => this._close(), html`
<div class="title">${t("views_dialog_title", L)}</div>
<div class="hint">${t("views_dialog_hint", L)}</div>
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
<div class="save-row">
<input
class="name-input"
type="text"
.value=${this._name}
placeholder=${t("views_name_placeholder", L)}
maxlength="60"
@input=${(e: Event) => (this._name = (e.target as HTMLInputElement).value)}
@keydown=${(e: KeyboardEvent) => {
if (e.key === "Enter") this._save();
}}
/>
<ha-button @click=${this._save} .disabled=${!this._name.trim() || this._busy}>
${t("views_save_current", L)}
</ha-button>
</div>
${this._views.length === 0
? html`<div class="empty">${t("views_none_yet", L)}</div>`
: html`
<div class="list">
${this._views.map(
(v) => html`
<div class="row">
<span class="row-name">${v.name}</span>
<ha-icon-button
.path=${"M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"}
.label=${t("delete", L)}
@click=${() => this._delete(v.id)}
></ha-icon-button>
</div>
`,
)}
</div>
`}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>${t("close", L)}</ha-button>
</div>
<div class="save-row">
<input
class="name-input"
type="text"
.value=${this._name}
placeholder=${t("views_name_placeholder", L)}
maxlength="60"
@input=${(e: Event) => (this._name = (e.target as HTMLInputElement).value)}
@keydown=${(e: KeyboardEvent) => {
if (e.key === "Enter") this._save();
}}
/>
<ha-button @click=${this._save} .disabled=${!this._name.trim() || this._busy}>
${t("views_save_current", L)}
</ha-button>
</div>
</div>
`;
${this._views.length === 0
? html`<div class="empty">${t("views_none_yet", L)}</div>`
: html`
<div class="list dialog-scroll">
${this._views.map(
(v) => html`
<div class="row">
<span class="row-name">${v.name}</span>
<ha-icon-button
.path=${"M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"}
.label=${t("delete", L)}
@click=${() => this._delete(v.id)}
></ha-icon-button>
</div>
`,
)}
</div>
`}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>${t("close", L)}</ha-button>
</div>
`);
}
static styles = css`
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.card {
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: min(360px, calc(100vw - 24px));
max-width: 480px;
width: 90vw;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
static styles = [modalShellStyles, css`
:host {
--ms-modal-max-height: 90dvh;
--ms-modal-gap: 12px;
--ms-modal-z: 1000;
}
.title {
font-size: 18px;
@@ -198,11 +180,7 @@ export class MaintenanceSavedViewsDialog extends LitElement {
padding: 8px 0;
}
.list {
display: flex;
flex-direction: column;
gap: 6px;
overflow-y: auto;
max-height: 50vh;
}
.row {
display: flex;
@@ -227,7 +205,7 @@ export class MaintenanceSavedViewsDialog extends LitElement {
gap: 8px;
padding-top: 8px;
}
`;
`];
}
if (!customElements.get("maintenance-saved-views-dialog")) {
@@ -19,6 +19,9 @@
* reset button completing it will press; tasks adopted before (or wired by
* hand) are offered the same in their own section, with a before/after line
* (integration_setups/reset_offers + wire_resets).
*
* #211: in the shared modal frame like the adopt dialog: the title and the
* buttons stay, the offers and the devices scroll as one.
*/
import { css, html, LitElement, nothing } from "lit";
@@ -26,6 +29,7 @@ import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf } from "../styles";
import { runWs } from "../helpers/ws-run";
import { focusModalShell, modalShellStyles, renderModalShell } from "../helpers/modal-shell";
import type { HomeAssistant } from "../types";
interface SetupTask {
@@ -112,6 +116,7 @@ export class MaintenanceSuggestedSetupsDialog extends LitElement {
this._localeReady = true;
ensureLocale(this._lang).then(() => this.requestUpdate());
}
if (changed.has("_open") && this._open) focusModalShell(this.shadowRoot);
}
/** Ticked by default: everything the target does not probably have yet. */
@@ -336,13 +341,12 @@ export class MaintenanceSuggestedSetupsDialog extends LitElement {
if (!this._open) return html``;
const L = this._lang;
return html`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${(e: Event) => e.stopPropagation()}>
<div class="title">${t("setups_title", L)}</div>
<div class="hint">${t("setups_hint", L)}</div>
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
return renderModalShell(() => this._close(), html`
<div class="title">${t("setups_title", L)}</div>
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
<div class="dialog-scroll">
<div class="hint">${t("setups_hint", L)}</div>
${this._renderOffers(L)}
${this._loading
? html`<div class="loading">…</div>`
@@ -428,50 +432,31 @@ export class MaintenanceSuggestedSetupsDialog extends LitElement {
})}
</div>
`}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel", L)}
</ha-button>
<ha-button @click=${this._adopt} .disabled=${!this._anythingTicked || this._adopting}>
${t("setups_adopt", L)}
</ha-button>
</div>
</div>
</div>
`;
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel", L)}
</ha-button>
<ha-button @click=${this._adopt} .disabled=${!this._anythingTicked || this._adopting}>
${t("setups_adopt", L)}
</ha-button>
</div>
`);
}
static styles = css`
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.card {
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: min(360px, calc(100vw - 24px));
max-width: 560px;
width: 90vw;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
static styles = [modalShellStyles, css`
:host {
--ms-modal-max-width: 560px;
--ms-modal-max-height: 90dvh;
--ms-modal-gap: 12px;
--ms-modal-z: 1000;
}
.title { font-size: 18px; font-weight: 500; }
.hint { color: var(--secondary-text-color); font-size: 13px; }
.error { color: var(--error-color, #f44336); font-size: 13px; }
.loading, .empty { color: var(--secondary-text-color); font-size: 14px; padding: 12px 0; }
.list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; max-height: 55vh; }
.list { display: flex; flex-direction: column; gap: 6px; }
.row {
display: flex; align-items: flex-start; gap: 10px; padding: 8px;
border: 1px solid var(--divider-color); border-radius: 6px;
@@ -497,7 +482,6 @@ export class MaintenanceSuggestedSetupsDialog extends LitElement {
.offers {
display: flex; flex-direction: column; gap: 6px; padding: 8px;
border: 1px solid var(--primary-color); border-radius: 6px;
overflow-y: auto; max-height: 40vh; flex-shrink: 0;
}
.offers-title { font-weight: 500; font-size: 14px; }
.offer { display: flex; align-items: flex-start; gap: 6px; font-size: 13px; cursor: pointer; }
@@ -520,7 +504,7 @@ export class MaintenanceSuggestedSetupsDialog extends LitElement {
color: var(--primary-text-color);
}
.actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
`;
`];
}
if (!customElements.get("maintenance-suggested-setups-dialog")) {
@@ -3072,6 +3072,7 @@ export class MaintenanceTaskDialog extends LitElement {
<option value="completion" ?selected=${this._intervalAnchor === "completion"}>${t("anchor_completion", L)}</option>
<option value="planned" ?selected=${this._intervalAnchor === "planned"}>${t("anchor_planned", L)}</option>
</select>
<div class="field-help anchor-help">${t("interval_anchor_hint", L)}</div>
</div>
`
: nothing}
@@ -250,6 +250,7 @@
"interval_anchor": "Ukotvení intervalu",
"anchor_completion": "Od data dokončení",
"anchor_planned": "Od plánovaného data (bez posunu)",
"interval_anchor_hint": "Od data dokončení: další termín se počítá ode dne, kdy je úkol označen jako hotový, takže dřívější nebo pozdější splnění posune plán. Od plánovaného data: plán si ponechá své termíny, například když je faktura zaplacena týdny před splatností.",
"edit_object": "Upravit objekt",
"name": "Název",
"manufacturer_optional": "Výrobce (volitelné)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Dejte několika senzorům stejný název objektu a stanou se jedním objektem; zadejte název existujícího objektu a přidají se tam.",
"adopt_summary_title": "Co převzetí změní",
"adopt_summary_existing": "„{name}“: úkoly {before} → {after}",
"adopt_summary_totals": "Úkoly: {tasks} · objekty: {objects}",
"adopt_summary_totals_new": "Úkoly: {tasks} · objekty: {objects} (z toho nové: {new})",
"adopt_problem_adopt": "Převzít vybrané",
"adopt_problem_done": "Převzato {tasks} senzorů problémů",
"adopt_problem_done_one": "Převzat 1 senzor problémů",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Flotila baterií nastavena — jeden úkol sleduje všechny.",
"update_banner": "Na serveru je novější verze Maintenance Supporter — načtěte znovu pro aktualizaci panelu.",
"update_reload": "Načíst znovu",
"restart_pending_banner": "Maintenance Supporter byl aktualizován na verzi {version}. Restartujte Home Assistant, aby se aktualizace dokončila.",
"restart_pending_error": "Nepodařilo se: Maintenance Supporter byl aktualizován na verzi {version}, ale Home Assistant od té doby nebyl restartován. Restartujte Home Assistant a zkuste to znovu.",
"row_actions_banner": "Dokončit a Přeskočit jsou v řádcích úkolů nyní tlačítka. Dáváte přednost starým ikonám?",
"row_actions_keep": "Ponechat tlačítka",
"row_actions_back": "Zpět na ikony",
@@ -252,6 +252,7 @@
"interval_anchor": "Intervalanker",
"anchor_completion": "Fra fuldførelsesdato",
"anchor_planned": "Fra planlagt dato (ingen drift)",
"interval_anchor_hint": "Fra fuldførelsesdato: næste forfaldsdato regnes fra den dag, opgaven markeres som udført, så tidlig eller sen udførelse flytter planen. Fra planlagt dato: planen beholder sine datoer, for eksempel når en regning betales uger før forfald.",
"edit_object": "Rediger objekt",
"name": "Navn",
"manufacturer_optional": "Producent (valgfrit)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Giv flere sensorer samme objektnavn, så bliver de ét objekt; skriv et eksisterende objekts navn for at tilføje dem der.",
"adopt_summary_title": "Hvad overtagelsen ændrer",
"adopt_summary_existing": "\"{name}\": opgaver {before} → {after}",
"adopt_summary_totals": "Opgaver: {tasks} · objekter: {objects}",
"adopt_summary_totals_new": "Opgaver: {tasks} · objekter: {objects} (heraf nye: {new})",
"adopt_problem_adopt": "Adopter valgte",
"adopt_problem_done": "Adopterede {tasks} problemsensor(er)",
"adopt_problem_done_one": "Adopterede 1 problemsensor",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Batteriflåde opsat — én opgave følger alle batterier.",
"update_banner": "En nyere version af Maintenance Supporter er på serveren — genindlæs for at opdatere panelet.",
"update_reload": "Genindlæs",
"restart_pending_banner": "Maintenance Supporter er blevet opdateret til {version}. Genstart Home Assistant for at fuldføre opdateringen.",
"restart_pending_error": "Det lykkedes ikke: Maintenance Supporter er blevet opdateret til {version}, men Home Assistant er ikke blevet genstartet siden. Genstart Home Assistant, og prøv igen.",
"row_actions_banner": "Fuldfør og Spring over er nu knapper i opgaverækkerne. Foretrækker du de gamle ikoner?",
"row_actions_keep": "Behold knapper",
"row_actions_back": "Tilbage til ikoner",
@@ -252,6 +252,7 @@
"interval_anchor": "Intervall-Anker",
"anchor_completion": "Ab Erledigung",
"anchor_planned": "Ab geplantem Datum (kein Drift)",
"interval_anchor_hint": "Ab Erledigung: Die nächste Fälligkeit zählt ab dem Tag, an dem die Aufgabe erledigt wird; früher oder später erledigt verschiebt also den Plan. Ab geplantem Datum: Der Plan behält seine Termine, zum Beispiel wenn eine Rechnung Wochen vor der Fälligkeit bezahlt wird.",
"edit_object": "Objekt bearbeiten",
"name": "Name",
"manufacturer_optional": "Hersteller (optional)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Gib mehreren Sensoren denselben Objektnamen, dann werden sie ein Objekt; der Name eines vorhandenen Objekts ordnet sie dort zu.",
"adopt_summary_title": "Was die Übernahme ändert",
"adopt_summary_existing": "„{name}“: Aufgaben {before} → {after}",
"adopt_summary_totals": "Aufgaben: {tasks} · Objekte: {objects}",
"adopt_summary_totals_new": "Aufgaben: {tasks} · Objekte: {objects} (davon neu: {new})",
"adopt_problem_adopt": "Auswahl übernehmen",
"adopt_problem_done": "{tasks} Problem-Sensor(en) übernommen",
"adopt_problem_done_one": "1 Problem-Sensor übernommen",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Batterie-Flotte eingerichtet — ein Task überwacht alle Batterien.",
"update_banner": "Auf dem Server läuft eine neuere Version von Maintenance Supporter — neu laden, um das Panel zu aktualisieren.",
"update_reload": "Neu laden",
"restart_pending_banner": "Maintenance Supporter wurde auf {version} aktualisiert. Starte Home Assistant neu, um das Update abzuschließen.",
"restart_pending_error": "Das hat nicht geklappt: Maintenance Supporter wurde auf {version} aktualisiert, Home Assistant aber seitdem nicht neu gestartet. Starte Home Assistant neu und versuche es dann noch einmal.",
"row_actions_banner": "Erledigt und Überspringen sind in den Aufgabenzeilen jetzt Buttons. Lieber die alten Symbole?",
"row_actions_keep": "Buttons behalten",
"row_actions_back": "Zurück zu Symbolen",
@@ -252,6 +252,7 @@
"interval_anchor": "Interval anchor",
"anchor_completion": "From completion date",
"anchor_planned": "From planned date (no drift)",
"interval_anchor_hint": "From completion date: the next due date counts from the day the task is marked done, so doing it early or late moves the schedule. From planned date: the schedule keeps its dates, for example when a bill is paid weeks before it is due.",
"edit_object": "Edit Object",
"name": "Name",
"manufacturer_optional": "Manufacturer (optional)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Give several sensors the same object name and they become one object; type an existing object's name to add them there.",
"adopt_summary_title": "What adopting changes",
"adopt_summary_existing": "\"{name}\": tasks {before} → {after}",
"adopt_summary_totals": "Tasks: {tasks} · objects: {objects}",
"adopt_summary_totals_new": "Tasks: {tasks} · objects: {objects} ({new} new)",
"adopt_problem_adopt": "Adopt selected",
"adopt_problem_done": "Adopted {tasks} problem sensor(s)",
"adopt_problem_done_one": "Adopted 1 problem sensor",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Battery fleet set up — one task tracks all your batteries.",
"update_banner": "A newer version of Maintenance Supporter is on the server — reload to update the panel.",
"update_reload": "Reload",
"restart_pending_banner": "Maintenance Supporter was updated to {version}. Restart Home Assistant to finish the update.",
"restart_pending_error": "That did not work: Maintenance Supporter was updated to {version}, but Home Assistant has not been restarted since. Restart Home Assistant, then try again.",
"row_actions_banner": "Complete and Skip in task rows are now buttons. Prefer the old icons?",
"row_actions_keep": "Keep buttons",
"row_actions_back": "Back to icons",
@@ -250,6 +250,7 @@
"interval_anchor": "Anclaje del intervalo",
"anchor_completion": "Desde la fecha de finalización",
"anchor_planned": "Desde la fecha planificada (sin desviación)",
"interval_anchor_hint": "Desde la fecha de finalización: el próximo vencimiento se cuenta desde el día en que se marca la tarea como hecha, así que hacerla antes o después mueve el calendario. Desde la fecha planificada: el calendario mantiene sus fechas, por ejemplo cuando una factura se paga semanas antes de su vencimiento.",
"edit_object": "Editar objeto",
"name": "Nombre",
"manufacturer_optional": "Fabricante (opcional)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Da a varios sensores el mismo nombre de objeto y se convierten en un solo objeto; escribe el nombre de un objeto existente para añadirlos allí.",
"adopt_summary_title": "Qué cambia al adoptar",
"adopt_summary_existing": "«{name}»: tareas {before} → {after}",
"adopt_summary_totals": "Tareas: {tasks} · objetos: {objects}",
"adopt_summary_totals_new": "Tareas: {tasks} · objetos: {objects} (nuevos: {new})",
"adopt_problem_adopt": "Adoptar seleccionados",
"adopt_problem_done": "{tasks} sensor(es) de problema adoptado(s)",
"adopt_problem_done_one": "1 sensor de problema adoptado",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Flota de baterías configurada — una tarea controla todas.",
"update_banner": "Hay una versión más reciente de Maintenance Supporter en el servidor — recarga para actualizar el panel.",
"update_reload": "Recargar",
"restart_pending_banner": "Maintenance Supporter se actualizó a la versión {version}. Reinicia Home Assistant para completar la actualización.",
"restart_pending_error": "No ha funcionado: Maintenance Supporter se actualizó a la versión {version}, pero Home Assistant no se ha reiniciado desde entonces. Reinicia Home Assistant y vuelve a intentarlo.",
"row_actions_banner": "Completar y Omitir ahora son botones en las filas de tareas. ¿Prefieres los iconos antiguos?",
"row_actions_keep": "Mantener botones",
"row_actions_back": "Volver a los iconos",
@@ -252,6 +252,7 @@
"interval_anchor": "Välin ankkuri",
"anchor_completion": "Suorituspäivästä",
"anchor_planned": "Suunnitellusta päivästä (ei poikkeamaa)",
"interval_anchor_hint": "Suorituspäivästä: seuraava eräpäivä lasketaan päivästä, jolloin tehtävä merkitään tehdyksi, joten aikainen tai myöhäinen suoritus siirtää aikataulua. Suunnitellusta päivästä: aikataulu pitää päivänsä, esimerkiksi kun lasku maksetaan viikkoja ennen eräpäivää.",
"edit_object": "Muokkaa kohdetta",
"name": "Nimi",
"manufacturer_optional": "Valmistaja (valinnainen)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Anna useille antureille sama kohteen nimi, niin niistä tulee yksi kohde; kirjoita olemassa olevan kohteen nimi lisätäksesi ne sinne.",
"adopt_summary_title": "Mitä käyttöönotto muuttaa",
"adopt_summary_existing": "\"{name}\": tehtävät {before} → {after}",
"adopt_summary_totals": "Tehtäviä: {tasks} · kohteita: {objects}",
"adopt_summary_totals_new": "Tehtäviä: {tasks} · kohteita: {objects} (uusia: {new})",
"adopt_problem_adopt": "Ota valitut käyttöön",
"adopt_problem_done": "Otettiin käyttöön {tasks} ongelma-anturia",
"adopt_problem_done_one": "Otettiin käyttöön 1 ongelma-anturi",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Paristokanta määritetty — yksi tehtävä seuraa kaikkia.",
"update_banner": "Palvelimella on uudempi Maintenance Supporter -versio — lataa uudelleen päivittääksesi paneelin.",
"update_reload": "Lataa uudelleen",
"restart_pending_banner": "Maintenance Supporter on päivitetty versioon {version}. Käynnistä Home Assistant uudelleen viimeistelläksesi päivityksen.",
"restart_pending_error": "Se ei onnistunut: Maintenance Supporter on päivitetty versioon {version}, mutta Home Assistantia ei ole sen jälkeen käynnistetty uudelleen. Käynnistä Home Assistant uudelleen ja yritä sitten uudelleen.",
"row_actions_banner": "Valmis ja Ohita ovat tehtäväriveillä nyt painikkeita. Haluatko mieluummin vanhat kuvakkeet?",
"row_actions_keep": "Pidä painikkeet",
"row_actions_back": "Takaisin kuvakkeisiin",
@@ -250,6 +250,7 @@
"interval_anchor": "Ancrage de l'intervalle",
"anchor_completion": "Depuis la date de réalisation",
"anchor_planned": "Depuis la date prévue (sans dérive)",
"interval_anchor_hint": "Depuis la date de réalisation : la prochaine échéance compte à partir du jour où la tâche est marquée comme faite, donc la faire en avance ou en retard décale le planning. Depuis la date prévue : le planning garde ses dates, par exemple quand une facture est payée des semaines avant son échéance.",
"edit_object": "Modifier l'objet",
"name": "Nom",
"manufacturer_optional": "Fabricant (optionnel)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Donnez le même nom d'objet à plusieurs capteurs et ils forment un seul objet ; saisissez le nom d'un objet existant pour les y ajouter.",
"adopt_summary_title": "Ce que l'adoption modifie",
"adopt_summary_existing": "« {name} » : tâches {before} → {after}",
"adopt_summary_totals": "Tâches : {tasks} · objets : {objects}",
"adopt_summary_totals_new": "Tâches : {tasks} · objets : {objects} (dont nouveaux : {new})",
"adopt_problem_adopt": "Adopter la sélection",
"adopt_problem_done": "{tasks} capteur(s) de problème adopté(s)",
"adopt_problem_done_one": "1 capteur de problème adopté",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Parc de piles configuré — une tâche suit toutes vos piles.",
"update_banner": "Une version plus récente de Maintenance Supporter est sur le serveur — rechargez pour mettre à jour le panneau.",
"update_reload": "Recharger",
"restart_pending_banner": "Maintenance Supporter a été mis à jour vers la version {version}. Redémarrez Home Assistant pour terminer la mise à jour.",
"restart_pending_error": "Cela n'a pas fonctionné : Maintenance Supporter a été mis à jour vers la version {version}, mais Home Assistant n'a pas été redémarré depuis. Redémarrez Home Assistant, puis réessayez.",
"row_actions_banner": "Terminer et Ignorer sont désormais des boutons dans les lignes de tâches. Vous préférez les anciennes icônes ?",
"row_actions_keep": "Garder les boutons",
"row_actions_back": "Revenir aux icônes",
@@ -252,6 +252,7 @@
"interval_anchor": "अंतराल आधार",
"anchor_completion": "पूर्ण तिथि से",
"anchor_planned": "नियोजित तिथि से (कोई विचलन नहीं)",
"interval_anchor_hint": "पूर्ण तिथि से: अगली देय तिथि उस दिन से गिनी जाती है जिस दिन कार्य पूरा चिह्नित किया जाता है, इसलिए पहले या देर से करने पर समय-सारणी खिसक जाती है। नियोजित तिथि से: समय-सारणी अपनी तिथियाँ बनाए रखती है, उदाहरण के लिए जब कोई बिल देय तिथि से हफ़्तों पहले चुकाया जाता है।",
"edit_object": "वस्तु संपादित करें",
"name": "नाम",
"manufacturer_optional": "निर्माता (वैकल्पिक)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "कई सेंसरों को एक ही ऑब्जेक्ट नाम दें तो वे एक ऑब्जेक्ट बन जाते हैं; मौजूदा ऑब्जेक्ट का नाम लिखें तो वे वहीं जुड़ जाते हैं।",
"adopt_summary_title": "अपनाने से क्या बदलेगा",
"adopt_summary_existing": "\"{name}\": कार्य {before} → {after}",
"adopt_summary_totals": "कार्य: {tasks} · ऑब्जेक्ट: {objects}",
"adopt_summary_totals_new": "कार्य: {tasks} · ऑब्जेक्ट: {objects} (नए: {new})",
"adopt_problem_adopt": "चयनित अपनाएँ",
"adopt_problem_done": "{tasks} समस्या सेंसर अपनाए गए",
"adopt_problem_done_one": "1 समस्या सेंसर अपनाया गया",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "बैटरी फ्लीट सेट — एक कार्य सभी बैटरियों को ट्रैक करता है।",
"update_banner": "सर्वर पर Maintenance Supporter का नया संस्करण है — पैनल अपडेट करने के लिए पुनः लोड करें।",
"update_reload": "पुनः लोड करें",
"restart_pending_banner": "Maintenance Supporter को {version} संस्करण में अपडेट किया गया है। अपडेट पूरा करने के लिए Home Assistant को पुनः आरंभ करें।",
"restart_pending_error": "यह नहीं हो सका: Maintenance Supporter को {version} संस्करण में अपडेट किया गया है, लेकिन उसके बाद से Home Assistant को पुनः आरंभ नहीं किया गया है। Home Assistant को पुनः आरंभ करें, फिर दोबारा प्रयास करें।",
"row_actions_banner": "कार्य पंक्तियों में पूर्ण और छोड़ें अब बटन हैं। पुराने आइकन पसंद हैं?",
"row_actions_keep": "बटन रखें",
"row_actions_back": "आइकन पर वापस जाएँ",
@@ -252,6 +252,7 @@
"interval_anchor": "Intervallum horgonya",
"anchor_completion": "Az elvégzés dátumától",
"anchor_planned": "A tervezett dátumtól (nincs csúszás)",
"interval_anchor_hint": "Az elvégzés dátumától: a következő határidő attól a naptól számít, amikor a feladatot elvégzettnek jelölik, így a korábbi vagy későbbi elvégzés eltolja az ütemezést. A tervezett dátumtól: az ütemezés megtartja a dátumait, például ha egy számlát hetekkel a határidő előtt fizetnek be.",
"edit_object": "Objektum szerkesztése",
"name": "Név",
"manufacturer_optional": "Gyártó (opcionális)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Adja több érzékelőnek ugyanazt az objektumnevet, és egy objektummá válnak; meglévő objektum nevét beírva oda kerülnek.",
"adopt_summary_title": "Mit változtat az átvétel",
"adopt_summary_existing": "„{name}”: feladatok {before} → {after}",
"adopt_summary_totals": "Feladatok: {tasks} · objektumok: {objects}",
"adopt_summary_totals_new": "Feladatok: {tasks} · objektumok: {objects} (ebből új: {new})",
"adopt_problem_adopt": "Kijelöltek átvétele",
"adopt_problem_done": "{tasks} problémaérzékelő átvéve",
"adopt_problem_done_one": "1 problémaérzékelő átvéve",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Elemflotta beállítva — egyetlen feladat követi az összes elemet.",
"update_banner": "A Maintenance Supporter újabb verziója érhető el a szerveren — töltse újra az oldalt a panel frissítéséhez.",
"update_reload": "Újratöltés",
"restart_pending_banner": "A Maintenance Supporter frissült a(z) {version} verzióra. Indítsa újra a Home Assistantot a frissítés befejezéséhez.",
"restart_pending_error": "Ez nem sikerült: a Maintenance Supporter frissült a(z) {version} verzióra, de a Home Assistant azóta nem indult újra. Indítsa újra a Home Assistantot, majd próbálja újra.",
"row_actions_banner": "A Kész és a Kihagyás a feladatsorokban mostantól gombok. Inkább a régi ikonokat szeretné?",
"row_actions_keep": "Gombok megtartása",
"row_actions_back": "Vissza az ikonokhoz",
@@ -250,6 +250,7 @@
"interval_anchor": "Ancoraggio intervallo",
"anchor_completion": "Dalla data di completamento",
"anchor_planned": "Dalla data pianificata (nessuna deriva)",
"interval_anchor_hint": "Dalla data di completamento: la prossima scadenza si conta dal giorno in cui l'attività viene segnata come fatta, quindi farla prima o dopo sposta la pianificazione. Dalla data pianificata: la pianificazione mantiene le sue date, per esempio quando una bolletta viene pagata settimane prima della scadenza.",
"edit_object": "Modifica oggetto",
"name": "Nome",
"manufacturer_optional": "Produttore (opzionale)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Assegna lo stesso nome oggetto a più sensori e diventano un unico oggetto; digita il nome di un oggetto esistente per aggiungerli lì.",
"adopt_summary_title": "Cosa cambia l'adozione",
"adopt_summary_existing": "«{name}»: attività {before} → {after}",
"adopt_summary_totals": "Attività: {tasks} · oggetti: {objects}",
"adopt_summary_totals_new": "Attività: {tasks} · oggetti: {objects} (di cui nuovi: {new})",
"adopt_problem_adopt": "Adotta selezionati",
"adopt_problem_done": "{tasks} sensore/i di problema adottato/i",
"adopt_problem_done_one": "1 sensore di problema adottato",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Parco batterie configurato — un'attività monitora tutte.",
"update_banner": "Sul server è presente una versione più recente di Maintenance Supporter — ricarica per aggiornare il pannello.",
"update_reload": "Ricarica",
"restart_pending_banner": "Maintenance Supporter è stato aggiornato alla versione {version}. Riavvia Home Assistant per completare l'aggiornamento.",
"restart_pending_error": "Non è andato a buon fine: Maintenance Supporter è stato aggiornato alla versione {version}, ma da allora Home Assistant non è stato riavviato. Riavvia Home Assistant e riprova.",
"row_actions_banner": "Completa e Salta nelle righe delle attività ora sono pulsanti. Preferisci le vecchie icone?",
"row_actions_keep": "Mantieni i pulsanti",
"row_actions_back": "Torna alle icone",
@@ -252,6 +252,7 @@
"interval_anchor": "間隔の起点",
"anchor_completion": "完了日から",
"anchor_planned": "予定日から(ずれなし)",
"interval_anchor_hint": "完了日から: 次の期限はタスクを完了にした日から数えるため、早く・遅く完了すると予定がずれます。予定日から: 予定は元の日付を保ちます。たとえば請求書を期限の数週間前に支払った場合です。",
"edit_object": "対象を編集",
"name": "名前",
"manufacturer_optional": "メーカー(任意)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "複数のセンサーに同じオブジェクト名を付けると1つのオブジェクトになります。既存のオブジェクト名を入力すればそこに追加されます。",
"adopt_summary_title": "取り込みで変わること",
"adopt_summary_existing": "「{name}」: タスク {before} → {after}",
"adopt_summary_totals": "タスク: {tasks} · オブジェクト: {objects}",
"adopt_summary_totals_new": "タスク: {tasks} · オブジェクト: {objects}(うち新規: {new})",
"adopt_problem_adopt": "選択を取り込む",
"adopt_problem_done": "{tasks} 件の問題センサーを取り込みました",
"adopt_problem_done_one": "1 件の問題センサーを取り込みました",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "電池フリートを設定 — 1つのタスクで全電池を管理。",
"update_banner": "サーバーに新しいバージョンの Maintenance Supporter があります — 再読み込みしてパネルを更新してください。",
"update_reload": "再読み込み",
"restart_pending_banner": "Maintenance Supporter はバージョン {version} に更新されました。更新を完了するには Home Assistant を再起動してください。",
"restart_pending_error": "実行できませんでした: Maintenance Supporter はバージョン {version} に更新されましたが、その後 Home Assistant が再起動されていません。Home Assistant を再起動してから、もう一度お試しください。",
"row_actions_banner": "タスク行の「完了」と「スキップ」がボタンになりました。以前のアイコンに戻しますか?",
"row_actions_keep": "ボタンのままにする",
"row_actions_back": "アイコンに戻す",
@@ -252,6 +252,7 @@
"interval_anchor": "주기 기준",
"anchor_completion": "완료일 기준",
"anchor_planned": "예정일 기준 (밀림 없음)",
"interval_anchor_hint": "완료일 기준: 다음 예정일은 작업을 완료로 표시한 날부터 계산되므로, 일찍 또는 늦게 완료하면 일정이 바뀝니다. 예정일 기준: 일정이 원래 날짜를 유지합니다. 예를 들어 청구서를 기한보다 몇 주 일찍 납부한 경우입니다.",
"edit_object": "객체 편집",
"name": "이름",
"manufacturer_optional": "제조사 (선택)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "여러 센서에 같은 개체 이름을 주면 하나의 개체가 됩니다. 기존 개체 이름을 입력하면 거기에 추가됩니다.",
"adopt_summary_title": "가져오기로 바뀌는 점",
"adopt_summary_existing": "\"{name}\": 작업 {before} → {after}",
"adopt_summary_totals": "작업: {tasks} · 객체: {objects}",
"adopt_summary_totals_new": "작업: {tasks} · 객체: {objects} (새 객체: {new})",
"adopt_problem_adopt": "선택 항목 가져오기",
"adopt_problem_done": "문제 센서 {tasks}개를 가져왔습니다",
"adopt_problem_done_one": "문제 센서 1개를 가져왔습니다",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "배터리 플릿이 설정되었습니다 — 하나의 작업이 모든 배터리를 추적합니다.",
"update_banner": "서버에 Maintenance Supporter의 새 버전이 있습니다 — 패널을 업데이트하려면 새로 고침하세요.",
"update_reload": "새로 고침",
"restart_pending_banner": "Maintenance Supporter가 {version} 버전으로 업데이트되었습니다. 업데이트를 완료하려면 Home Assistant를 다시 시작하세요.",
"restart_pending_error": "실패했습니다: Maintenance Supporter가 {version} 버전으로 업데이트되었지만 그 이후 Home Assistant가 다시 시작되지 않았습니다. Home Assistant를 다시 시작한 후 다시 시도하세요.",
"row_actions_banner": "작업 행의 완료와 건너뛰기가 이제 버튼으로 표시됩니다. 이전 아이콘이 더 좋으신가요?",
"row_actions_keep": "버튼 유지",
"row_actions_back": "아이콘으로 되돌리기",
@@ -252,6 +252,7 @@
"interval_anchor": "Intervallforankring",
"anchor_completion": "Fra fullføringsdato",
"anchor_planned": "Fra planlagt dato (ingen avdrift)",
"interval_anchor_hint": "Fra fullføringsdato: neste forfallsdato regnes fra dagen oppgaven markeres som utført, så tidlig eller sen utførelse flytter planen. Fra planlagt dato: planen beholder datoene sine, for eksempel når en regning betales uker før forfall.",
"edit_object": "Rediger objekt",
"name": "Navn",
"manufacturer_optional": "Produsent (valgfritt)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Gi flere sensorer samme objektnavn, så blir de ett objekt; skriv navnet på et eksisterende objekt for å legge dem der.",
"adopt_summary_title": "Hva overtakelsen endrer",
"adopt_summary_existing": "«{name}»: oppgaver {before} → {after}",
"adopt_summary_totals": "Oppgaver: {tasks} · objekter: {objects}",
"adopt_summary_totals_new": "Oppgaver: {tasks} · objekter: {objects} (herav nye: {new})",
"adopt_problem_adopt": "Ta i bruk valgte",
"adopt_problem_done": "Tok i bruk {tasks} problemsensor(er)",
"adopt_problem_done_one": "Tok i bruk 1 problemsensor",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Batteriflåte satt opp — én oppgave følger alle batteriene.",
"update_banner": "En nyere versjon av Maintenance Supporter er på serveren — last inn på nytt for å oppdatere panelet.",
"update_reload": "Last inn på nytt",
"restart_pending_banner": "Maintenance Supporter er oppdatert til {version}. Start Home Assistant på nytt for å fullføre oppdateringen.",
"restart_pending_error": "Det fungerte ikke: Maintenance Supporter er oppdatert til {version}, men Home Assistant er ikke startet på nytt siden. Start Home Assistant på nytt, og prøv igjen.",
"row_actions_banner": "Fullfør og Hopp over er nå knapper i oppgaveradene. Foretrekker du de gamle ikonene?",
"row_actions_keep": "Behold knapper",
"row_actions_back": "Tilbake til ikoner",
@@ -250,6 +250,7 @@
"interval_anchor": "Interval-anker",
"anchor_completion": "Vanaf voltooiing",
"anchor_planned": "Vanaf geplande datum (geen drift)",
"interval_anchor_hint": "Vanaf voltooiing: de volgende vervaldatum telt vanaf de dag waarop de taak als gedaan wordt gemarkeerd, dus eerder of later uitvoeren verschuift de planning. Vanaf geplande datum: de planning houdt haar data, bijvoorbeeld wanneer een rekening weken voor de vervaldatum wordt betaald.",
"edit_object": "Object bewerken",
"name": "Naam",
"manufacturer_optional": "Fabrikant (optioneel)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Geef meerdere sensoren dezelfde objectnaam en ze worden één object; typ de naam van een bestaand object om ze daar toe te voegen.",
"adopt_summary_title": "Wat overnemen verandert",
"adopt_summary_existing": "\"{name}\": taken {before} → {after}",
"adopt_summary_totals": "Taken: {tasks} · objecten: {objects}",
"adopt_summary_totals_new": "Taken: {tasks} · objecten: {objects} (waarvan nieuw: {new})",
"adopt_problem_adopt": "Selectie overnemen",
"adopt_problem_done": "{tasks} probleemsensor(en) overgenomen",
"adopt_problem_done_one": "1 probleemsensor overgenomen",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Batterijvloot ingesteld — één taak volgt al je batterijen.",
"update_banner": "Er staat een nieuwere versie van Maintenance Supporter op de server — herlaad om het paneel bij te werken.",
"update_reload": "Herladen",
"restart_pending_banner": "Maintenance Supporter is bijgewerkt naar {version}. Start Home Assistant opnieuw op om de update te voltooien.",
"restart_pending_error": "Dat is niet gelukt: Maintenance Supporter is bijgewerkt naar {version}, maar Home Assistant is sindsdien niet opnieuw opgestart. Start Home Assistant opnieuw op en probeer het dan nog eens.",
"row_actions_banner": "Voltooien en Overslaan zijn in taakrijen nu knoppen. Liever de oude pictogrammen?",
"row_actions_keep": "Knoppen houden",
"row_actions_back": "Terug naar pictogrammen",
@@ -250,6 +250,7 @@
"interval_anchor": "Punkt zaczepienia interwału",
"anchor_completion": "Od daty wykonania",
"anchor_planned": "Od daty planowanej (bez przesunięć)",
"interval_anchor_hint": "Od daty wykonania: następny termin liczy się od dnia oznaczenia zadania jako wykonanego, więc wcześniejsze lub późniejsze wykonanie przesuwa harmonogram. Od daty planowanej: harmonogram zachowuje swoje daty, na przykład gdy rachunek zostaje opłacony kilka tygodni przed terminem.",
"edit_object": "Edytuj obiekt",
"name": "Nazwa",
"manufacturer_optional": "Producent (opcjonalne)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Nadaj kilku czujnikom tę samą nazwę obiektu, a staną się jednym obiektem; wpisz nazwę istniejącego obiektu, aby je tam dodać.",
"adopt_summary_title": "Co zmieni przejęcie",
"adopt_summary_existing": "„{name}”: zadania {before} → {after}",
"adopt_summary_totals": "Zadania: {tasks} · obiekty: {objects}",
"adopt_summary_totals_new": "Zadania: {tasks} · obiekty: {objects} (w tym nowe: {new})",
"adopt_problem_adopt": "Przejmij zaznaczone",
"adopt_problem_done": "Przejęto {tasks} czujnik(ów) problemów",
"adopt_problem_done_one": "Przejęto 1 czujnik problemów",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Flota baterii skonfigurowana — jedno zadanie śledzi wszystkie.",
"update_banner": "Na serwerze jest nowsza wersja Maintenance Supporter — załaduj ponownie, aby zaktualizować panel.",
"update_reload": "Załaduj ponownie",
"restart_pending_banner": "Maintenance Supporter został zaktualizowany do wersji {version}. Uruchom ponownie Home Assistant, aby dokończyć aktualizację.",
"restart_pending_error": "Nie udało się: Maintenance Supporter został zaktualizowany do wersji {version}, ale od tego czasu Home Assistant nie został uruchomiony ponownie. Uruchom ponownie Home Assistant i spróbuj jeszcze raz.",
"row_actions_banner": "Ukończ i Pomiń w wierszach zadań są teraz przyciskami. Wolisz stare ikony?",
"row_actions_keep": "Zachowaj przyciski",
"row_actions_back": "Wróć do ikon",
@@ -252,6 +252,7 @@
"interval_anchor": "Âncora do intervalo",
"anchor_completion": "A partir da data de conclusão",
"anchor_planned": "A partir da data planejada (sem deriva)",
"interval_anchor_hint": "A partir da data de conclusão: o próximo vencimento conta a partir do dia em que a tarefa é marcada como feita, então fazê-la antes ou depois desloca o cronograma. A partir da data planejada: o cronograma mantém suas datas, por exemplo quando uma conta é paga semanas antes do vencimento.",
"edit_object": "Editar objeto",
"name": "Nome",
"manufacturer_optional": "Fabricante (opcional)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Dê a vários sensores o mesmo nome de objeto e eles viram um único objeto; digite o nome de um objeto existente para adicioná-los lá.",
"adopt_summary_title": "O que a adoção muda",
"adopt_summary_existing": "\"{name}\": tarefas {before} → {after}",
"adopt_summary_totals": "Tarefas: {tasks} · objetos: {objects}",
"adopt_summary_totals_new": "Tarefas: {tasks} · objetos: {objects} (novos: {new})",
"adopt_problem_adopt": "Adotar selecionados",
"adopt_problem_done": "{tasks} sensor(es) de problema adotado(s)",
"adopt_problem_done_one": "1 sensor de problema adotado",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Frota de baterias configurada — uma única tarefa acompanha todas as suas baterias.",
"update_banner": "Há uma versão mais nova do Maintenance Supporter no servidor — recarregue para atualizar o painel.",
"update_reload": "Recarregar",
"restart_pending_banner": "O Maintenance Supporter foi atualizado para a versão {version}. Reinicie o Home Assistant para finalizar a atualização.",
"restart_pending_error": "Não deu certo: o Maintenance Supporter foi atualizado para a versão {version}, mas o Home Assistant ainda não foi reiniciado. Reinicie o Home Assistant e tente de novo.",
"row_actions_banner": "Concluir e Pular agora são botões nas linhas de tarefas. Prefere os ícones antigos?",
"row_actions_keep": "Manter botões",
"row_actions_back": "Voltar aos ícones",
@@ -250,6 +250,7 @@
"interval_anchor": "Âncora do intervalo",
"anchor_completion": "A partir da data de conclusão",
"anchor_planned": "A partir da data planeada (sem desvio)",
"interval_anchor_hint": "A partir da data de conclusão: o próximo vencimento conta a partir do dia em que a tarefa é marcada como feita, por isso fazê-la antes ou depois desloca o calendário. A partir da data planeada: o calendário mantém as suas datas, por exemplo quando uma fatura é paga semanas antes do vencimento.",
"edit_object": "Editar objeto",
"name": "Nome",
"manufacturer_optional": "Fabricante (opcional)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Dê a vários sensores o mesmo nome de objeto e tornam-se um só objeto; escreva o nome de um objeto existente para os juntar lá.",
"adopt_summary_title": "O que a adoção altera",
"adopt_summary_existing": "«{name}»: tarefas {before} → {after}",
"adopt_summary_totals": "Tarefas: {tasks} · objetos: {objects}",
"adopt_summary_totals_new": "Tarefas: {tasks} · objetos: {objects} (novos: {new})",
"adopt_problem_adopt": "Adotar selecionados",
"adopt_problem_done": "{tasks} sensor(es) de problema adotado(s)",
"adopt_problem_done_one": "1 sensor de problema adotado",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Frota de baterias configurada — uma tarefa acompanha todas.",
"update_banner": "Há uma versão mais recente do Maintenance Supporter no servidor — recarregue para atualizar o painel.",
"update_reload": "Recarregar",
"restart_pending_banner": "O Maintenance Supporter foi atualizado para a versão {version}. Reinicie o Home Assistant para concluir a atualização.",
"restart_pending_error": "Não foi possível: o Maintenance Supporter foi atualizado para a versão {version}, mas o Home Assistant ainda não foi reiniciado. Reinicie o Home Assistant e tente novamente.",
"row_actions_banner": "Concluir e Ignorar são agora botões nas linhas de tarefas. Prefere os ícones antigos?",
"row_actions_keep": "Manter botões",
"row_actions_back": "Voltar aos ícones",
@@ -249,6 +249,7 @@
"interval_anchor": "Якорь интервала",
"anchor_completion": "От даты выполнения",
"anchor_planned": "От плановой даты (без смещения)",
"interval_anchor_hint": "От даты выполнения: следующий срок отсчитывается от дня, когда задача отмечена выполненной, поэтому раннее или позднее выполнение сдвигает график. От плановой даты: график сохраняет свои даты, например когда счёт оплачен за несколько недель до срока.",
"edit_object": "Изменить объект",
"name": "Имя",
"manufacturer_optional": "Производитель (опционально)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Дайте нескольким датчикам одно имя объекта — они станут одним объектом; введите имя существующего объекта, чтобы добавить их туда.",
"adopt_summary_title": "Что изменит добавление",
"adopt_summary_existing": "«{name}»: задачи {before} → {after}",
"adopt_summary_totals": "Задачи: {tasks} · объекты: {objects}",
"adopt_summary_totals_new": "Задачи: {tasks} · объекты: {objects} (из них новых: {new})",
"adopt_problem_adopt": "Принять выбранные",
"adopt_problem_done": "Принято сенсоров проблем: {tasks}",
"adopt_problem_done_one": "Принят 1 сенсор проблем",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Парк батарей настроен — одна задача следит за всеми.",
"update_banner": "На сервере более новая версия Maintenance Supporter — перезагрузите, чтобы обновить панель.",
"update_reload": "Перезагрузить",
"restart_pending_banner": "Maintenance Supporter обновлён до версии {version}. Перезапустите Home Assistant, чтобы завершить обновление.",
"restart_pending_error": "Не получилось: Maintenance Supporter обновлён до версии {version}, но Home Assistant с тех пор не перезапускался. Перезапустите Home Assistant и попробуйте ещё раз.",
"row_actions_banner": "«Выполнено» и «Пропустить» в строках задач теперь кнопки. Предпочитаете старые значки?",
"row_actions_keep": "Оставить кнопки",
"row_actions_back": "Вернуть значки",
@@ -250,6 +250,7 @@
"interval_anchor": "Intervallankare",
"anchor_completion": "Från slutförandedatum",
"anchor_planned": "Från planerat datum (ingen drift)",
"interval_anchor_hint": "Från slutförandedatum: nästa förfallodatum räknas från dagen då uppgiften markeras som utförd, så att göra den tidigt eller sent flyttar schemat. Från planerat datum: schemat behåller sina datum, till exempel när en räkning betalas veckor före förfallodagen.",
"edit_object": "Redigera objekt",
"name": "Namn",
"manufacturer_optional": "Tillverkare (valfritt)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Ge flera sensorer samma objektnamn så blir de ett objekt; skriv ett befintligt objekts namn för att lägga dem där.",
"adopt_summary_title": "Vad övertagandet ändrar",
"adopt_summary_existing": "”{name}”: uppgifter {before} → {after}",
"adopt_summary_totals": "Uppgifter: {tasks} · objekt: {objects}",
"adopt_summary_totals_new": "Uppgifter: {tasks} · objekt: {objects} (varav nya: {new})",
"adopt_problem_adopt": "Anta valda",
"adopt_problem_done": "Antog {tasks} problemsensor(er)",
"adopt_problem_done_one": "Antog 1 problemsensor",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Batteriflotta konfigurerad — en uppgift följer alla batterier.",
"update_banner": "En nyare version av Maintenance Supporter finns på servern — ladda om för att uppdatera panelen.",
"update_reload": "Ladda om",
"restart_pending_banner": "Maintenance Supporter har uppdaterats till {version}. Starta om Home Assistant för att slutföra uppdateringen.",
"restart_pending_error": "Det fungerade inte: Maintenance Supporter har uppdaterats till {version}, men Home Assistant har inte startats om sedan dess. Starta om Home Assistant och försök igen.",
"row_actions_banner": "Slutför och Hoppa över är nu knappar i uppgiftsraderna. Föredrar du de gamla ikonerna?",
"row_actions_keep": "Behåll knappar",
"row_actions_back": "Tillbaka till ikoner",
@@ -252,6 +252,7 @@
"interval_anchor": "Aralık referansı",
"anchor_completion": "Tamamlama tarihinden",
"anchor_planned": "Planlanan tarihten (kayma olmaz)",
"interval_anchor_hint": "Tamamlama tarihinden: sonraki vade, görevin yapıldı olarak işaretlendiği günden itibaren sayılır; bu yüzden erken veya geç yapmak planı kaydırır. Planlanan tarihten: plan tarihlerini korur; örneğin bir fatura vadesinden haftalar önce ödendiğinde.",
"edit_object": "Nesneyi Düzenle",
"name": "Ad",
"manufacturer_optional": "Üretici (isteğe bağlı)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Birden çok sensöre aynı nesne adını verin, tek nesne olurlar; mevcut bir nesnenin adını yazarsanız oraya eklenirler.",
"adopt_summary_title": "Devralmanın değiştirecekleri",
"adopt_summary_existing": "\"{name}\": görevler {before} → {after}",
"adopt_summary_totals": "Görevler: {tasks} · nesneler: {objects}",
"adopt_summary_totals_new": "Görevler: {tasks} · nesneler: {objects} (yeni: {new})",
"adopt_problem_adopt": "Seçilenleri devral",
"adopt_problem_done": "{tasks} sorun sensörü devralındı",
"adopt_problem_done_one": "1 sorun sensörü devralındı",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Pil filosu kuruldu — tek bir görev tüm pillerinizi takip ediyor.",
"update_banner": "Sunucuda Maintenance Supporter'ın daha yeni bir sürümü var — paneli güncellemek için sayfayı yeniden yükleyin.",
"update_reload": "Yeniden yükle",
"restart_pending_banner": "Maintenance Supporter {version} sürümüne güncellendi. Güncellemeyi tamamlamak için Home Assistant'ı yeniden başlatın.",
"restart_pending_error": "Bu işlem başarısız oldu: Maintenance Supporter {version} sürümüne güncellendi, ancak Home Assistant o zamandan beri yeniden başlatılmadı. Home Assistant'ı yeniden başlatın ve tekrar deneyin.",
"row_actions_banner": "Görev satırlarındaki Tamamla ve Atla artık düğme. Eski simgeleri mi tercih edersiniz?",
"row_actions_keep": "Düğmeleri koru",
"row_actions_back": "Simgelere geri dön",
@@ -249,6 +249,7 @@
"interval_anchor": "Прив'язка інтервалу",
"anchor_completion": "Від дати виконання",
"anchor_planned": "Від запланованої дати (без зміщення)",
"interval_anchor_hint": "Від дати виконання: наступний термін відраховується від дня, коли завдання позначено виконаним, тож раннє чи пізнє виконання зсуває графік. Від запланованої дати: графік зберігає свої дати, наприклад коли рахунок сплачено за кілька тижнів до терміну.",
"edit_object": "Редагувати об'єкт",
"name": "Назва",
"manufacturer_optional": "Виробник (необов'язково)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "Дайте кільком сенсорам однакову назву об'єкта — вони стануть одним об'єктом; введіть назву наявного об'єкта, щоб додати їх туди.",
"adopt_summary_title": "Що змінить додавання",
"adopt_summary_existing": "«{name}»: завдання {before} → {after}",
"adopt_summary_totals": "Завдання: {tasks} · об'єкти: {objects}",
"adopt_summary_totals_new": "Завдання: {tasks} · об'єкти: {objects} (з них нових: {new})",
"adopt_problem_adopt": "Прийняти вибрані",
"adopt_problem_done": "Прийнято сенсорів проблем: {tasks}",
"adopt_problem_done_one": "Прийнято 1 сенсор проблем",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "Парк батарей налаштовано — одне завдання стежить за всіма.",
"update_banner": "На сервері новіша версія Maintenance Supporter — перезавантажте, щоб оновити панель.",
"update_reload": "Перезавантажити",
"restart_pending_banner": "Maintenance Supporter оновлено до версії {version}. Перезапустіть Home Assistant, щоб завершити оновлення.",
"restart_pending_error": "Не вдалося: Maintenance Supporter оновлено до версії {version}, але Home Assistant відтоді не перезапускався. Перезапустіть Home Assistant і спробуйте ще раз.",
"row_actions_banner": "«Виконано» та «Пропустити» в рядках завдань тепер кнопки. Бажаєте старі значки?",
"row_actions_keep": "Залишити кнопки",
"row_actions_back": "Повернути значки",
@@ -252,6 +252,7 @@
"interval_anchor": "计划锚点",
"anchor_completion": "从完成日期起算",
"anchor_planned": "从计划日期起算 (无偏差)",
"interval_anchor_hint": "从完成日期起算:下一个到期日从任务标记为完成的那天开始计算,因此提前或推迟完成都会移动计划。从计划日期起算:计划保持原定日期,例如账单在到期前几周就已支付时。",
"edit_object": "编辑维护项",
"name": "名称",
"manufacturer_optional": "制造商 (可选)",
@@ -1121,6 +1122,8 @@
"adopt_object_hint": "给多个传感器相同的对象名称,它们就会合并为一个对象;输入已有对象的名称即可加入该对象。",
"adopt_summary_title": "采用后会发生的变化",
"adopt_summary_existing": "“{name}”:任务 {before} → {after}",
"adopt_summary_totals": "任务:{tasks} · 对象:{objects}",
"adopt_summary_totals_new": "任务:{tasks} · 对象:{objects}(其中新建:{new})",
"adopt_problem_adopt": "采用所选",
"adopt_problem_done": "已采用 {tasks} 个问题传感器",
"adopt_problem_done_one": "已采用 1 个问题传感器",
@@ -1282,6 +1285,8 @@
"battery_fleet_setup_done": "电池群已设置 — 一个任务跟踪所有电池。",
"update_banner": "服务器上有更新版本的 Maintenance Supporter——请重新加载以更新面板。",
"update_reload": "重新加载",
"restart_pending_banner": "Maintenance Supporter 已更新到 {version} 版本。请重启 Home Assistant 以完成更新。",
"restart_pending_error": "操作失败:Maintenance Supporter 已更新到 {version} 版本,但 Home Assistant 此后尚未重启。请重启 Home Assistant 后重试。",
"row_actions_banner": "任务行中的“完成”和“跳过”现在是按钮。更喜欢旧的图标?",
"row_actions_keep": "保留按钮",
"row_actions_back": "改回图标",
@@ -5,7 +5,7 @@ import { isSafeLink } from "./helpers/url";
import { queryTokens, scoreFields } from "./helpers/search-match";
import { objectRef, parseRef, renderRefChip, taskRef, type HasRef } from "./helpers/reference";
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
import { isStaleBundle } from "./helpers/bundle-version";
import { isStaleBundle, pendingRestartVersion, type ServerVersion } from "./helpers/bundle-version";
import { customElement, property, state } from "lit/decorators.js";
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, currencySymbolOf, t, ensureLocale, isLocaleLoaded, formatDate, formatDueDays, formatInterval, formatRecurrence, setProfilePrefs, langOf, formatCost, formatQty, syncCurrencyDecimals} from "./styles";
import { OVERVIEW_TABS, type OverviewTab } from "./helpers/overview-tabs";
@@ -313,7 +313,11 @@ export class MaintenanceSupporterPanel extends LitElement {
// and the fleet isn't set up yet.
@state() private _batteryFleetSetupAvailable = false;
@state() private _staleBundle = false;
/** The version an update installed while Home Assistant still runs the
* old code (helpers/update_state.py); null when both agree. */
@state() private _restartPendingVersion: string | null = null;
private _staleChecked = false;
private _onReconnect = (): void => this._checkServerVersion();
private _toastTimer: ReturnType<typeof setTimeout> | null = null;
private _dismissedSuggestions = new Set<string>();
@@ -408,7 +412,7 @@ export class MaintenanceSupporterPanel extends LitElement {
private _statsService: StatisticsService | null = null;
private _userService: UserService | null = null;
private _dataLoaded = false;
private _lastConnection: unknown = null;
private _lastConnection: HomeAssistant["connection"] | null = null;
private get _lang(): string {
return langOf(this.hass);
@@ -553,7 +557,7 @@ export class MaintenanceSupporterPanel extends LitElement {
}
this._dataLoaded = false;
this._initialLoadDone = false;
this._lastConnection = null;
this._watchConnection(null);
this._deepLinkHandled = false;
// `_presetsApplied` intentionally survives (see its declaration).
// Pending timers must not fire into a detached element (bug audit
@@ -582,13 +586,13 @@ export class MaintenanceSupporterPanel extends LitElement {
if (changedProps.has("hass") && this.hass) {
if (!this._dataLoaded) {
this._dataLoaded = true;
this._lastConnection = this.hass.connection;
this._watchConnection(this.hass.connection);
// Seed initial history state so back button returns to overview
history.replaceState({ msp_view: "overview", msp_entry: null, msp_task: null }, "");
this._loadData();
this._subscribe();
} else if (this.hass.connection !== this._lastConnection) {
this._lastConnection = this.hass.connection;
this._watchConnection(this.hass.connection);
if (this._unsub) {
try { this._unsub(); } catch { /* ignore */ }
this._unsub = null;
@@ -743,6 +747,31 @@ export class MaintenanceSupporterPanel extends LitElement {
}
}
/** Follow `conn`'s reconnects for the version check (and stop following
* the previous connection). */
private _watchConnection(conn: HomeAssistant["connection"] | null): void {
this._lastConnection?.removeEventListener?.("ready", this._onReconnect);
this._lastConnection = conn;
conn?.addEventListener?.("ready", this._onReconnect);
}
/** Stale-bundle handshake (roadmap guard 2): compare the version esbuild
* stamped into this bundle with the backend's — a mismatch means the
* browser/service worker is still serving an OLD cached frontend, and no
* amount of HA restarts fixes that. Unless an update waits for its
* restart: then that explains any difference, and only the restart helps. */
private _checkServerVersion(): void {
this.hass.connection
.sendMessagePromise<ServerVersion>({ type: "maintenance_supporter/version" })
.then((v) => {
this._restartPendingVersion = pendingRestartVersion(v);
this._staleBundle = !this._restartPendingVersion && isStaleBundle(v?.version);
})
.catch(() => {
/* backend too old to answer — no banner */
});
}
private async _loadData(): Promise<void> {
const [objResult, statsResult, budgetResult, groupsResult, settingsResult, viewsResult] = await Promise.all([
this.hass.connection.sendMessagePromise({ type: "maintenance_supporter/objects", compact: true }).catch(() => null),
@@ -780,20 +809,11 @@ export class MaintenanceSupporterPanel extends LitElement {
.catch(() => {
this._batteryFleetSetupAvailable = false;
});
// Stale-bundle handshake (roadmap guard 2): compare the version esbuild
// stamped into this bundle with the backend's — a mismatch means the
// browser/service worker is still serving an OLD cached frontend, and no
// amount of HA restarts fixes that. Checked once per panel lifetime.
// Checked once per panel lifetime, and again after every reconnect (a
// Home Assistant restart ends the "restart pending" state).
if (!this._staleChecked) {
this._staleChecked = true;
this.hass.connection
.sendMessagePromise<{ version: string }>({ type: "maintenance_supporter/version" })
.then((v) => {
this._staleBundle = isStaleBundle(v?.version);
})
.catch(() => {
/* backend too old to answer — no banner */
});
this._checkServerVersion();
}
if (statsResult) this._stats = statsResult as StatisticsResponse;
if (budgetResult) { this._budget = budgetResult as BudgetStatus; syncCurrencyDecimals(this._budget); }
@@ -1272,6 +1292,7 @@ export class MaintenanceSupporterPanel extends LitElement {
labels: task.labels ?? [],
area_id: obj.object.area_id ?? null,
responsible_user_id: task.responsible_user_id ?? null,
assignee_pool: task.assignee_pool ?? null,
group_names: groupNames,
});
}
@@ -1281,8 +1302,11 @@ export class MaintenanceSupporterPanel extends LitElement {
const byDue = (a: TaskRow, b: TaskRow) => byStatus(a, b) || byDays(a, b);
const areaName = (r: TaskRow) =>
r.area_id ? this.hass?.areas?.[r.area_id]?.name || "" : "";
const userName = (r: TaskRow) =>
r.responsible_user_id ? this._userService?.getUserName(r.responsible_user_id) || "" : "";
// Whom the task is meant for, as the person filter reads it (D#110):
// the responsible person, else everyone it is shared with.
const userNames = (r: TaskRow) =>
taskUserIds(r).map((id) => this._userService?.getUserName(id) || "").filter(Boolean);
const userName = (r: TaskRow) => userNames(r).sort((a, b) => a.localeCompare(b)).join(", ");
const groupName = (r: TaskRow) => r.group_names[0] || "";
const sorts: Record<SortMode, (a: TaskRow, b: TaskRow) => number> = {
due_date: byDue,
@@ -3172,6 +3196,14 @@ export class MaintenanceSupporterPanel extends LitElement {
render() {
return html`
<div class="panel">
${this._restartPendingVersion
? html`
<div class="update-banner restart-pending-banner" role="status">
<ha-icon icon="mdi:restart"></ha-icon>
<span>${t("restart_pending_banner", this._lang).replace("{version}", this._restartPendingVersion)}</span>
</div>
`
: nothing}
${this._staleBundle
? html`
<div class="update-banner" role="status">
@@ -3906,8 +3938,10 @@ export class MaintenanceSupporterPanel extends LitElement {
const a = row.area_id ? this.hass?.areas?.[row.area_id]?.name : null;
keys = [a || noneLabel];
} else if (this._groupByMode === "user") {
const u = row.responsible_user_id ? this._userService?.getUserName(row.responsible_user_id) : null;
keys = [u || noneLabel];
// A shared task with nobody responsible is listed under each of its
// people, as the person filter lists it (D#110).
const names = taskUserIds(row).map((id) => this._userService?.getUserName(id)).filter((n): n is string => !!n);
keys = names.length > 0 ? [...new Set(names)] : [noneLabel];
} else if (this._groupByMode === "group") {
keys = row.group_names.length > 0 ? row.group_names : [noneLabel];
} else if (this._groupByMode === "object") {
@@ -633,6 +633,8 @@ export interface TaskRow {
labels: string[];
area_id: string | null;
responsible_user_id: string | null;
/** "Shared with": with nobody responsible, the task is meant for each (D#110). */
assignee_pool?: string[] | null;
group_names: string[];
/** #189: titles of the calendar events behind next_due (calendar kind). */
next_event_titles: string[];
@@ -660,6 +662,10 @@ export interface HomeAssistant {
subscribeMsg: Record<string, unknown>,
options?: Record<string, unknown>
): Promise<() => void>;
/** "ready" fires after a reconnect (a Home Assistant restart). Optional:
* test stubs leave it out. */
addEventListener?(event: "ready", callback: () => void): void;
removeEventListener?(event: "ready", callback: () => void): void;
};
callService(
domain: string,