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
@@ -480,6 +480,13 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
"""
hass.data.setdefault(DOMAIN, {})
# Which version this run imported: an update HACS installs later changes
# the files (and HA's integration cache) but not the code in memory, and
# the panel asks for a restart until they agree again.
from .helpers.update_state import async_remember_running_version
await async_remember_running_version(hass)
# The Lovelace card / calendar card / strategy modules FIRST (#184): HA
# builds the list of extra module scripts into index.html at the moment a
# page is served, and the frontend does not load modules registered later
@@ -974,6 +981,10 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
return
nm = hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY)
# Whose phones carry the reminder: read BEFORE completing, which
# hands a rotation on to the next person.
task_cfg = (config_entry.data.get(CONF_TASKS) or {}).get(task_id) or {} if config_entry is not None else {}
reminded_user_ids = task_user_ids(task_cfg)
try:
if action_type == "complete":
@@ -1017,9 +1028,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
# Action succeeded — dismiss notification and clear NM rate-limit state
if nm is not None:
cfg_entry = hass.config_entries.async_get_entry(entry_id)
task_cfg = ((cfg_entry.data.get(CONF_TASKS) or {}).get(task_id) or {}) if cfg_entry is not None else {}
await nm.async_dismiss_task_notification(task_id, user_ids=task_user_ids(task_cfg))
await nm.async_dismiss_task_notification(task_id, user_ids=reminded_user_ids)
nm.clear_task_state(entry_id, task_id)
unsub_notification = hass.bus.async_listen("mobile_app_notification_action", _handle_notification_action)
@@ -973,7 +973,9 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
bundling_enabled = global_option(self.hass, CONF_NOTIFICATION_BUNDLING_ENABLED)
bundle_threshold = int(global_option(self.hass, CONF_NOTIFICATION_BUNDLE_THRESHOLD))
if bundling_enabled and len(notifiable) >= bundle_threshold:
# No household target: a bundle would reach nobody, so the tasks go
# out one by one to their people's phones (D#110).
if bundling_enabled and nm.can_bundle and len(notifiable) >= bundle_threshold:
await nm.async_send_bundled(
entry_id=self.entry.entry_id,
object_name=obj_name,
@@ -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,
@@ -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": "改回图标",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,300 @@
/*! maintenance_supporter frontend 2.103.0 */
import{e as b}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DPJSGXJS.js";import{d as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-2BQZ33DM.js";import{a as x,b as w,c as $}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-QVOLUT24.js";import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import{I as y,a as l,b as u,c as n,f as p,h as g,m as v,n as d,t as i,v as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var j=3,o=class extends g{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._objectNames={};this._objects=[];this._covered={};this._users=[];this._responsible="";this._forMinutes="0";this._summaryOpen=!0;this._localeReady=!1;this._userService=null;this._toggle=e=>{let s=new Set(this._selected);s.has(e)?s.delete(e):s.add(e),this._selected=s};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(e=>e.entity_id))};this._adopt=async()=>{if(this._selected.size===0||this._adopting)return;this._error="";let e=this._sensors.filter(t=>this._selected.has(t.entity_id)).map(t=>({entity_id:t.entity_id,name:t.name,entry_id:this._existingEntryFor(t)??void 0,object_name:this._effectiveName(t),device_id:t.device_id??void 0,part_id:t.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),s=await m(this,{type:"maintenance_supporter/problem_sensors/adopt",selections:e},{busy:t=>{this._adopting=t},onError:t=>{this._error=t}});s!==void 0&&(this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:s})),this._open=!1)}}get _lang(){return y(this.hass)}updated(e){e.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,f(this._lang).then(()=>this.requestUpdate())),e.has("_open")&&this._open&&$(this.shadowRoot)}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._objectNames={},this._responsible="",this._forMinutes="0",this._userService?this._userService.updateHass(this.hass):this._userService=new b(this.hass);let[e,s,t]=await Promise.all([m(this,{type:"maintenance_supporter/problem_sensors/discover"},{onError:r=>{this._error=r}}),this._userService.getUsers().catch(()=>[]),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"}).catch(()=>({objects:[]}))]);this._loading=!1,e!==void 0&&(this._objects=(t?.objects||[]).filter(r=>!r.object.archived_at).map(r=>({entry_id:r.entry_id,name:r.object.name,tasks:(r.tasks||[]).length})),this._sensors=e?.sensors||[],this._covered=Object.fromEntries(this._sensors.map(r=>[r.entity_id,r.covered_by??null])),this._selected=new Set(this._sensors.filter(r=>!r.covered_by).map(r=>r.entity_id)),this._users=s,this._summaryOpen=this._summaryGroups().length<=j)}_defaultName(e){return e.suggested_entry_id?e.suggested_object_name:e.candidate?.name??e.suggested_object_name}_effectiveName(e){return(this._objectNames[e.entity_id]??"").trim()||this._defaultName(e)}_existingEntryFor(e){let s=this._effectiveName(e),t=e.suggested_entry_id||e.target_entry_id||null;if(s===this._defaultName(e)&&t)return t;let r=this._objects.find(a=>a.name.trim().toLowerCase()===s.toLowerCase());return r?r.entry_id:null}_schedulePreview(){clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._preview()},350)}async _preview(){let e=this._sensors.map(t=>({entity_id:t.entity_id,name:t.name,entry_id:this._existingEntryFor(t)})),s=await m(this,{type:"maintenance_supporter/problem_sensors/preview",selections:e},{onError:()=>{}});s?.covered&&(this._covered={...this._covered,...s.covered})}_summaryGroups(){let e=new Map;for(let s of this._sensors){if(!this._selected.has(s.entity_id))continue;let t=this._existingEntryFor(s),r=this._effectiveName(s),a=t??`new:${r.toLowerCase()}`,c=e.get(a)??{name:r,entry:t,adding:0};c.adding+=1,e.set(a,c)}return[...e.values()]}_summary(e){let s=this._summaryGroups();if(s.length===0)return p;let t=s.filter(a=>!a.entry).length,r=i(t?"adopt_summary_totals_new":"adopt_summary_totals",e).replace("{tasks}",String(s.reduce((a,c)=>a+c.adding,0))).replace("{objects}",String(s.length)).replace("{new}",String(t));return n`
<details
class="summary"
.open=${this._summaryOpen}
@toggle=${a=>{this._summaryOpen=a.target.open}}
>
<summary>
<span class="summary-title">${i("adopt_summary_title",e)}</span>
<span class="summary-totals">${r}</span>
</summary>
${s.map(a=>{if(!a.entry)return n`<div class="summary-line new">
${i("setups_new_object_count",e).replace("{name}",a.name).replace("{count}",String(a.adding))}
</div>`;let c=this._objects.find(_=>_.entry_id===a.entry)?.tasks??0;return n`<div class="summary-line">
${i("adopt_summary_existing",e).replace("{name}",a.name).replace("{before}",String(c)).replace("{after}",String(c+a.adding))}
</div>`})}
</details>
`}_close(){clearTimeout(this._previewTimer),this._open=!1}_targetLabel(e,s){if(e.candidate&&this._existingEntryFor(e)===e.candidate.entry_id){let t=e.candidate.reasons.map(r=>i(`setups_reason_${r}`,s)).join(", ");return i("setups_target_match",s).replace("{name}",e.candidate.name).replace("{reasons}",t)}return this._effectiveName(e)}render(){if(!this._open)return n``;let e=this._lang,s=this._sensors.length>0&&this._selected.size===this._sensors.length;return w(()=>this._close(),n`
<div class="title">${i("adopt_problem_title",e)}</div>
${this._error?n`<div class="error">${this._error}</div>`:p}
<div class="dialog-scroll">
<div class="hint">${i("adopt_problem_hint",e)}</div>
${this._loading?n`<div class="loading">…</div>`:this._sensors.length===0?n`<div class="empty">${i("adopt_problem_none",e)}</div>`:n`
<label class="select-all">
<input
type="checkbox"
.checked=${s}
@change=${this._toggleAll}
/>
<span>${i("selected",e)}: ${this._selected.size} / ${this._sensors.length}</span>
</label>
<div class="row-sub adopt-object-hint">${i("adopt_object_hint",e)}</div>
<datalist id="adopt-object-names">
${this._objects.map(t=>n`<option value=${t.name}></option>`)}
</datalist>
<div class="list">
${this._sensors.map(t=>{let r=this._selected.has(t.entity_id),a=t.state==="on",c=[t.device_name,t.area_name].filter(Boolean).join(" \xB7 ");return n`
<label class="row">
<input
type="checkbox"
.checked=${r}
@change=${()=>this._toggle(t.entity_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${t.name}</span>
<span class="chip ${a?"chip-active":"chip-ok"}">
${a?i("adopt_problem_active",e):i("adopt_problem_ok",e)}
</span>
</div>
${c?n`<div class="row-sub">${c}</div>`:p}
${r?n`<div class="row-object" @click=${_=>_.stopPropagation()}>
<input
class="adopt-object"
list="adopt-object-names"
aria-label=${i("object",e)}
placeholder=${this._defaultName(t)}
.value=${this._objectNames[t.entity_id]??this._defaultName(t)}
@input=${_=>{this._objectNames={...this._objectNames,[t.entity_id]:_.target.value},this._schedulePreview()}}
/>
</div>`:p}
<div class="row-target">
→ ${this._targetLabel(t,e)}${this._existingEntryFor(t)?p:n` <span class="new-tag">${i("adopt_problem_new_object",e)}</span>`}
</div>
${this._covered[t.entity_id]?n`<div class="maybe">
${i("setups_maybe_covered",e).replace("{name}",this._covered[t.entity_id].name)}
</div>`:p}
${t.suggested_part_name?n`<div class="row-part">
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
${i("adopt_problem_part",e).replace("{name}",t.suggested_part_name)}
</div>`:p}
</div>
</label>
`})}
</div>
`}
${!this._loading&&this._sensors.length>0?this._summary(e):p}
${!this._loading&&this._sensors.length>0?n`
<label class="responsible">
<span>${i("for_at_least_minutes",e)}</span>
<input
class="for-input"
type="number"
min=${h[0]}
max=${h[1]}
.value=${this._forMinutes}
@input=${t=>this._forMinutes=t.target.value}
/>
</label>
<div class="for-hint">${i("adopt_for_minutes_hint",e)}</div>
`:p}
${!this._loading&&this._sensors.length>0&&this._users.length>0?n`
<label class="responsible">
<span>${i("adopt_problem_responsible",e)}</span>
<select
.value=${this._responsible}
@change=${t=>{this._responsible=t.target.value}}
>
<option value="" ?selected=${!this._responsible}>${i("no_user_assigned",e)}</option>
${this._users.map(t=>n`<option value=${t.id} ?selected=${t.id===this._responsible}>${t.name}</option>`)}
</select>
</label>
`:p}
</div>
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${i("cancel",e)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${i("adopt_problem_adopt",e)}
</ha-button>
</div>
`)}};o.styles=[x,u`
: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;
}
/* 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;
gap: 8px;
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;
}
.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;
cursor: pointer;
}
.row input {
margin-top: 2px;
cursor: pointer;
}
.row-main {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
flex: 1;
}
.row-top {
display: flex;
align-items: center;
gap: 8px;
}
.row-name {
font-weight: 500;
font-size: 13px;
}
.row-sub {
color: var(--secondary-text-color);
font-size: 12px;
}
.row-object { margin-top: 6px; }
.row-object input {
width: 100%; box-sizing: border-box; padding: 6px 8px; font: inherit;
border: 1px solid var(--divider-color); border-radius: 6px;
background: var(--card-background-color); color: var(--primary-text-color);
}
.adopt-object-hint { margin: 0 0 8px; }
.row-target {
color: var(--secondary-text-color);
font-size: 12px;
}
.row-part {
color: var(--secondary-text-color);
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
}
.row-part ha-icon {
--mdc-icon-size: 14px;
}
.new-tag {
font-style: italic;
}
.maybe {
font-size: 11px;
color: var(--warning-color, #ff9800);
}
.summary {
font-size: 12px;
border-top: 1px dashed var(--divider-color);
padding-top: 6px;
}
.summary > summary {
cursor: pointer;
margin-bottom: 2px;
}
.summary-title {
font-weight: 500;
}
.summary-totals {
color: var(--secondary-text-color);
}
.summary-line {
color: var(--primary-color);
}
.chip {
font-size: 11px;
padding: 1px 8px;
border-radius: 10px;
white-space: nowrap;
}
.chip-active {
background: var(--error-color, #f44336);
color: #fff;
}
.chip-ok {
background: var(--divider-color);
color: var(--secondary-text-color);
}
.responsible {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--secondary-text-color);
flex-wrap: wrap;
}
.for-input {
width: 70px;
background: var(--card-background-color);
color: var(--primary-text-color);
border: 1px solid var(--divider-color);
border-radius: 4px;
padding: 4px 6px;
}
.for-hint {
font-size: 11px;
color: var(--secondary-text-color);
margin: -4px 0 2px;
}
.responsible select {
flex: 1;
min-width: 140px;
padding: 4px 6px;
border-radius: 4px;
border: 1px solid var(--divider-color);
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
}
.actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 8px;
}
`],l([v({attribute:!1})],o.prototype,"hass",2),l([d()],o.prototype,"_open",2),l([d()],o.prototype,"_loading",2),l([d()],o.prototype,"_adopting",2),l([d()],o.prototype,"_error",2),l([d()],o.prototype,"_sensors",2),l([d()],o.prototype,"_selected",2),l([d()],o.prototype,"_objectNames",2),l([d()],o.prototype,"_objects",2),l([d()],o.prototype,"_covered",2),l([d()],o.prototype,"_users",2),l([d()],o.prototype,"_responsible",2),l([d()],o.prototype,"_forMinutes",2),l([d()],o.prototype,"_summaryOpen",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",o);export{o as MaintenanceAdoptProblemSensorsDialog};
@@ -0,0 +1,279 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as X}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-H44SM3WA.js";import{a as o}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-274MLN75.js";import{f as E,g as M,h as z,i as Q,j as U,k as Y,l as q,m as G,n as R,o as A,p as W}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FH35SLMS.js";import{a as B}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KZ3OKTLK.js";import{e as P,f as K}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DR2AFVDA.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EOMYEBQ4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-MMIPM7EE.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-R77C34J4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IRRUA3DS.js";import{C as S,I as F,L as g,M as v,P as N,Q as I,V as w,X as T,a as $,c as p,f as y,h as H,m as k,n as j,o as C,r as D,t as a,v as L,z as O}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";function J(b,i,t,s){let l=b.entries.filter(e=>e.type==="completed").sort((e,n)=>e.ts-n.ts),d=`${b.from?t.date(b.from):"\u2026"} \u2013 ${b.to?t.date(b.to):"\u2026"}`,r=b.totals,h=[[i.completions,t.number(r.completions)],[i.totalCost,t.cost(r.totalCost)],[i.avgCost,r.avgCost!=null?t.cost(r.avgCost):i.none],[i.totalTime,r.totalDuration?t.duration(r.totalDuration):i.none]].map(([e,n])=>`<div class="kpi"><div class="k">${o(e)}</div><div class="v">${o(n)}</div></div>`).join(""),u=b.byObject.map(e=>`<tr>
<td>${o(e.objectName)}</td>
<td class="num">${o(t.number(e.completions))}</td>
<td class="num">${o(t.cost(e.cost))}</td>
<td class="num">${o(t.share(e.share))}</td>
</tr>`).join(""),f=b.buckets.buckets.reduce((e,n)=>Math.max(e,n.cost),0),c=b.buckets.buckets.map(e=>{let n=f>0?Math.round(e.cost/f*100):0;return`<tr>
<td class="nowrap">${o(t.bucket(e))}</td>
<td class="num">${o(t.number(e.completions))}</td>
<td class="bar-cell">${n>0?`<span class="bar" style="width:${n}%"></span>`:""}</td>
<td class="num">${o(t.cost(e.cost))}</td>
</tr>`}).join(""),m=l.map(e=>{let n=[e.notes,e.completedBy?`${i.completedBy}: ${e.completedBy}`:null].filter(Boolean).join(" \xB7 "),x=e.phaseName?`${e.taskName} \xB7 ${e.phaseName}`:e.taskName;return`<tr>
<td class="nowrap">${o(t.date(e.timestamp))}</td>
<td>${o(e.objectName)}</td>
<td>${o(x)}</td>
<td class="num">${e.cost!=null?o(t.cost(e.cost)):o(i.none)}</td>
<td class="num">${e.duration!=null?o(t.duration(e.duration)):o(i.none)}</td>
<td class="notes">${o(n)||o(i.none)}</td>
</tr>`}).join(`
`);return`<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="color-scheme" content="light">
<title>${o(i.title)} \u2014 ${o(b.areaName)}</title>
<style>
/* Printable sheet: it opens as a blob in whatever viewer the OS supplies
(Companion = WebView, dark phones paint a dark default canvas), so the
document states its own light scheme and paints its background. */
:root { color-scheme: light; }
* { box-sizing: border-box; }
body { font: 13px/1.5 -apple-system, Segoe UI, Roboto, sans-serif; color: #1a1a1a; background: #fff; margin: 32px; }
h1 { font-size: 22px; margin: 0 0 2px; }
h2 { font-size: 15px; margin: 24px 0 8px; }
.sub { color: #666; margin: 0 0 16px; }
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin: 0 0 8px; }
.kpi { border: 1px solid #ddd; border-radius: 6px; padding: 8px 10px; }
.kpi .k { color: #777; font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; }
.kpi .v { font-size: 16px; font-weight: 600; }
table { border-collapse: collapse; width: 100%; }
th { text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: #666; border-bottom: 2px solid #ccc; padding: 6px 8px; }
td { border-bottom: 1px solid #e5e5e5; padding: 6px 8px; vertical-align: top; }
td.num, th.num { text-align: right; white-space: nowrap; }
td.nowrap { white-space: nowrap; }
td.notes { color: #444; white-space: pre-wrap; }
td.bar-cell { width: 40%; vertical-align: middle; }
.bar { display: block; height: 10px; background: #9aa7b8; border-radius: 0 3px 3px 0;
-webkit-print-color-adjust: exact; print-color-adjust: exact; }
tfoot td { border-bottom: none; border-top: 2px solid #ccc; font-weight: 600; }
tr { break-inside: avoid; }
.cap-note { margin-top: 14px; color: #888; font-size: 11px; }
@media print { body { margin: 12mm; } }
</style>
</head>
<body>
<h1>${o(i.title)} \u2014 ${o(b.areaName)}</h1>
<p class="sub">${o(i.period)}: ${o(d)} \xB7 ${o(i.generated)} ${o(t.date(s))}</p>
<div class="kpis">${h}</div>
<h2>${o(i.costPerObject)}</h2>
<table>
<thead><tr>
<th>${o(i.colObject)}</th><th class="num">${o(i.completions)}</th>
<th class="num">${o(i.colCost)}</th><th class="num">${o(i.share)}</th>
</tr></thead>
<tbody>${u||`<tr><td colspan="4">${o(i.none)}</td></tr>`}</tbody>
<tfoot><tr>
<td>${o(i.totalCost)}</td><td class="num">${o(t.number(r.completions))}</td>
<td class="num">${o(t.cost(r.totalCost))}</td><td></td>
</tr></tfoot>
</table>
<h2>${o(i.costPerBucket)}</h2>
<table>
<thead><tr>
<th>${o(i.period)}</th><th class="num">${o(i.completions)}</th>
<th></th><th class="num">${o(i.colCost)}</th>
</tr></thead>
<tbody>${c||`<tr><td colspan="4">${o(i.none)}</td></tr>`}</tbody>
</table>
<h2>${o(i.historyHeading)} (${o(t.number(l.length))})</h2>
<table>
<thead><tr>
<th>${o(i.colDate)}</th><th>${o(i.colObject)}</th><th>${o(i.colTask)}</th>
<th class="num">${o(i.colCost)}</th><th class="num">${o(i.colDuration)}</th><th>${o(i.colNotes)}</th>
</tr></thead>
<tbody>
${m||`<tr><td colspan="6">${o(i.none)}</td></tr>`}
</tbody>
<tfoot><tr>
<td colspan="3">${o(i.totalCost)}</td>
<td class="num">${o(t.cost(r.totalCost))}</td>
<td class="num">${r.totalDuration?o(t.duration(r.totalDuration)):""}</td><td></td>
</tr></tfoot>
</table>
${b.capped?`<p class="cap-note">${o(i.capNote)}</p>`:""}
</body>
</html>`}var Z=25,V=5,tt={completed:C.ok,missed:C.overdue,reset:C.paused,skipped:"var(--secondary-text-color)"},_=class extends H{constructor(){super(...arguments);this.areaKey="";this.objects=[];this.showArchived=!1;this.currencySymbol="";this.userName=()=>null;this._from="";this._to="";this._entryFilter="";this._taskQuery="";this._expanded=!1;this._printing=!1;this._histories=new X(this);this._localeReady=!1;this._filtersFor=null;this._memo=null}createRenderRoot(){return this}get _lang(){return F(this.hass)}willUpdate(t){if(super.willUpdate(t),t.has("areaKey")&&this._filtersFor!==this.areaKey){this._filtersFor=this.areaKey;let s=R();this._from=s.from,this._to=s.to,this._entryFilter="",this._taskQuery="",this._expanded=!1}if(t.has("objects")||t.has("areaKey")||t.has("showArchived")||t.has("hass")&&t.get("hass")===void 0){let s=this._areaObjects().flatMap(l=>l.tasks.map(d=>({entryId:l.entry_id,task:d})));this._histories.sync(this.hass,s)}}updated(t){super.updated(t),!this._localeReady&&this.hass&&(this._localeReady=!0,L(this._lang).then(()=>this.requestUpdate()))}_areaObjects(){return z(this.objects,this.areaKey,this.showArchived).sort((t,s)=>t.object.name.localeCompare(s.object.name))}_entries(t){let s=[this.objects,this.areaKey,this.showArchived,this._histories.version];if(this._memo&&this._memo.deps.every((d,r)=>d===s[r]))return this._memo.entries;let l=Q(t,(d,r)=>this._histories.historyOf(d,r));return this._memo={deps:s,entries:l},l}_figures(){let t=this._areaObjects(),s=this._entries(t),l={from:this._from||null,to:this._to||null},d=U(s,{...l,entryId:this._entryFilter||null,taskQuery:this._taskQuery}),r=this._entryFilter?t.filter(h=>h.entry_id===this._entryFilter):t;return{objects:t,entries:s,filtered:d,totals:Y(d),buckets:G(d,l),byObject:q(d,r)}}_setRange(t){this._from=t.from,this._to=t.to,this._expanded=!1}_openObject(t){this.dispatchEvent(new CustomEvent("open-object",{detail:{entryId:t},bubbles:!0,composed:!0}))}_openTask(t,s){this.dispatchEvent(new CustomEvent("open-task",{detail:{entryId:t,taskId:s},bubbles:!0,composed:!0}))}_bucketLabel(t,s){return t.month==null?String(t.year):`${T(t.month,this._lang,s)} ${t.year}`}_countText(t,s,l){let d=this._lang;return l===1?a(s,d):a(t,d).replace("{n}",g(l,d))}async _print(){if(this._printing)return;let t=K();this._printing=!0;try{await this._histories.settled()}finally{this._printing=!1}let s=this._lang,l=this.currencySymbol,{filtered:d,totals:r,buckets:h,byObject:u}=this._figures(),f={title:a("area_report_title",s),generated:a("report_generated",s),period:a("area_report_period",s),completions:a("area_kpi_completions",s),totalCost:a("total_cost",s),avgCost:a("avg_cost",s),totalTime:a("area_kpi_total_time",s),costPerObject:a("area_cost_per_object",s),costPerBucket:h.unit==="month"?a("area_cost_per_month",s):a("area_cost_per_year",s),share:a("area_cost_share",s),historyHeading:a("area_history_section",s),colDate:a("date",s),colObject:a("object",s),colTask:a("task_name",s),colCost:a("cost",s),colDuration:a("duration",s),colNotes:a("notes_label",s),completedBy:a("completed_by",s),capNote:a("object_history_cap_note",s),none:"\u2014"},c=d.filter(n=>n.type==="completed"),m=d.map(n=>({...n,completedBy:n.completedBy?this.userName(n.completedBy):null,notes:n.notes?B(n.notes,s):null})),e=J({areaName:E(this.areaKey,this.hass?.areas,a("no_area",s)),from:this._from||S(c[c.length-1]?.timestamp)||null,to:this._to||O(),totals:r,byObject:u,buckets:h,entries:m,capped:this._histories.capped},f,{date:n=>N(n,s),cost:n=>v(n,l,s),duration:n=>w(n,s),bucket:n=>this._bucketLabel(n,"long"),share:n=>`${g(n*100,s,0)} %`,number:n=>g(n,s)},new Date().toISOString());P(e,t)}_renderRangeChips(t){let s=this._lang,l=O(),d=R(l),r=u=>this._from===u.from&&this._to===u.to,h=W(t,Number(l.slice(0,4))).slice(0,V);return p`
<div class="filter-chips area-range-chips">
<button class="filter-chip ${r(d)?"active":""}" @click=${()=>this._setRange(R())}>
${a("area_range_12m",s)}
</button>
${h.map(u=>p`
<button class="filter-chip ${r(A(u))?"active":""}" @click=${()=>this._setRange(A(u))}>${u}</button>
`)}
<button class="filter-chip ${!this._from&&!this._to?"active":""}" @click=${()=>this._setRange({from:"",to:""})}>
${a("area_range_all",s)}
</button>
</div>
`}_renderChart(t){let s=this._lang,l=this.currencySymbol,d=t.reduce((c,m)=>Math.max(c,m.cost),0),r=t.reduce((c,m)=>Math.min(c,m.cost),0);if(d<=0&&r>=0)return p`<p class="area-empty">${a("area_no_costs",s)}</p>`;let h=c=>Math.round(c/(d-r)*1e3)/10,u=Math.max(1,Math.ceil(t.length/12)),f=t.map((c,m)=>({b:c,i:m})).filter(({i:c})=>c%u===0);return p`
<div class="area-chart">
<div class="area-chart-max">${v(d,l,s)}</div>
<div class="area-chart-bars">
${r<0?p`<div class="area-chart-zero" style="bottom: ${h(-r)}%"></div>`:y}
${t.map(c=>{let m=`${this._bucketLabel(c,"long")}: ${v(c.cost,l,s)} \xB7 ${a("area_kpi_completions",s)}: ${g(c.completions,s)}`,e=c.cost<0?h(c.cost-r):h(-r);return p`<div class="area-bar" title=${m} aria-label=${m}>
${c.cost!==0?p`<div class="area-bar-fill ${c.cost<0?"credit":""}" style="height: ${h(Math.abs(c.cost))}%${e?`; bottom: ${e}%`:""}"></div>`:y}
</div>`})}
</div>
${r<0?p`<div class="area-chart-min">${v(r,l,s)}</div>`:y}
<div class="area-chart-axis ${u>1?"grouped":""}">
${f.map(({b:c,i:m})=>p`<span class="area-bar-label" style="flex-grow: ${Math.min(u,t.length-m)}">${c.month==null?String(c.year):p`${T(c.month,s,"short")}${c.month===0||m===0?p`<br />${c.year}`:y}`}</span>`)}
</div>
</div>
`}render(){if(!this.hass||!this.areaKey)return y;let t=this._lang,s=this.currencySymbol,{objects:l,entries:d,filtered:r,totals:h,buckets:u,byObject:f}=this._figures(),c=l.reduce((e,n)=>e+n.tasks.filter(x=>this.showArchived||!x.archived).length,0),m=this._expanded?r:r.slice(0,Z);return p`
<div class="detail-section area-view">
<div class="detail-header">
<h2 class="area-title">
<ha-icon .icon=${M(this.areaKey,this.hass.areas)}></ha-icon>
${E(this.areaKey,this.hass.areas,a("no_area",t))}
</h2>
<div class="action-buttons">
<ha-button appearance="plain" class="area-print" .disabled=${this._printing} @click=${()=>this._print()}>
<ha-icon icon="mdi:printer-outline"></ha-icon>
${this._printing?a("loading",t):a("area_report_print",t)}
</ha-button>
</div>
</div>
<p class="meta">
${this._countText("area_object_count","area_object_count_one",l.length)} ·
${this._countText("templates_task_count","templates_task_count_one",c)}
${this._histories.loading?p` · <span class="area-loading">${a("loading",t)}</span>`:y}
</p>
${this._renderRangeChips(d)}
<div class="filter-bar area-filters">
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${t}
.label=${a("date_from",t)}
.value=${this._from}
@value-changed=${e=>{this._from=e.detail.value}}
></ms-date-field>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${t}
.label=${a("date_to",t)}
.value=${this._to}
@value-changed=${e=>{this._to=e.detail.value}}
></ms-date-field>
<label class="filter-field">
<span class="filter-label">${a("object",t)}</span>
<select class="area-object-filter" .value=${this._entryFilter}
@change=${e=>{this._entryFilter=e.target.value}}>
<option value="" ?selected=${!this._entryFilter}>${a("all_objects",t)}</option>
${l.map(e=>p`<option value=${e.entry_id} ?selected=${e.entry_id===this._entryFilter}>${e.object.name}</option>`)}
</select>
</label>
<input
type="search"
class="search-input area-task-filter"
aria-label=${a("area_task_filter",t)}
placeholder=${a("area_task_filter",t)}
.value=${this._taskQuery}
@input=${e=>{this._taskQuery=e.target.value}}
/>
</div>
<div class="kpi-bar area-kpis">
<div class="kpi-card">
<div class="kpi-label">${a("area_kpi_completions",t)}</div>
<div class="kpi-value-large area-kpi-completions">${g(h.completions,t)}</div>
</div>
<div class="kpi-card">
<div class="kpi-label">${a("total_cost",t)}</div>
<div class="kpi-value-large area-kpi-cost">${v(h.totalCost,s,t)}</div>
</div>
<div class="kpi-card">
<div class="kpi-label">${a("avg_cost",t)}</div>
<div class="kpi-value-large">${h.avgCost!=null?v(h.avgCost,s,t):"\u2014"}</div>
</div>
<div class="kpi-card">
<div class="kpi-label">${a("area_kpi_total_time",t)}</div>
<div class="kpi-value-large">${h.totalDuration?w(h.totalDuration,t):"\u2014"}</div>
</div>
</div>
<h3>${u.unit==="month"?a("area_cost_per_month",t):a("area_cost_per_year",t)}</h3>
${this._renderChart(u.buckets)}
<h3>${a("area_cost_per_object",t)}</h3>
<div class="objects-table-wrap">
<table class="objects-table area-object-table">
<thead>
<tr>
<th>${a("object",t)}</th>
<th class="num">${a("area_kpi_completions",t)}</th>
<th class="num">${a("cost",t)}</th>
<th class="num">${a("area_cost_share",t)}</th>
</tr>
</thead>
<tbody>
${f.map(e=>p`
<tr class="objects-table-row" @click=${()=>this._openObject(e.entryId)}>
<td><span class="objects-table-name">${e.objectName}</span></td>
<td class="num">${g(e.completions,t)}</td>
<td class="num">${v(e.cost,s,t)}</td>
<td class="num">
<span class="area-share"><span class="area-share-fill" style="width: ${Math.round(e.share*100)}%"></span></span>
${g(e.share*100,t,0)} %
</td>
</tr>
`)}
</tbody>
</table>
</div>
<h3>
${a("area_history_section",t)}
<span class="area-count">${g(r.length,t)}</span>
</h3>
${r.length===0?p`<p class="area-empty">${a("object_history_empty",t)}</p>`:p`
<div class="objects-table-wrap">
<table class="objects-table area-history-table">
<thead>
<tr>
<th>${a("date",t)}</th>
<th></th>
<th>${a("object",t)}</th>
<th>${a("task_name",t)}</th>
<th class="num">${a("cost",t)}</th>
<th class="num">${a("duration",t)}</th>
<th>${a("notes_label",t)}</th>
</tr>
</thead>
<tbody>
${m.map(e=>{let n=e.notes?B(e.notes,t):"";return p`
<tr class="objects-table-row area-history-row" @click=${()=>this._openTask(e.entryId,e.taskId)}>
<td title=${I(e.timestamp,t)}>${N(e.timestamp,t)}</td>
<td>
<span class="area-entry-type" style="color: ${tt[e.type]??"inherit"}">
<ha-icon .icon=${D[e.type]||"mdi:circle"}></ha-icon>${a(e.type,t)}
</span>
</td>
<td>
<button class="area-object-link" @click=${x=>{x.stopPropagation(),this._openObject(e.entryId)}}>
${e.objectName}
</button>
</td>
<td><button class="area-task-link">${e.taskName}${e.phaseName?` \xB7 ${e.phaseName}`:""}</button></td>
<td class="num">${e.cost!=null?v(e.cost,s,t):"\u2014"}</td>
<td class="num">${e.duration!=null?w(e.duration,t):"\u2014"}</td>
<td class="oc-notes" title=${n}>${n||"\u2014"}</td>
</tr>
`})}
</tbody>
</table>
</div>
${r.length>m.length?p`<ha-button appearance="plain" class="area-more" @click=${()=>{this._expanded=!0}}>
${a("show_all",t)} (${g(r.length,t)})
</ha-button>`:y}
`}
${this._histories.capped?p`<p class="area-cap-note">${a("object_history_cap_note",t)}</p>`:y}
</div>
`}};$([k({attribute:!1})],_.prototype,"hass",2),$([k()],_.prototype,"areaKey",2),$([k({attribute:!1})],_.prototype,"objects",2),$([k({type:Boolean})],_.prototype,"showArchived",2),$([k()],_.prototype,"currencySymbol",2),$([k({attribute:!1})],_.prototype,"userName",2),$([j()],_.prototype,"_from",2),$([j()],_.prototype,"_to",2),$([j()],_.prototype,"_entryFilter",2),$([j()],_.prototype,"_taskQuery",2),$([j()],_.prototype,"_expanded",2),$([j()],_.prototype,"_printing",2);customElements.get("maintenance-area-view")||customElements.define("maintenance-area-view",_);export{_ as MaintenanceAreaView};
@@ -0,0 +1,54 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as O}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-H44SM3WA.js";import{q as j,r as E,s as L,t as R,u as C}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FH35SLMS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EOMYEBQ4.js";import{a as _,b as S,c as A}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VZUMUVFP.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-MMIPM7EE.js";import{I as g,L as k,M as v,P as w,a as l,c as a,f as m,h as y,m as p,n as b,t as r,v as $,z as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var q=[{key:"name",label:"area",num:!1},{key:"objects",label:"objects",num:!0},{key:"tasks",label:"tasks",num:!0},{key:"overdue",label:"overdue",num:!0},{key:"due_soon",label:"due_soon",num:!0},{key:"cost_year",label:"area_cost_year",num:!0},{key:"cost_total",label:"total_cost",num:!0},{key:"last",label:"last_performed",num:!1}],i=class extends y{constructor(){super(...arguments);this.objects=[];this.showArchived=!1;this.currencySymbol="";this._query="";this._sort=E(S(_.areaSort));this._histories=new O(this);this._localeReady=!1;this._memo=null}createRenderRoot(){return this}get _lang(){return g(this.hass)}willUpdate(t){if(super.willUpdate(t),t.has("objects")||t.has("showArchived")||t.has("hass")&&t.get("hass")===void 0){let o=this.objects.filter(s=>this.showArchived||!s.object.archived).flatMap(s=>s.tasks.map(n=>({entryId:s.entry_id,task:n})));this._histories.sync(this.hass,o)}}updated(t){super.updated(t),!this._localeReady&&this.hass&&(this._localeReady=!0,$(this._lang).then(()=>this.requestUpdate()))}_rows(t){let o=this._lang,s=[this.objects,this.showArchived,this._histories.version,this.hass?.areas,o,t];if(this._memo&&this._memo.deps.every((c,h)=>c===s[h]))return this._memo.rows;let n=j(this.objects,{showArchived:this.showArchived,areas:this.hass?.areas,noAreaLabel:r("no_area",o),year:t,historyOf:(c,h)=>this._histories.historyOf(c,h)});return this._memo={deps:s,rows:n},n}_setSort(t){this._sort=L(this._sort,t),A(_.areaSort,`${this._sort.key}:${this._sort.dir}`)}_open(t){this.dispatchEvent(new CustomEvent("open-area",{detail:{areaKey:t},bubbles:!0,composed:!0}))}_toggleArchived(){this.dispatchEvent(new CustomEvent("archived-toggle",{bubbles:!0,composed:!0}))}_headerLabel(t,o,s){return t==="area_cost_year"?r("area_cost_year",s).replace("{year}",String(o)):r(t,s)}render(){if(!this.hass)return m;let t=this._lang,o=Number(f().slice(0,4)),s=this.currencySymbol,n=this._rows(o),c=R(C(n,this._query),this._sort),h=this.objects.filter(e=>e.object.archived).length,d=e=>k(e,t);return a`
<div class="filter-bar area-filter-bar">
<input
type="search"
class="search-input"
aria-label=${r("areas_filter",t)}
placeholder=${r("areas_filter",t)}
.value=${this._query}
@input=${e=>{this._query=e.target.value}}
/>
${this._histories.loading?a`<span class="area-loading">${r("loading",t)}</span>`:m}
${h>0?a`<ha-button class="archived-toggle ${this.showArchived?"active":""}" @click=${()=>this._toggleArchived()}>
<ha-icon icon="mdi:archive-outline"></ha-icon>
${this.showArchived?r("hide_archived",t):`${r("show_archived",t)} (${h})`}
</ha-button>`:m}
</div>
${n.length===0?a`<div class="empty-state">${r("no_objects",t)}</div>`:c.length===0?a`<div class="empty-state">${r("areas_filter_none",t)}</div>`:a`
<div class="objects-table-wrap">
<table class="objects-table areas-table">
<thead>
<tr>
${q.map(e=>{let u=this._sort.key===e.key;return a`<th class=${e.num?"num":""}
aria-sort=${u?this._sort.dir==="asc"?"ascending":"descending":"none"}>
<button class="area-sort ${u?"active":""}" title=${r("sort_label",t)} @click=${()=>this._setSort(e.key)}>
${this._headerLabel(e.label,o,t)}
${u?a`<ha-icon icon=${this._sort.dir==="asc"?"mdi:arrow-up":"mdi:arrow-down"}></ha-icon>`:m}
</button>
</th>`})}
</tr>
</thead>
<tbody>
${c.map(e=>a`
<tr class="objects-table-row" tabindex="0"
@click=${()=>this._open(e.key)}
@keydown=${u=>{u.key==="Enter"&&this._open(e.key)}}>
<td>
<ha-icon class="area-row-icon" .icon=${e.icon}></ha-icon>
<span class="objects-table-name">${e.name}</span>
</td>
<td class="num">${d(e.objects)}</td>
<td class="num">${d(e.tasks)}</td>
<td class="num">${e.overdue?a`<span class="area-count overdue">${d(e.overdue)}</span>`:d(0)}</td>
<td class="num">${e.dueSoon?a`<span class="area-count due-soon">${d(e.dueSoon)}</span>`:d(0)}</td>
<td class="num">${v(e.costYear,s,t)}</td>
<td class="num">${v(e.costTotal,s,t)}</td>
<td>${e.lastCompletion?w(e.lastCompletion,t):"\u2014"}</td>
</tr>
`)}
</tbody>
</table>
</div>
`}
`}};l([p({attribute:!1})],i.prototype,"hass",2),l([p({attribute:!1})],i.prototype,"objects",2),l([p({type:Boolean})],i.prototype,"showArchived",2),l([p()],i.prototype,"currencySymbol",2),l([b()],i.prototype,"_query",2),l([b()],i.prototype,"_sort",2);customElements.get("maintenance-areas-view")||customElements.define("maintenance-areas-view",i);export{i as MaintenanceAreasView};
@@ -0,0 +1,113 @@
/*! maintenance_supporter frontend 2.103.0 */
import"/maintenance_supporter_panelfiles/panel-chunks/chunk-5TWB2KD3.js";import{I as u,a as n,b as _,c as o,f as p,h as c,m as g,n as a,t as s}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var d=["round_robin","least_completed","random"],v=["low","normal","high"],m=365,i=class extends c{constructor(){super(...arguments);this._open=!1;this._mode="assign";this._count=0;this._users=[];this._present=[];this._assignKind="one";this._person="";this._pool=new Set;this._rotation=d[0];this._addText="";this._remove=new Set;this._setWarning=!1;this._warning=7;this._setPriority=!1;this._priority="normal";this._setMute=!1;this._mute=!1;this._resolve=null}get _lang(){return u(this.hass)}open(t,e,r){return this._resolve?.(null),this._mode=t,this._count=e.length,this._users=[...r].sort((l,h)=>l.name.localeCompare(h.name)),this._present=[...new Set(e.flatMap(l=>l.labels||[]))].sort((l,h)=>l.localeCompare(h)),this._assignKind="one",this._person="",this._pool=new Set,this._rotation=d[0],this._addText="",this._remove=new Set,this._setWarning=!1,this._warning=7,this._setPriority=!1,this._priority="normal",this._setMute=!1,this._mute=!1,this._open=!0,new Promise(l=>{this._resolve=l})}_finish(t){this._open=!1;let e=this._resolve;this._resolve=null,e?.(t)}changes(){if(this._mode==="assign"){if(this._assignKind==="one")return{responsible_user_id:this._person||null,assignee_pool:null,rotation_strategy:null};let e=this._users.map(r=>r.id).filter(r=>this._pool.has(r));return e.length<2?null:{assignee_pool:e,rotation_strategy:this._rotation,responsible_user_id:null}}if(this._mode==="labels"){let e=[...new Set(this._addText.split(",").map(l=>l.trim()).filter(Boolean))],r=[...this._remove];return e.length||r.length?{labels_add:e,labels_remove:r}:null}let t={};return this._setWarning&&(t.warning_days=this._warning),this._setPriority&&(t.priority=this._priority),this._setMute&&(t.notify_enabled=!this._mute),Object.keys(t).length?t:null}_toggle(t,e){let r=new Set(t);return r.has(e)?r.delete(e):r.add(e),r}_renderAssign(t){return o`
<div class="hint">${s("bulk_assign_hint",t)}</div>
<label class="choice">
<input type="radio" name="kind" .checked=${this._assignKind==="one"} @change=${()=>this._assignKind="one"} />
${s("bulk_assign_one",t)}
</label>
${this._assignKind==="one"?o`<select class="person" .value=${this._person} @change=${e=>this._person=e.target.value}>
<option value="" ?selected=${!this._person}>${s("unassigned",t)}</option>
${this._users.map(e=>o`<option value=${e.id} ?selected=${this._person===e.id}>${e.name}</option>`)}
</select>`:p}
<label class="choice">
<input type="radio" name="kind" .checked=${this._assignKind==="rotate"} @change=${()=>this._assignKind="rotate"} />
${s("bulk_assign_rotate",t)}
</label>
${this._assignKind==="rotate"?o`<div class="pool">
${this._users.map(e=>o`<label class="row">
<input type="checkbox" .checked=${this._pool.has(e.id)} @change=${()=>this._pool=this._toggle(this._pool,e.id)} />
${e.name}
</label>`)}
</div>
<label class="field">
<span>${s("rotation_strategy",t)}</span>
<select class="rotation" .value=${this._rotation} @change=${e=>this._rotation=e.target.value}>
${d.map(e=>o`<option value=${e} ?selected=${this._rotation===e}>${s(`rotation_${e}`,t)}</option>`)}
</select>
</label>
${this._pool.size<2?o`<div class="hint">${s("bulk_rotation_min_two",t)}</div>`:p}`:p}
`}_renderLabels(t){return o`
<ms-textfield
class="labels-add"
label=${s("bulk_labels_add",t)}
.helper=${s("labels_help",t)}
.value=${this._addText}
@input=${e=>this._addText=e.target.value}
></ms-textfield>
${this._present.length?o`<div class="section-title">${s("bulk_labels_remove",t)}</div>
<div class="pool">
${this._present.map(e=>o`<label class="row">
<input type="checkbox" .checked=${this._remove.has(e)} @change=${()=>this._remove=this._toggle(this._remove,e)} />
${e}
</label>`)}
</div>`:p}
`}_renderEdit(t){return o`
<div class="hint">${s("bulk_edit_hint",t)}</div>
<div class="row edit-row">
<input type="checkbox" class="set-warning" .checked=${this._setWarning} @change=${e=>this._setWarning=e.target.checked} />
<span class="grow">${s("warning_days",t)}</span>
<input type="number" class="warning" min="0" max=${m} .value=${String(this._warning)} ?disabled=${!this._setWarning}
@change=${e=>{let r=parseInt(e.target.value,10);this._warning=Number.isInteger(r)?Math.min(Math.max(r,0),m):this._warning}} />
</div>
<div class="row edit-row">
<input type="checkbox" class="set-priority" .checked=${this._setPriority} @change=${e=>this._setPriority=e.target.checked} />
<span class="grow">${s("priority",t)}</span>
<select class="priority" .value=${this._priority} ?disabled=${!this._setPriority} @change=${e=>this._priority=e.target.value}>
${v.map(e=>o`<option value=${e} ?selected=${this._priority===e}>${s(`priority_${e}`,t)}</option>`)}
</select>
</div>
<div class="row edit-row">
<input type="checkbox" class="set-mute" .checked=${this._setMute} @change=${e=>this._setMute=e.target.checked} />
<span class="grow">${s("bulk_no_notifications",t)}</span>
<input type="checkbox" class="mute" .checked=${this._mute} ?disabled=${!this._setMute} @change=${e=>this._mute=e.target.checked} />
</div>
`}render(){if(!this._open)return o``;let t=this._lang,e=this._mode==="assign"?s("bulk_assign",t):this._mode==="labels"?s("labels",t):s("edit",t),r=this.changes()!==null;return o`
<ha-dialog open @closed=${()=>this._finish(null)}>
<div class="dialog-title">${e}</div>
<div class="content">
<div class="count">${s("bulk_n_selected",t).replace("{n}",String(this._count))}</div>
${this._mode==="assign"?this._renderAssign(t):this._mode==="labels"?this._renderLabels(t):this._renderEdit(t)}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${()=>this._finish(null)}>${s("cancel",t)}</ha-button>
<ha-button class="apply" .disabled=${!r} @click=${()=>this._finish(this.changes())}>${s("save",t)}</ha-button>
</div>
</ha-dialog>
`}};i.styles=_`
.dialog-title { font-size: 18px; font-weight: 500; padding-bottom: 12px; }
.content {
display: flex;
flex-direction: column;
gap: 10px;
min-width: min(360px, calc(100vw - 24px));
max-width: 520px;
max-height: 60vh;
overflow-y: auto;
}
@media (max-width: 600px) {
.content { min-width: 0; max-width: none; max-height: none; }
}
.count, .hint { color: var(--secondary-text-color); font-size: 13px; }
.section-title {
font-size: 14px;
font-weight: 500;
padding-bottom: 4px;
border-bottom: 1px solid var(--divider-color);
}
.choice, .row { display: flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.pool { display: flex; flex-direction: column; gap: 4px; padding-left: 26px; }
.field { display: flex; align-items: center; gap: 8px; justify-content: space-between; font-size: 14px; }
.grow { flex: 1; min-width: 0; }
select, input[type="number"] {
font: inherit;
padding: 4px 6px;
border: 1px solid var(--divider-color);
border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
max-width: 100%;
}
input[type="number"] { width: 72px; }
select.person { margin-left: 26px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 16px; }
`,n([g({attribute:!1})],i.prototype,"hass",2),n([a()],i.prototype,"_open",2),n([a()],i.prototype,"_mode",2),n([a()],i.prototype,"_count",2),n([a()],i.prototype,"_users",2),n([a()],i.prototype,"_present",2),n([a()],i.prototype,"_assignKind",2),n([a()],i.prototype,"_person",2),n([a()],i.prototype,"_pool",2),n([a()],i.prototype,"_rotation",2),n([a()],i.prototype,"_addText",2),n([a()],i.prototype,"_remove",2),n([a()],i.prototype,"_setWarning",2),n([a()],i.prototype,"_warning",2),n([a()],i.prototype,"_setPriority",2),n([a()],i.prototype,"_priority",2),n([a()],i.prototype,"_setMute",2),n([a()],i.prototype,"_mute",2);customElements.get("maintenance-bulk-edit-dialog")||customElements.define("maintenance-bulk-edit-dialog",i);export{i as MaintenanceBulkEditDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
function r(t){return String(t??"").replace(/[&<>"']/g,n=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[n])}export{r as a};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
var n={default_warning_days:[0,365],default_consumable_threshold:[1,90],battery_low_percent:[1,90],battery_recovered_percent:[20,100],archive_oneoff_days:[0,3650],delete_archived_oneoff_days:[0,3650],notify_due_soon_interval_hours:[0,720],notify_overdue_interval_hours:[0,720],notify_triggered_interval_hours:[0,720],max_notifications_per_day:[0,1e3],notification_bundle_threshold:[2,20],snooze_duration_hours:[1,168],warranty_reminder_days:[1,365],budget_alert_threshold:[10,100],currency_decimals:[0,3]};function o(e){let r=n[e];if(!r)throw new Error(`not an int-ranged setting: ${e}`);return r}var t=[0,14],_=[0,1440],u=[1,1e4],a=[0,365],d=15,s=[.1,.9],b=[0,365],A=[.01,1e4],E=[.01,1e5],i=[.1,5];export{n as a,o as b,t as c,_ as d,u as e,a as f,d as g,s as h,b as i,A as j,E as k,i as l};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,256 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import{I as v,a as r,b as _,c as l,f as o,h,m as c,n,t as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var s=class extends h{constructor(){super(...arguments);this.objects=[];this.placesEnabled=!1;this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._place="";this._remindOnSite=!1;this._entryId=null;this._replacing=!1;this._oldDeviceId="";this._replaceDevice="keep"}get _lang(){return v(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._place="",this._remindOnSite=!1,this._replacing=!1,this._error="",this._open=!0}openReplace(e,a){this._entryId=e,this._replacing=!0,this._name=a.name||"",this._oldDeviceId=a.ha_device_id||"",this._haDeviceId="",this._replaceDevice="keep",this._error="",this._open=!0}openEdit(e,a){this._entryId=e,this._name=a.name||"",this._manufacturer=a.manufacturer||"",this._model=a.model||"",this._serialNumber=a.serial_number||"",this._areaId=a.area_id||"",this._installationDate=a.installation_date||"",this._warrantyExpiry=a.warranty_expiry||"",this._documentationUrl=a.documentation_url||"",this._notes=a.notes||"",this._haDeviceId=a.ha_device_id||"",this._parentEntryId=a.parent_entry_id||"",this._place=a.place||"",this._remindOnSite=!!a.remind_on_site,this._replacing=!1,this._error="",this._open=!0}async _replace(){if(this._loading||!this._entryId)return;let e={type:"maintenance_supporter/object/replace",entry_id:this._entryId,name:this._name.trim()||null};this._oldDeviceId?(this._replaceDevice==="other"&&(e.ha_device_id=this._haDeviceId),this._replaceDevice==="none"&&(e.ha_device_id=null)):this._haDeviceId&&(e.ha_device_id=this._haDeviceId);let a=await p(this,e,{busy:t=>{this._loading=t},fallbackKey:"save_error",onError:t=>{this._error=t}});a!==void 0&&(this._open=!1,this.dispatchEvent(new CustomEvent("object-replaced",{detail:{entry_id:a?.entry_id,device_swap:a?.device_swap}})))}_deviceName(e){let t=this.hass?.devices?.[e];return t?.name_by_user||t?.name||e}_deviceSelector(e){return l`<ha-form
.hass=${this.hass}
.data=${{device:this._haDeviceId||void 0}}
.schema=${[{name:"device",selector:{device:{}}}]}
.computeLabel=${()=>e}
@value-changed=${a=>this._haDeviceId=a.detail.value?.device||""}
></ha-form>`}_renderReplace(){let e=this._lang,a=(d,u)=>l`<label class="radio-row">
<input
type="radio"
name="replace-device"
.checked=${this._replaceDevice===d}
@change=${()=>{this._replaceDevice=d}}
/>
<span>${u}</span>
</label>`,t=this._replaceDevice==="other"&&!this._haDeviceId;return l`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${i("replace_object",e)}</div>
<div class="content">
${this._error?l`<div class="error">${this._error}</div>`:o}
<div class="hint">${i("replace_object_prompt",e)}</div>
<ms-textfield
label="${i("replace_name_label",e)}"
.value=${this._name}
@input=${d=>this._name=d.target.value}
></ms-textfield>
${this._oldDeviceId?l`<div class="radio-group" role="radiogroup" aria-label=${i("replace_device_heading",e)}>
<div class="textarea-label">${i("replace_device_heading",e)}</div>
${a("keep",i("replace_device_keep",e).replace("{device}",this._deviceName(this._oldDeviceId)))}
${this._replaceDevice==="keep"?l`<div class="hint">${i("replace_device_keep_hint",e)}</div>`:o}
${a("other",i("replace_device_other",e))}
${this._replaceDevice==="other"?l`${this._deviceSelector(i("replace_device_pick",e))}
<div class="hint">${i("replace_device_other_hint",e)}</div>`:o}
${a("none",i("replace_device_none",e))}
</div>`:this._deviceSelector(i("link_device_optional",e))}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${i("cancel",e)}
</ha-button>
<ha-button @click=${this._replace} .disabled=${this._loading||t}>
${this._loading?i("saving",e):i("replace_object",e)}
</ha-button>
</div>
</ha-dialog>
`}_renderPlace(e){let a=!!this._place&&!this.hass?.states?.[this._place];return l`
<ha-form
.hass=${this.hass}
.data=${{place:this._place||void 0}}
.schema=${[{name:"place",selector:{entity:{domain:"zone"}}}]}
.computeLabel=${()=>i("place_optional",e)}
@value-changed=${t=>{this._place=t.detail.value?.place||"",this._place||(this._remindOnSite=!1)}}
></ha-form>
${a?l`<div class="place-missing">${i("place_missing",e)}</div>`:o}
<label class="place-on-site ${this._place?"":"disabled"}">
<input
type="checkbox"
.checked=${this._remindOnSite}
?disabled=${!this._place}
@change=${t=>this._remindOnSite=t.target.checked}
/>
<span>${i("remind_on_site",e)}</span>
</label>
${this._place?l`<div class="place-hint">${i("remind_on_site_hint",e)}</div>`:o}
`}async _save(){if(this._loading||!this._name.trim())return;this._error="";let e={name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null,...this.placesEnabled?{place:this._place||null,remind_on_site:!!this._place&&this._remindOnSite}:{}},a=await p(this,this._entryId?{type:"maintenance_supporter/object/update",entry_id:this._entryId,...e}:{type:"maintenance_supporter/object/create",...e},{busy:t=>{this._loading=t},fallbackKey:"save_error",onError:t=>{this._error=t}});a!==void 0&&(this._open=!1,this.dispatchEvent(new CustomEvent("object-saved",{detail:{device_swap:a?.device_swap}})))}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;if(this._replacing)return this._renderReplace();let e=this._lang,a=this._entryId?i("edit_object",e):i("new_object",e);return l`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${a}</div>
<div class="content">
${this._error?l`<div class="error">${this._error}</div>`:o}
<ms-textfield
label="${i("name",e)}"
required
.value=${this._name}
@input=${t=>this._name=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("manufacturer_optional",e)}"
.value=${this._manufacturer}
@input=${t=>this._manufacturer=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("model_optional",e)}"
.value=${this._model}
@input=${t=>this._model=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("serial_number_optional",e)}"
.value=${this._serialNumber}
@input=${t=>this._serialNumber=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("documentation_url_optional",e)}"
type="url"
.value=${this._documentationUrl}
@input=${t=>this._documentationUrl=t.target.value}
></ms-textfield>
<ha-area-picker
.hass=${this.hass}
label="${i("area_id_optional",e)}"
.value=${this._areaId}
@value-changed=${t=>this._areaId=t.detail.value||""}
></ha-area-picker>
${this.placesEnabled?this._renderPlace(e):o}
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${e}
label="${i("installation_date_optional",e)}"
.value=${this._installationDate}
@value-changed=${t=>this._installationDate=t.detail.value}
></ms-date-field>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${e}
label="${i("warranty_expiry_optional",e)}"
.value=${this._warrantyExpiry}
@value-changed=${t=>this._warrantyExpiry=t.detail.value}
></ms-date-field>
<ha-form
.hass=${this.hass}
.data=${{device:this._haDeviceId||void 0}}
.schema=${[{name:"device",selector:{device:{}}}]}
.computeLabel=${()=>i("link_device_optional",e)}
@value-changed=${t=>this._haDeviceId=t.detail.value?.device||""}
></ha-form>
${this._parentChoices().length?l`<label class="textarea-field">
<span class="textarea-label">${i("parent_object_optional",e)}</span>
<select
class="parent-select"
.value=${this._parentEntryId}
@change=${t=>this._parentEntryId=t.target.value}
>
<option value="" ?selected=${!this._parentEntryId}>
${i("parent_none",e)}
</option>
${this._parentChoices().map(t=>l`<option
value=${t.entry_id}
?selected=${this._parentEntryId===t.entry_id}
>${t.object.name}</option>`)}
</select>
</label>`:o}
<label class="textarea-field">
<span class="textarea-label">${i("object_notes_optional",e)}</span>
<textarea
rows="3"
.value=${this._notes}
@input=${t=>this._notes=t.target.value}
></textarea>
<span class="md-hint">${i("notes_markdown_hint",e)}</span>
</label>
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${i("cancel",this._lang)}
</ha-button>
<ha-button
@click=${this._save}
.disabled=${this._loading||!this._name.trim()}
>
${this._loading?i("saving",this._lang):i("save",this._lang)}
</ha-button>
</div>
</ha-dialog>
`}};s.styles=_`
.place-on-site {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
color: var(--primary-text-color);
}
.place-on-site.disabled {
color: var(--disabled-text-color, var(--secondary-text-color));
}
.place-hint,
.place-missing {
font-size: 12px;
color: var(--secondary-text-color);
margin-top: -4px;
}
.place-missing {
color: var(--warning-color, #c77700);
}
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
ms-textfield {
display: block;
}
.textarea-field {
display: flex; flex-direction: column; gap: 4px;
}
.textarea-label {
font-size: 12px; color: var(--secondary-text-color, #888); font-weight: 500;
}
.textarea-field textarea {
padding: 8px 10px; font-size: 14px; font-family: inherit;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
resize: vertical;
}
.textarea-field textarea:focus {
outline: none; border-color: var(--primary-color);
}
.md-hint {
font-size: 11px; color: var(--secondary-text-color); font-style: italic;
}
.parent-select {
padding: 8px 10px; font-size: 14px; font-family: inherit;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
.hint {
font-size: 13px;
color: var(--secondary-text-color);
line-height: 1.4;
}
.radio-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.radio-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
}
.radio-row input {
margin: 0;
}
`,r([c({attribute:!1})],s.prototype,"hass",2),r([c({attribute:!1})],s.prototype,"objects",2),r([c({type:Boolean})],s.prototype,"placesEnabled",2),r([n()],s.prototype,"_open",2),r([n()],s.prototype,"_loading",2),r([n()],s.prototype,"_error",2),r([n()],s.prototype,"_name",2),r([n()],s.prototype,"_manufacturer",2),r([n()],s.prototype,"_model",2),r([n()],s.prototype,"_serialNumber",2),r([n()],s.prototype,"_areaId",2),r([n()],s.prototype,"_installationDate",2),r([n()],s.prototype,"_warrantyExpiry",2),r([n()],s.prototype,"_documentationUrl",2),r([n()],s.prototype,"_notes",2),r([n()],s.prototype,"_haDeviceId",2),r([n()],s.prototype,"_parentEntryId",2),r([n()],s.prototype,"_place",2),r([n()],s.prototype,"_remindOnSite",2),r([n()],s.prototype,"_entryId",2),r([n()],s.prototype,"_replacing",2),r([n()],s.prototype,"_oldDeviceId",2),r([n()],s.prototype,"_replaceDevice",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",s);export{s as a};
@@ -0,0 +1,79 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as r,b as l,c as i,f as o,h as n,m as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var e=class extends n{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1;this.multiline=!1;this.rows=3}_onInput(s){let a=s.target.value;this.value=a,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:a}}))}render(){return i`
<label class="field">
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:o}</span>`:o}
${this.multiline?i`
<textarea
.value=${this.value??""}
rows=${this.rows}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
@input=${this._onInput}
@change=${this._onInput}
></textarea>`:i`
<input
.value=${this.value??""}
.type=${this.type}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
step=${this.step??o}
min=${this.min??o}
max=${this.max??o}
pattern=${this.pattern??o}
@input=${this._onInput}
@change=${this._onInput}
/>`}
${this.helper?i`<span class="helper">${this.helper}</span>`:o}
</label>
`}};e.styles=l`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
input {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
}
input:focus {
border-color: var(--primary-color);
}
input:disabled { opacity: 0.5; cursor: not-allowed; }
textarea {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
resize: vertical;
}
textarea:focus { border-color: var(--primary-color); }
textarea:disabled { opacity: 0.5; cursor: not-allowed; }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,r([t()],e.prototype,"label",2),r([t()],e.prototype,"value",2),r([t()],e.prototype,"placeholder",2),r([t()],e.prototype,"type",2),r([t({type:Boolean})],e.prototype,"required",2),r([t({type:Boolean})],e.prototype,"disabled",2),r([t()],e.prototype,"step",2),r([t()],e.prototype,"min",2),r([t()],e.prototype,"max",2),r([t()],e.prototype,"pattern",2),r([t()],e.prototype,"helper",2),r([t({type:Boolean})],e.prototype,"multiline",2),r([t({type:Number})],e.prototype,"rows",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e);
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{c as r,f as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var a=["#c62828","#ad1457","#6a1b9a","#4527a0","#283593","#1565c0","#00838f","#2e7d32","#558b2f","#ef6c00","#6d4c41","#546e7a"];function u(e){let s=(e||"").split(/\s+/).filter(Boolean);return s.length===0?"?":s.length===1?s[0][0].toUpperCase():(s[0][0]+s[s.length-1][0]).toUpperCase()}function c(e){let s=0;for(let n of e)s=s*31+n.charCodeAt(0)>>>0;return a[s%a.length]}function o(e){return e?{id:e.id,name:e.name,initials:e.initials||u(e.name),color:e.color||c(e.id)}:null}function h(e){return e?r`<span class="person-avatar" style="--person-color: ${e.color}" title=${e.name}>${e.initials}</span>`:i}function m(e,s=""){return e?r`<span class="person-chip ${s}" title=${e.name}>${h(e)}<span class="person-name">${e.name}</span></span>`:i}var l=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let n=Date.now();if(!s&&this.usersCache&&n-this.cacheTimestamp<this.CACHE_TTL_MS)return this.usersCache;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/users/list"});return this.usersCache=t.users,this.cacheTimestamp=n,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,n,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:n,user_id:t})}async getTasksByUser(s){return(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tasks/by_user",user_id:s})).tasks}getUserName(s){return!s||!this.usersCache?null:this.usersCache.find(t=>t.id===s)?.name||null}getPerson(s){return o(this.getUser(s))}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(n=>n.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{a,o as b,h as c,m as d,l as e};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{b as s}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-IRRUA3DS.js";async function a(o,n,t=300){return(await o.connection.sendMessagePromise({type:"auth/sign_path",path:n,expires:t})).path}async function r(o,n,t=300){return a(o,`/api/maintenance_supporter/document/${n}`,t)}async function p(o,n,t=""){let e=window.open("about:blank","_blank");try{let i=await r(o,n);e&&(e.location.href=new URL(i+t,window.location.origin).href)}catch(i){throw e&&e.close(),i}}async function l(o,n,t){s(await r(o,n,30),t)}function m(o,n=null){let t=URL.createObjectURL(new Blob([o],{type:"text/html"}));n&&!n.closed?n.location.href=t:window.open(t,"_blank"),setTimeout(()=>URL.revokeObjectURL(t),6e4)}function u(){return window.open("about:blank","_blank")}export{a,r as b,p as c,l as d,m as e,u as f};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
var E={\u00DF:"s","\u1E9E":"s",\u00E6:"a",\u0153:"o",\u00F8:"o",\u0111:"d",\u00F0:"d",\u00FE:"t",\u0142:"l",\u0131:"i"},d=10,R=7,g=4,h=2,S=5,x=3,_=/[a-z0-9]+/g;function u(r){let e="";for(let t of r.toLowerCase()){if(t.charCodeAt(0)<128&&t.length===1){e+=t;continue}let n=E[t];if(n){e+=n;continue}let o=t.normalize("NFKD")[0]||t;e+=o.charCodeAt(0)<128?o:t}return e}function m(r){return u(r).match(_)||[]}function C(r){return u(r).replace(/[^a-z0-9]+/g,"")}function b(r){return r.replace(/ue/g,"u").replace(/oe/g,"o").replace(/ae/g,"a").replace(/ss/g,"s")}function F(r){let e=[];for(let t of m(r)){let n=[t],o=b(t);o&&o!==t&&n.push(o),e.push(n)}return e}function p(r,e){if(r===e)return!0;let t=r.length,n=e.length;if(Math.abs(t-n)>1)return!1;if(t===n){let l=[];for(let i=0;i<t;i++)r[i]!==e[i]&&l.push(i);if(l.length===1)return!0;if(l.length===2&&l[1]===l[0]+1){let i=l[0];return r[i]===e[i+1]&&r[i+1]===e[i]}return!1}let[o,s]=t>n?[r,e]:[e,r],c=0,f=0,a=!1;for(;c<o.length&&f<s.length;)if(o[c]===s[f])c++,f++;else{if(a)return!1;a=!0,c++}return!0}function N(r,e){let t=0;for(let n of r)if(n){if(e===n)return d;if(e.startsWith(n)){t=Math.max(t,R);continue}if(n.length>=x&&e.includes(n)){t=Math.max(t,g);continue}n.length>=S&&t<h&&(p(n,e)||e.length>n.length&&p(n,e.slice(0,n.length)))&&(t=h)}return t}function I(r,e){let t=0;for(let n of m(e))if(t=Math.max(t,N(r,n)),t===d)return t;if(t<g){let n=C(e);for(let o of r)if(o.length>=x&&n.includes(o))return g}return t}function O(r,e){if(!r)return!1;let t=F(e);if(!t.length){let n=u(e.trim());return n.length>0&&u(r).includes(n)}return M(t,[{text:r,weight:1}])>0}function M(r,e){if(!r.length)return 0;let t=e.filter(o=>o.text),n=0;for(let o of r){let s=0;for(let c of t){let f=I(o,c.text)*c.weight;f>s&&(s=f)}if(s===0)return 0;n+=s}return n}export{F as a,O as b,M as c};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,37 @@
/*! maintenance_supporter frontend 2.103.0 */
import{B as k,L as R,N as x,a as m,b as h,ba as w,c as g,f as v,h as b,m as y,n as _,t as l}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";function E(i){return`${i.entry_id??""}\0${i.part_id}`}function S(i,t,e,o){let n=!!i.entry_id&&i.entry_id!==t,r=n?i.entry_id:t,s=e.find(p=>p.entry_id===r),a=(s?.parts||[]).find(p=>p.id===i.part_id)||null,c=n&&s?.object?.name||"",u=a?.name||l("shared_part_unknown",o);return{part:a,foreign:n,ownerName:c,label:c?`${u} (${c})`:u}}function N(i,t,e,o){let{part:n,label:r}=S(i,t,e,o),s=n&&n.stock!==null&&n.stock!==void 0?` (${x(n.stock,n.unit,o)})`:"",a=n?.storage_location?` \u2014 ${n.storage_location}`:"";return`${R(i.quantity,o)}\xD7 ${r}${s}${a}`}function V(i,t,e,o){let r=(e.find(a=>a.entry_id===t)?.parts||[]).map(a=>({...a})),s=new Set(r.map(a=>E({part_id:a.id})));for(let a of i?.consumes_parts||[]){if(!a.entry_id||a.entry_id===t)continue;let c=E(a);if(s.has(c))continue;s.add(c);let{part:u,ownerName:p}=S(a,t,e,o);r.push({id:a.part_id,name:u?.name||l("shared_part_unknown",o),unit:u?.unit,stock:u?.stock??null,storage_location:u?.storage_location,entry_id:a.entry_id,owner_name:p})}return r}var L=20;function T(){let i=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:t=>t.map(()=>Math.floor(Math.random()*256))}).getRandomValues(i),Array.from(i,t=>t.toString(16).padStart(2,"0")).join("")}function f(i){let t=i?.reading_values;if(!Array.isArray(t))return[];let e=[],o=new Set;for(let n of t){if(!n||typeof n!="object")continue;let r=n;typeof r.id!="string"||!r.id||o.has(r.id)||typeof r.value!="number"||!Number.isFinite(r.value)||(o.add(r.id),e.push({id:r.id,name:typeof r.name=="string"?r.name:r.id,unit:typeof r.unit=="string"?r.unit:null,value:r.value}))}return e}function P(i){return i.filter(t=>t.type==="completed"&&(t.reading_value!=null||f(t).length>0)).sort((t,e)=>t.timestamp.localeCompare(e.timestamp))}function M(i){return P(i??[]).map(t=>({timestamp:t.timestamp,values:f(t)})).filter(t=>t.values.length>0)}function A(i,t,e){let o;for(let n of i){if(e!==void 0){let s=k(n.timestamp);if(!isNaN(s)&&s>=e)break}let r=n.values.find(s=>s.id===t);r&&(o=r)}return o}function C(i,t,e){let o=f(t).find(r=>r.id===e);if(!o)return null;let n=null;for(let r of P(i)){if(r.timestamp>=t.timestamp)break;let s=f(r).find(a=>a.id===e);s&&(n=s.value)}return n==null?null:o.value-n}function D(i){let t=new Set,e=new Set;for(let o of i){let n=(o.name||"").trim().toLowerCase();n&&(t.has(n)?e.add(o.id):t.add(n))}return e}function F(i){let t=[],e=new Set,o=new Set;for(let n of i){let r=(n.name||"").trim();if(!(!r||e.has(n.id)||o.has(r.toLowerCase()))&&(e.add(n.id),o.add(r.toLowerCase()),t.push({id:n.id,name:r,unit:(n.unit||"").trim()||null}),t.length>=L))break}return t}function j(i){let t=(i??"").trim();if(t==="")return null;let e=Number(t.replace(",","."));return!Number.isFinite(e)||e<0?null:Math.round(e)}var B=["notes","cost","duration","photo","user"],I={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};var d=class extends b{constructor(){super(...arguments);this.value="";this.lang="en";this._credit=!1;this._amount=""}willUpdate(e){if(!e.has("value"))return;let o=(this.value??"").trim();if(o===this._signed())return;let n=parseFloat(o);if(o===""||Number.isNaN(n)){this._amount="",this._credit=!1;return}this._credit=n<0,this._amount=String(Math.abs(n))}_signed(){let e=this._amount.trim();return e===""?"":this._credit?`-${e}`:e}_emit(){this.value=this._signed(),this.dispatchEvent(new CustomEvent("value-changed",{detail:{value:this.value},bubbles:!0,composed:!0}))}_setCredit(e){this._credit!==e&&(this._credit=e,this._emit())}_onInput(e){let o=e.target.value;o.startsWith("-")&&(this._credit=!0),this._amount=o.replace(/^-+/,""),this._emit()}render(){let e=this.lang;return g`
<div class="wrap">
<div class="sign" role="group">
<button type="button" class="kind cost ${this._credit?"":"on"}" aria-pressed=${String(!this._credit)}
@click=${()=>this._setCredit(!1)}>${l("cost",e)}</button>
<button type="button" class="kind credit ${this._credit?"on":""}" aria-pressed=${String(this._credit)}
@click=${()=>this._setCredit(!0)}>${l("cost_kind_credit",e)}</button>
</div>
<input type="number" step="0.01" min="0" inputmode="decimal" class="field-input amount"
.value=${this._amount} @input=${this._onInput} />
</div>
${this._credit?g`<div class="hint">${l("cost_credit_hint",e)}</div>`:v}
`}};d.styles=[w,h`
:host { display: block; }
.wrap { display: flex; gap: 8px; align-items: stretch; }
.sign {
display: inline-flex;
flex: none;
border: 1px solid var(--divider-color);
border-radius: 6px;
overflow: hidden;
}
.kind {
font: inherit;
font-size: 13px;
padding: 0 10px;
border: none;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
}
.kind.on { background: var(--primary-color); color: var(--text-primary-color, #fff); }
.kind.credit.on { background: var(--success-color, #43a047); }
.amount { flex: 1; min-width: 0; }
.hint { margin-top: 4px; font-size: 12px; color: var(--secondary-text-color); }
`],m([y()],d.prototype,"value",2),m([y()],d.prototype,"lang",2),m([_()],d.prototype,"_credit",2),m([_()],d.prototype,"_amount",2);customElements.get("ms-cost-input")||customElements.define("ms-cost-input",d);export{E as a,N as b,V as c,L as d,T as e,f,M as g,A as h,C as i,D as j,F as k,j as l,B as m,I as n};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{t as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var _=[{key:"name",labelKey:"name",required:!0},{key:"ref_no",labelKey:"ref_number"},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],u=_.map(e=>e.key),i=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function g(e){if(!Array.isArray(e))return[...i];let n=new Set,r=[];for(let t of e)typeof t=="string"&&u.includes(t)&&!n.has(t)&&(n.add(t),r.push(t));return r.length?(r.includes("name")||r.unshift("name"),r):[...i]}function f(e,n){return a(`home_dwelling_${e==="house"||e==="apartment"?e:"unknown"}`,n)}function m(e,n){if(!e)return"";try{return new Intl.DisplayNames([n],{type:"region"}).of(e)||e}catch{return e}}var l={floors:"home_reason_floors",basement_floor:"home_reason_basement",area_basement:"home_reason_basement",area_garden:"home_reason_garden",area_shed:"home_reason_shed",area_driveway:"home_reason_driveway",area_garage:"home_reason_garage",entity_garage:"home_reason_garage",area_attic:"home_reason_attic",entity_lawn_mower:"home_reason_lawn_mower",entity_gate:"home_reason_gate",area_balcony:"home_reason_balcony",small_home:"home_reason_small_home"},c={ups:"home_reason_ups"};function d(e,n){let r=[];for(let t of e){let o=l[t];if(!o)continue;let s=a(o,n);r.includes(s)||r.push(s)}return r.join(", ")}function b(e,n,r){if(e==="starter")return a("home_reason_starter",n);if(e==="country")return a("home_reason_country",n).replace("{country}",m(r,n));if(e.startsWith("feature_")){let t=e.slice(8),o=l[`area_${t}`]??c[t];return a("home_reason_feature",n).replace("{feature}",o?a(o,n):e)}return a(`home_trait_${e}`,n)}export{_ as a,i as b,g as c,f as d,m as e,d as f,b as g};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as y}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FH35SLMS.js";var l=6;async function g(e,i,n){let r=new Array(e.length),a=0,t=async()=>{for(;a<e.length;){let s=a++;r[s]=await n(e[s],s)}},o=Math.min(Math.max(1,i),e.length);return await Promise.all(Array.from({length:o},t)),r}function u(e){let i=e.history?.length??0;return typeof e.history_count!="number"||e.history_count>i}function d(e){return JSON.stringify([e.history_count??null,e.history??[]])}function h(e,i){return`${e}:${i}`}var c=class{constructor(i){this._host=i;this._cache=new Map;this._inFlight=new Map;this._running=0;this._pending=Promise.resolve();this.version=0;i.addController(this)}hostConnected(){}get loading(){return this._running>0}get capped(){for(let i of this._cache.values())if(i.entries.length>=y)return!0;return!1}historyOf(i,n){return this._cache.get(h(i,n.id))?.entries??n.history??[]}sync(i,n){if(!i?.connection)return this._pending;let r=n.filter(t=>u(t.task)).map(t=>({...t,key:h(t.entryId,t.task.id),sig:d(t.task)})).filter(t=>this._cache.get(t.key)?.sig!==t.sig&&this._inFlight.get(t.key)!==t.sig);if(!r.length)return this._pending;for(let t of r)this._inFlight.set(t.key,t.sig);this._running++,this._host.requestUpdate();let a=g(r,l,async t=>{let o;try{let s=await i.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:t.entryId,task_id:t.task.id});o=Array.isArray(s?.history)?s.history:t.task.history??[]}catch{o=t.task.history??[]}this._inFlight.get(t.key)===t.sig&&(this._inFlight.delete(t.key),this._cache.set(t.key,{sig:t.sig,entries:o}))}).then(()=>{this._running--,this.version++,this._host.requestUpdate()});return this._pending=Promise.all([this._pending,a]).then(()=>{}),this._pending}settled(){return this._pending}};export{c as a};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
function c(o,t,e){let l=new Blob([o],{type:e}),d=URL.createObjectURL(l),n=document.createElement("a");n.href=d,n.download=t,n.target="_blank",n.rel="noopener",n.style.display="none",document.body.appendChild(n),n.dispatchEvent(new MouseEvent("click")),document.body.removeChild(n),setTimeout(()=>URL.revokeObjectURL(d),6e4)}function r(o,t){let e=document.createElement("a");e.href=o,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{c as a,r as b};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{P as n,t as o}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var l={"Completed from dashboard button":"hist_note_button","Completed from the To-do list":"hist_note_todo","Completed by voice":"hist_note_voice","Completed from the notification":"hist_note_notification","Completed via NFC tag":"hist_note_nfc","Completed from the shopping list":"hist_note_shopping_list","Completed from a mirrored to-do list":"hist_note_todo_mirror","Skipped from notification":"hist_note_skipped_notification","Skipped from dashboard button":"hist_note_skipped_button","Sensor trigger activated":"hist_note_sensor_triggered","Initial value set during task creation":"hist_note_initial"},m=[[/^Auto-completed: sensor recovered \((.+)\)$/,"hist_note_auto_recovered",t=>({value:t[1]})],[/^Reset to (\d{4}-\d{2}-\d{2})$/,"hist_note_reset",(t,e)=>({date:n(t[1],e)})],[/^Trigger entity replaced: (.+) → (.+)$/,"hist_note_trigger_replaced",t=>({old:t[1],new:t[2]})],[/^Sensor trigger removed \(entity was: (.+)\)\. Schedule converted to (\w+)\.$/,"hist_note_trigger_removed",(t,e)=>({entity:t[1],schedule:o(t[2],e)})],[/^Entity (.+) removed from compound trigger; (\d+) conditions remain\.$/,"hist_note_compound_removed",t=>({entity:t[1],count:t[2]})]];function h(t,e){if(!t)return"";let i=l[t.trim()];if(i)return o(i,e);for(let[s,d,_]of m){let r=t.trim().match(s);if(r){let c=_(r,e);return o(d,e).replace(/\{(\w+)\}/g,(a,g)=>c[g]??a)}}return t}export{h as a};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
function c(n){return n==="use"?"use":"purchase"}var s=n=>typeof n=="number"&&Number.isFinite(n)?n:null;function a(n){let t=s(n?.cost);if(t===null)return null;let o=s(n?.package_size);return o&&o>0?t/o:t}function p(n,t){let o=s(t)&&t>0?t:s(n.restock_quantity)||1,e=s(n.package_size);return Math.round(o*(e&&e>0?e:1)*100)/100}function l(n){let t=s(n.cost);if(n.cost_basis!=="use")return t;let o=s(n.parts_cost),e=n.purchase?null:t;return e===null&&o===null?null:(e??0)+(o??0)}function _(n){if(!n)return[];let t=[],o=n.photo_doc_id;typeof o=="string"&&t.push(o);let e=n.photo_doc_ids;Array.isArray(e)&&t.push(...e);let r=[];for(let u of t){if(typeof u!="string")continue;let i=u.trim();if(!(!i||r.includes(i))&&(r.push(i),r.length>=10))break}return r}export{_ as a,c as b,a as c,p as d,l as e};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,33 @@
/*! maintenance_supporter frontend 2.103.0 */
import{b as r,c as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var d=r`
.backdrop {
position: fixed; inset: 0; z-index: var(--ms-modal-z, 100);
background: rgba(0,0,0,0.5);
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: var(--ms-modal-max-width, 480px);
max-height: var(--ms-modal-max-height, 92vh); overflow: auto;
background: var(--card-background-color, var(--ha-card-background, #1c1c1c));
color: var(--primary-text-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
padding: 20px;
display: flex; flex-direction: column; gap: var(--ms-modal-gap, 14px);
z-index: calc(var(--ms-modal-z, 100) + 1);
}
/* Focused programmatically on open (so Escape works at once) — no ring. */
.dialog:focus { outline: none; }
/* The one part that shrinks and scrolls (min-height: 0); the header and the
buttons around it keep their content's height. */
.dialog-scroll {
flex: 1 1 auto; min-height: 0;
overflow-y: auto; overscroll-behavior: contain;
display: flex; flex-direction: column; gap: var(--ms-modal-gap, 14px);
}
`;function l(o,a){o.key!=="Escape"||o.defaultPrevented||o.composedPath().some(e=>e instanceof Element&&e.localName==="ms-camera-capture")||(o.preventDefault(),o.stopPropagation(),a())}function i(o,a){return t`
<div class="backdrop" @click=${o}></div>
<div class="dialog" role="dialog" aria-modal="true" tabindex="-1"
@keydown=${e=>l(e,o)}>${a}</div>
`}function c(o){o?.querySelector(".dialog")?.focus({preventScroll:!0})}export{d as a,i as b,c};
@@ -0,0 +1,88 @@
/*! maintenance_supporter frontend 2.103.0 */
import{P as m,a,b as y,c as s,f as l,h as g,m as o,n as f,t as d}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";function $(n){let t=m("2001-02-03",n).match(/\d+/g)||[],e=v=>t.findIndex(_=>Number(_)===v),h=e(2001),c=e(2),u=e(3);return h===0?"YMD":c>=0&&u>=0&&c<u?"MDY":"DMY"}function p(n,i,t){if(n<1e3||n>9999||i<1||i>12||t<1||t>31)return null;let e=new Date(n,i-1,t);return e.getFullYear()!==n||e.getMonth()!==i-1||e.getDate()!==t?null:`${n}-${String(i).padStart(2,"0")}-${String(t).padStart(2,"0")}`}function b(n,i){let t=n.trim(),e;if(e=t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return p(+e[1],+e[2],+e[3]);if(e=t.match(/^(\d{4})$/))return p(+e[1],1,1);if(e=t.match(/^(\d{1,2})[./](\d{4})$/))return p(+e[2],+e[1],1);if(e=t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return p(+e[3],+e[2],+e[1]);if(e=t.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let h=+e[1],c=+e[2],u=+e[3];return $(i)==="MDY"?p(u,h,c):p(u,c,h)}return null}function x(n,i){if(i)switch(n){case"date":return i.split("T")[0];case"time":return i.length===5?`${i}:00`:i;case"datetime":{let[t,e="00:00:00"]=i.split("T");return`${t} ${e.length===5?`${e}:00`:e}`}}}function k(n,i){if(typeof i!="string"||!i)return"";switch(n){case"date":return i.slice(0,10);case"time":return i.slice(0,5);case"datetime":{let[t,e="00:00:00"]=i.split(" ");return`${t}T${e.length===5?`${e}:00`:e}`}}}var r=class extends g{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let t=b(this._typed,this.lang);if(!t){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(t)}_onTypedKey(t){t.key==="Enter"?(t.preventDefault(),this._commitTyped()):t.key==="Escape"&&(t.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(t){t.stopPropagation(),this._emit(k(this.kind,t.detail?.value))}_clear(){this._emit("")}_emit(t){t!==this.value&&(this.value=t,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:t}})))}render(){return s`
<div class="field">
${this.label?s`<span class="label">${this.label}${this.required?s`<span class="req">*</span>`:l}</span>`:l}
<div class="row">
${this._typing?s`<input class="typed" type="text" inputmode="numeric" autocomplete="off"
.value=${this._typed}
placeholder=${m("1978-03-15",this.lang)}
aria-invalid=${this._typedInvalid?"true":"false"}
@input=${t=>{this._typed=t.target.value,this._typedInvalid=!1}}
@keydown=${this._onTypedKey}
@blur=${this._commitTyped} />`:s`<ha-selector
.hass=${this.hass}
.selector=${this._selector()}
.value=${x(this.kind,this.value)}
.required=${this.required}
.disabled=${this.disabled}
@value-changed=${this._onSelectorChange}
></ha-selector>`}
${this.kind==="date"&&!this.disabled&&!this._typing?s`<button type="button" class="type-toggle" title=${d("date_type_toggle",this.lang)} aria-label=${d("date_type_toggle",this.lang)} @click=${this._startTyping}>
<ha-icon icon="mdi:keyboard-outline"></ha-icon>
</button>`:l}
${this.clearable&&this.value&&!this.disabled&&!this._typing?s`<button type="button" class="clear" title=${d("clear",this.lang)} aria-label=${d("clear",this.lang)} @click=${this._clear}>
<ha-icon icon="mdi:close"></ha-icon>
</button>`:l}
</div>
${this._typedInvalid?s`<span class="helper invalid">${d("date_type_invalid",this.lang)}</span>`:this.helper?s`<span class="helper">${this.helper}</span>`:l}
</div>
`}};r.styles=y`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
.row {
display: flex;
align-items: center;
gap: 4px;
}
ha-selector {
flex: 1;
min-width: 0;
}
.clear, .type-toggle {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 20px;
}
.clear:hover, .type-toggle:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); }
.clear:focus-visible, .type-toggle:focus-visible { outline: 2px solid var(--primary-color); }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
.helper.invalid { color: var(--error-color, #f44336); font-style: normal; }
input.typed {
flex: 1;
min-width: 0;
height: 40px;
box-sizing: border-box;
padding: 0 12px;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font: inherit;
}
input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); }
input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; }
`,a([o({attribute:!1})],r.prototype,"hass",2),a([o()],r.prototype,"kind",2),a([o()],r.prototype,"label",2),a([o()],r.prototype,"value",2),a([o()],r.prototype,"helper",2),a([o({type:Boolean})],r.prototype,"clearable",2),a([o({type:Boolean})],r.prototype,"disabled",2),a([o({type:Boolean})],r.prototype,"required",2),a([o()],r.prototype,"lang",2),a([f()],r.prototype,"_typing",2),a([f()],r.prototype,"_typed",2),a([f()],r.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",r);
@@ -0,0 +1,234 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as q}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-IRRUA3DS.js";import{I as k,a,b as f,c as n,f as u,h as x,m as h,n as c,t as e}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";function d(r){return r.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function _(r){return!r.startsWith("data:image/svg+xml,")&&!r.startsWith("data:image/png;base64,")?"":d(r)}function y(r){return r.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var l=class extends x{constructor(){super(...arguments);this.lang="";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._quickResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}get _dialogLang(){return this.lang||k(this.hass)}openForObject(t,i){this._entryId=t,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null,this._open=!0,this._generate()}openForTask(t,i,s,o){this._entryId=t,this._taskId=i,this._objectName=s,this._taskName=o,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null,this._open=!0,this._generate()}async _generate(){let t=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null,this._quickResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let s=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&(s.push(this.hass.connection.sendMessagePromise({...i,action:"complete"})),await this._hasQuickCompleteDefaults()&&s.push(this.hass.connection.sendMessagePromise({...i,action:"quick_complete"})));let o=await Promise.all(s);if(t!==this._generateSeq)return;this._viewResult=o[0],o.length>1&&(this._completeResult=o[1]),o.length>2&&(this._quickResult=o[2])}catch(i){if(t!==this._generateSeq)return;let s=i?.code,o=i?.message;this._error=s==="no_url"||typeof o=="string"&&o.includes("No Home Assistant URL")?e("qr_error_no_url",this._dialogLang):e("qr_error",this._dialogLang)}finally{t===this._generateSeq&&(this._loading=!1)}}async _hasQuickCompleteDefaults(){try{let i=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId})).tasks||[]).find(s=>s.id===this._taskId);return!!i?.quick_complete_defaults&&Object.keys(i.quick_complete_defaults).length>0}catch{return!1}}_setUrlMode(t){this._urlMode!==t&&(this._urlMode=t,this._generate())}_print(){if(!this._viewResult)return;let t=this._viewResult,i=t.label.task_name?`${t.label.object_name} \u2014 ${t.label.task_name}`:t.label.object_name,s=[t.label.manufacturer,t.label.model].filter(Boolean).join(" "),o=window.open("","_blank","width=600,height=500");if(!o)return;let p=this._dialogLang,m=d(i),g=d(s),v=!!this._completeResult,b=!!this._quickResult,w=d(e("qr_action_view",p)),$=d(e("qr_action_complete",p)),R=d(e("qr_action_quick_complete",p));o.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="color-scheme" content="light">
<title>${m}</title>
<style>
/* Printable sheet \u2014 must not inherit the phone's dark theme. The QR images
carry their own white quiet zone and stay scannable either way, but the
labels below are explicit dark greys and would vanish on a WebView's dark
canvas. Same reasoning as helpers/report.ts. */
:root{color-scheme:light}
body{font-family:sans-serif;text-align:center;padding:20px;background:#fff;color:#1a1a1a}
h2{margin:0 0 4px}
.sub{color:#666;font-size:14px;margin-bottom:16px}
.qr-row{display:flex;justify-content:center;gap:24px;margin:12px 0}
.qr-col{display:flex;flex-direction:column;align-items:center;gap:6px}
.qr-col img{width:${b?"170px":v?"200px":"280px"}}
.qr-label{font-size:13px;font-weight:500;color:#333}
.url{font-size:10px;color:#999;word-break:break-all;margin-top:8px;max-width:480px}
</style></head><body>
<h2>${m}</h2>
${g?`<div class="sub">${g}</div>`:""}
<div class="qr-row">
<div class="qr-col">
<img src="${_(this._viewResult.svg_data_uri)}" alt="QR Info" />
<div class="qr-label">${w}</div>
</div>
${v?`<div class="qr-col">
<img src="${_(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${$}</div>
</div>`:""}
${b?`<div class="qr-col">
<img src="${_(this._quickResult.svg_data_uri)}" alt="QR Quick-complete" />
<div class="qr-label">${R}</div>
</div>`:""}
</div>
<div class="url">${d(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
</body></html>`),o.document.close()}_downloadSvg(t,i){let s=decodeURIComponent(t.svg_data_uri.replace("data:image/svg+xml,","")),o=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;q(s,`qr-${y(o)}-${i}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._quickResult=null,this._error="",this._loading=!1}render(){if(!this._open)return n``;let t=this._dialogLang,i=this._taskName?`${e("qr_code",t)}: ${this._objectName} \u2014 ${this._taskName}`:`${e("qr_code",t)}: ${this._objectName}`,s=!!this._viewResult;return n`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${i}</div>
<div class="content">
${this._loading?n`<div class="loading">${e("qr_generating",t)}</div>`:this._error?n`<div class="error">${this._error}</div>`:s?n`
<div class="qr-pair">
<div class="qr-item">
<img
class="qr-image ${this._completeResult?"small":""}"
src="${this._viewResult.svg_data_uri}"
alt="QR Info"
/>
<div class="qr-item-label">${e("qr_action_view",t)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._viewResult,"info")}>
<ha-icon icon="mdi:download"></ha-icon>
${e("qr_download",t)}
</button>
</div>
${this._completeResult?n`
<div class="qr-item">
<img
class="qr-image small"
src="${this._completeResult.svg_data_uri}"
alt="QR Complete"
/>
<div class="qr-item-label">${e("qr_action_complete",t)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._completeResult,"complete")}>
<ha-icon icon="mdi:download"></ha-icon>
${e("qr_download",t)}
</button>
</div>
`:u}
${this._quickResult?n`
<div class="qr-item quick">
<img
class="qr-image small"
src="${this._quickResult.svg_data_uri}"
alt="QR Quick-complete"
/>
<div class="qr-item-label">${e("qr_action_quick_complete",t)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._quickResult,"quick-complete")}>
<ha-icon icon="mdi:download"></ha-icon>
${e("qr_download",t)}
</button>
</div>
`:u}
</div>
<div class="url-display">${this._viewResult.url}</div>
`:u}
<div class="action-row">
<label>${e("qr_url_mode",t)}</label>
<div class="action-toggle">
<button class="toggle-btn ${this._urlMode==="companion"?"active":""}"
@click=${()=>this._setUrlMode("companion")}>${e("qr_mode_companion",t)}</button>
<button class="toggle-btn ${this._urlMode==="local"?"active":""}"
@click=${()=>this._setUrlMode("local")}>${e("qr_mode_local",t)}</button>
<button class="toggle-btn ${this._urlMode==="server"?"active":""}"
@click=${()=>this._setUrlMode("server")}>${e("qr_mode_server",t)}</button>
</div>
</div>
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${e("cancel",t)}
</ha-button>
<ha-button
@click=${this._print}
.disabled=${!s}
>
${e("qr_print",t)}
</ha-button>
</div>
</ha-dialog>
`}};l.styles=f`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.qr-pair {
display: flex;
flex-wrap: wrap; /* three codes (quick-complete, #192) wrap on a phone */
gap: 20px;
justify-content: center;
width: 100%;
}
.qr-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.qr-image {
width: 240px;
height: 240px;
image-rendering: pixelated;
}
.qr-image.small {
width: 180px;
height: 180px;
}
.qr-item-label {
max-width: 180px;
font-size: 12px;
font-weight: 500;
color: var(--secondary-text-color);
text-align: center;
}
.dl-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: none;
border: 1px solid var(--divider-color, #e0e0e0);
cursor: pointer;
font-size: 13px;
color: var(--primary-text-color);
padding: 6px 14px;
border-radius: 18px;
transition: background 0.2s, border-color 0.2s;
}
.dl-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
border-color: var(--primary-color);
}
.dl-btn ha-icon {
--mdc-icon-size: 18px;
}
.url-display {
font-size: 11px;
color: var(--secondary-text-color);
word-break: break-all;
text-align: center;
max-width: 400px;
}
.loading {
padding: 40px 0;
color: var(--secondary-text-color);
}
.error {
padding: 20px 0;
color: var(--error-color, #f44336);
}
.action-row {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.action-row label {
font-size: 13px;
color: var(--secondary-text-color);
}
.action-toggle {
display: flex;
gap: 4px;
background: var(--divider-color, #e0e0e0);
border-radius: 6px;
padding: 3px;
}
.toggle-btn {
flex: 1;
padding: 8px 12px;
border: none;
background: transparent;
color: var(--primary-text-color);
cursor: pointer;
border-radius: 4px;
font-size: 13px;
transition: all 0.2s;
line-height: 1.3;
}
.toggle-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.toggle-btn.active {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
`,a([h({attribute:!1})],l.prototype,"hass",2),a([h()],l.prototype,"lang",2),a([c()],l.prototype,"_open",2),a([c()],l.prototype,"_loading",2),a([c()],l.prototype,"_error",2),a([c()],l.prototype,"_viewResult",2),a([c()],l.prototype,"_completeResult",2),a([c()],l.prototype,"_quickResult",2),a([c()],l.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",l);export{l as a};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
var r={objectSections:"msp-object-sections",printOptions:"msp-print-options",batteryRosterOpen:"msp-bf-roster-open",docSort:"msp-doc-sort",cameraLabel:"msp-camera-label",overviewTab:"msp-overview-tab",collapsedSections:"msp-collapsed-sections",chartRange:"msp-chart-range",chartHideOutliers:"msp-chart-hide-outliers",taskSort:"maintenance_supporter_sort",objectSort:"maintenance_supporter_object_sort",groupBy:"maintenance_supporter_groupby",objectView:"maintenance_supporter_object_view",objectsCache:"msp-objects-cache",gettingStartedDismissed:"msp-gs-dismissed",batteryRosterSort:"ms_bf_roster_sort",areaSort:"msp-area-sort"};function s(t){try{return localStorage.getItem(t)}catch{return null}}function o(t,e){try{localStorage.setItem(t,e)}catch{}}export{r as a,s as b,o as c};
@@ -0,0 +1,18 @@
/*! maintenance_supporter frontend 2.103.0 */
import{b as l}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-MMIPM7EE.js";import{a as c}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-2BQZ33DM.js";import{g as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var i={operatorWriteEnabled:!1,operatorIds:[]};function b(e,t=i){return e?e.is_admin?!0:t.operatorWriteEnabled&&t.operatorIds.includes(e.id):!1}var m=["buttons_compact","buttons","icons"];function f(e){let[t,s]=c.default_warning_days,n=e?.general?.default_warning_days,r=e?.general?.row_action_style;return{features:{...o.features,...e?.features??{}},defaultWarningDays:typeof n=="number"&&Number.isFinite(n)&&n>=t&&n<=s?n:o.defaultWarningDays,rowActionStyle:m.includes(r??"")?r:o.rowActionStyle,access:{operatorWriteEnabled:e?.operator_write_enabled===!0,operatorIds:Array.isArray(e?.admin_panel_user_ids)?e.admin_panel_user_ids.filter(p=>typeof p=="string"):[]},budget:e?.budget??null,rowActionNoticePending:e?.general?.row_action_notice_pending===!0,refsInLists:e?.general?.ref_numbers_in_lists===!0,objectsTableColumns:Array.isArray(e?.objects_table_columns)?e.objects_table_columns:void 0,partsCostMode:l(e?.budget?.parts_cost_mode)}}var o={features:{adaptive:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1,places:!1},defaultWarningDays:7,rowActionStyle:"buttons_compact",access:i,budget:null,rowActionNoticePending:!1,refsInLists:!1,objectsTableColumns:void 0,partsCostMode:"purchase"};function u(){let e=window;return e.__msSettingsCache??={promise:null}}function S(e){let t=u();if(t.promise)return t.promise;let s=e.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(n=>f(n)).catch(()=>(t.promise===s&&(t.promise=null),o));return t.promise=s,s}function $(){u().promise=null}var _=class{constructor(){this._handle=null}schedule(t,s=4e3){this.clear(),this._handle=setTimeout(()=>{this._handle=null,t()},s)}clear(){this._handle!==null&&(clearTimeout(this._handle),this._handle=null)}get pending(){return this._handle!==null}};var{I:E}=a;var M=e=>e.strings===void 0;var g={},x=(e,t=g)=>e._$AH=t;var P={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},N=e=>(...t)=>({_$litDirective$:e,values:t}),d=class{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,s,n){this._$Ct=t,this._$AM=s,this._$Ci=n}_$AS(t,s){return this.update(t,s)}update(t,s){return this.render(...s)}};export{M as a,x as b,P as c,N as d,d as e,i as f,b as g,m as h,f as i,S as j,$ as k,_ as l};
/*! Bundled license information:
lit-html/directive-helpers.js:
(**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
lit-html/directive.js:
(**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*)
*/
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
async function i(o,n,e){let t=o.auth;if(t?.expired&&t.refreshAccessToken)try{await t.refreshAccessToken()}catch{}return fetch(n,{...e,headers:{...e.headers??{},Authorization:`Bearer ${t?.data?.access_token??""}`}})}var d=100*1024*1024,l=500*1024*1024;async function c(o,n,e,t="doc_too_large",s=[]){let r=await i(o,n,{method:"POST",body:e});if(r.status===413)throw new Error(t);if(!r.ok){let a=await r.json().catch(()=>null);throw typeof a?.code=="string"&&s.includes(a.code)?new Error(a.code):new Error("doc_upload_failed")}return await r.json()}async function p(o,n,e,t){if(e.size>d)throw new Error("doc_too_large");let s=new FormData;s.append("entry_id",n);for(let a of t)s.append("tags",a);s.append("file",e,e.name);let r=await c(o,"/api/maintenance_supporter/document/upload",s);if(!r.id)throw new Error("doc_upload_failed");return{id:r.id,deduped:!!r.deduped,duplicate_in_object:r.duplicate_in_object??null}}async function m(o,n,e){return(await p(o,n,e,["photo"])).id}async function _(o,n,e){await Promise.all(e.map(t=>o.connection.sendMessagePromise({type:"maintenance_supporter/documents/discard_upload",entry_id:n,doc_id:t}).catch(()=>{})))}export{l as a,c as b,p as c,m as d,_ as e};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{d as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-2URORCTE.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-XDU67K3J.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-VZUMUVFP.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-MMIPM7EE.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-GMGHSBQJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-R77C34J4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2BQZ33DM.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";export{a as MaintenanceCompleteDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{H as a,I as b,J as c,K as d,L as e,M as f,N as g,O as h,P as i,Q as j,R as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-L5NQ4343.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-WNGWMATB.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-KZ3OKTLK.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DR2AFVDA.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EOMYEBQ4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4DQPCVDN.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-UVFWQWIH.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-5TWB2KD3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2URORCTE.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-XDU67K3J.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-VZUMUVFP.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-MMIPM7EE.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-GMGHSBQJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-R77C34J4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-S4GGDFOL.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IRRUA3DS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DPJSGXJS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2BQZ33DM.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-QVOLUT24.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";export{b as __resetSettingsCacheForTests,a as getRowActionStyle,h as openCompleteDialog,c as openCreateObjectDialog,e as openCreateTaskDialog,d as openEditObjectDialog,f as openEditTaskDialog,g as openHistoryEditDialog,k as openObjectQuickActions,i as openQrDialog,j as openTaskQuickActions};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4DQPCVDN.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-5TWB2KD3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-R77C34J4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";export{a as MaintenanceObjectDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-S4GGDFOL.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IRRUA3DS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";export{a as MaintenanceQrDialog};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,163 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as $,b as z,c as j}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-QVOLUT24.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import{I as w,a as d,b,c as n,f as c,h as x,m as y,n as l,t as r,v as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";var m=u=>`${u.entry_id}/${u.task_id}`,_="__new__",o=class extends x{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._setups=[];this._selected=new Set;this._baselines=new Map;this._targets=new Map;this._tasks=new Map;this._objects=[];this._offers=[];this._offerTicks=new Set;this._wiring=!1;this._localeReady=!1;this._wire=async()=>{if(this._wiring)return;let e=this._offers.filter(s=>this._offerTicks.has(m(s))).map(s=>({entry_id:s.entry_id,task_id:s.task_id}));if(e.length===0)return;this._error="";let t=await g(this,{type:"maintenance_supporter/integration_setups/wire_resets",items:e},{busy:s=>{this._wiring=s},onError:s=>{this._error=s}});t!==void 0&&(this.dispatchEvent(new CustomEvent("reset-counters-wired",{bubbles:!0,composed:!0,detail:t})),await this._loadOffers())};this._toggle=e=>{let t=new Set(this._selected);t.has(e)?t.delete(e):t.add(e),this._selected=t};this._adopt=async()=>{if(this._adopting)return;this._error="";let e=[...this._selected].map(s=>{let a=[...this._tasks.get(s)??[]],p={device_id:s,task_names:a},i=this._targets.get(s);i&&i!==_&&(p.entry_id=i);for(let f of a){let h=this._baselines.get(`${s} ${f}`),v=h?parseFloat(h):NaN;!isNaN(v)&&v>=0&&((p.baselines??={})[f]=v)}return p}).filter(s=>s.task_names.length>0);if(e.length===0)return;let t=await g(this,{type:"maintenance_supporter/integration_setups/adopt",selections:e},{busy:s=>{this._adopting=s},onError:s=>{this._error=s}});t!==void 0&&(this.dispatchEvent(new CustomEvent("integration-setups-adopted",{bubbles:!0,composed:!0,detail:t})),this._open=!1)}}get _lang(){return w(this.hass)}updated(e){e.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,k(this._lang).then(()=>this.requestUpdate())),e.has("_open")&&this._open&&j(this.shadowRoot)}_defaultTicks(e){return new Set(e.tasks.filter(t=>!t.covered_by).map(t=>t.task_name))}async open(){this._open=!0,this._loading=!0,this._error="",this._setups=[],this._selected=new Set,this._offers=[],this._loadOffers();let e=await g(this,{type:"maintenance_supporter/integration_setups/discover"},{onError:t=>{this._error=t}});if(e!==void 0){this._setups=e?.setups||[],this._selected=new Set(this._setups.map(t=>t.device_id)),this._baselines=new Map,this._targets=new Map(this._setups.map(t=>[t.device_id,t.target_entry_id||_])),this._tasks=new Map(this._setups.map(t=>[t.device_id,this._defaultTicks(t)]));try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._objects=(t.objects||[]).filter(s=>!s.object?.archived_at).map(s=>({entry_id:s.entry_id,name:s.object?.name||s.entry_id})).sort((s,a)=>s.name.localeCompare(a.name))}catch{this._objects=[]}}this._loading=!1}_close(){this._open=!1}async _loadOffers(){try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/reset_offers"});this._offers=e.offers||[],this._offerTicks=new Set(this._offers.filter(t=>!t.renamed).map(m))}catch{this._offers=[]}}_toggleOffer(e){let t=new Set(this._offerTicks),s=m(e);t.has(s)?t.delete(s):t.add(s),this._offerTicks=t}_renderOffers(e){if(this._offers.length===0)return c;let t=this._offers.filter(s=>this._offerTicks.has(m(s))).length;return n`
<div class="offers">
<div class="offers-title">${r("reset_offers_title",e)}</div>
<div class="hint">${r("reset_offers_hint",e)}</div>
${this._offers.map(s=>n`
<label class="offer" data-task=${s.task_id}>
<input type="checkbox" class="offer-check" .checked=${this._offerTicks.has(m(s))} @change=${()=>this._toggleOffer(s)} />
<span class="task-text">
<span>${s.task_name} · ${s.object_name}</span>
${s.renamed?n`<span class="offer-renamed">${r("reset_offers_renamed",e)}</span>`:c}
<span class="offer-before">${r("reset_offers_before",e).replace("{integration}",s.integration_name)}</span>
<span class="offer-after">
${r("reset_offers_after",e).replace("{button}",s.button_name)}${s.button_disabled?` ${r("setups_reset_enable",e)}`:""}
</span>
</span>
</label>
`)}
<div class="offers-actions">
<ha-button @click=${this._wire} .disabled=${t===0||this._wiring}>${r("reset_offers_apply",e)}</ha-button>
</div>
</div>
`}_toggleTask(e,t){let s=new Set(this._tasks.get(e)??[]);s.has(t)?s.delete(t):s.add(t),this._tasks=new Map(this._tasks).set(e,s)}async _retarget(e,t){this._targets=new Map(this._targets).set(e.device_id,t);let s=await g(this,{type:"maintenance_supporter/integration_setups/preview",device_id:e.device_id,entry_id:t===_?null:t},{onError:a=>{this._error=a}});!s||!Array.isArray(s.tasks)||(this._setups=this._setups.map(a=>a.device_id===e.device_id?{...a,...s}:a),this._tasks=new Map(this._tasks).set(e.device_id,this._defaultTicks(s)))}get _anythingTicked(){return[...this._selected].some(e=>(this._tasks.get(e)?.size??0)>0)}_targetOptions(e,t){let s=this._targets.get(e.device_id)??_,a=[];if(e.suggested_entry_id)a.push({value:e.suggested_entry_id,label:e.suggested_object_name});else{if(e.candidate){let i=e.candidate.reasons.map(f=>r(`setups_reason_${f}`,t)).join(", ");a.push({value:e.candidate.entry_id,label:r("setups_target_match",t).replace("{name}",e.candidate.name).replace("{reasons}",i)})}a.push({value:_,label:r("setups_target_new",t).replace("{name}",e.device_name)})}let p=new Set(a.map(i=>i.value));for(let i of this._objects)p.has(i.entry_id)||a.push({value:i.entry_id,label:i.name});return n`
<select
class="target-select"
@click=${i=>i.stopPropagation()}
@change=${i=>{this._retarget(e,i.target.value)}}
>
${a.map(i=>n`<option value=${i.value} ?selected=${i.value===s}>${i.label}</option>`)}
</select>
`}_beforeAfter(e,t){let s=this._tasks.get(e.device_id)?.size??0;if((this._targets.get(e.device_id)??_)===_)return r("setups_new_object_count",t).replace("{name}",e.device_name).replace("{count}",String(s));let p=e.target_task_count??0;return r("setups_before_after",t).replace("{before}",String(p)).replace("{after}",String(p+s))}render(){if(!this._open)return n``;let e=this._lang;return z(()=>this._close(),n`
<div class="title">${r("setups_title",e)}</div>
${this._error?n`<div class="error">${this._error}</div>`:c}
<div class="dialog-scroll">
<div class="hint">${r("setups_hint",e)}</div>
${this._renderOffers(e)}
${this._loading?n`<div class="loading">…</div>`:this._setups.length===0?this._offers.length===0?n`<div class="empty">${r("setups_none",e)}</div>`:c:n`
<div class="list">
${this._setups.map(t=>{let s=this._selected.has(t.device_id),a=[t.integration_name,t.area_name].filter(Boolean).join(" \xB7 "),p=this._tasks.get(t.device_id)??new Set;return n`
<div class="row ${s?"":"off"}" data-device=${t.device_id}>
<input
type="checkbox"
class="device-check"
.checked=${s}
@change=${()=>this._toggle(t.device_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${t.device_name}</span>
</div>
<div class="row-sub">${a}</div>
<div class="row-target">→ ${this._targetOptions(t,e)}</div>
${s?n`
<div class="task-list">
${t.tasks.map(i=>n`
<label class="task" title=${i.entity_ids.join(", ")}>
<input
type="checkbox"
class="task-check"
.checked=${p.has(i.task_name)}
@change=${()=>this._toggleTask(t.device_id,i.task_name)}
/>
<span class="task-text">
<span>${i.task_name_localized||i.task_name}</span>
${i.covered_by?n`<span class="maybe">${r("setups_maybe_covered",e).replace("{name}",i.covered_by.name)}</span>`:c}
${i.reset?n`<span class="reset">↻ ${r("setups_reset_line",e).replace("{button}",i.reset.name)}${i.reset.disabled?` ${r("setups_reset_enable",e)}`:""}</span>`:c}
</span>
</label>
${i.direction==="usage_delta"&&p.has(i.task_name)?n`
<div class="baseline-field">
<span class="baseline-label">${r("setups_baseline_hint",e)}</span>
<input
type="number"
step="any"
min="0"
.value=${this._baselines.get(`${t.device_id} ${i.task_name}`)??""}
@input=${f=>{let h=new Map(this._baselines);h.set(`${t.device_id} ${i.task_name}`,f.target.value),this._baselines=h}}
/>
</div>
`:c}
`)}
${t.already?.length?n`<div class="already">
${r("setups_already",e).replace("{names}",t.already.map(i=>i.existing_name).join(", "))}
</div>`:c}
</div>
<div class="before-after">${this._beforeAfter(t,e)}</div>
`:c}
</div>
</div>
`})}
</div>
`}
</div>
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${r("cancel",e)}
</ha-button>
<ha-button @click=${this._adopt} .disabled=${!this._anythingTicked||this._adopting}>
${r("setups_adopt",e)}
</ha-button>
</div>
`)}};o.styles=[$,b`
: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; }
.row {
display: flex; align-items: flex-start; gap: 10px; padding: 8px;
border: 1px solid var(--divider-color); border-radius: 6px;
}
.row.off .row-main { opacity: 0.6; }
.row input { margin-top: 2px; cursor: pointer; }
.row-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
.row-name { font-weight: 500; font-size: 13px; }
.row-sub, .row-target { color: var(--secondary-text-color); font-size: 12px; }
.row-target { display: flex; align-items: center; gap: 6px; min-width: 0; }
.target-select {
font-size: 12px; padding: 2px 4px; min-width: 0; max-width: 100%; flex: 1 1 auto;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.task-list { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.task { display: flex; align-items: flex-start; gap: 6px; font-size: 13px; cursor: pointer; }
.task-text { display: flex; flex-direction: column; min-width: 0; }
.maybe { font-size: 11px; color: var(--warning-color, #ff9800); }
.already { font-size: 11px; color: var(--secondary-text-color); margin-top: 2px; }
.reset { font-size: 11px; color: var(--secondary-text-color); }
.offers {
display: flex; flex-direction: column; gap: 6px; padding: 8px;
border: 1px solid var(--primary-color); border-radius: 6px;
}
.offers-title { font-weight: 500; font-size: 14px; }
.offer { display: flex; align-items: flex-start; gap: 6px; font-size: 13px; cursor: pointer; }
.offer-before { font-size: 11px; color: var(--secondary-text-color); }
.offer-after { font-size: 11px; color: var(--primary-color); }
.offer-renamed { font-size: 11px; color: var(--warning-color, #ff9800); }
.offers-actions { display: flex; justify-content: flex-end; }
.before-after {
font-size: 12px; font-weight: 500; color: var(--primary-color);
border-top: 1px dashed var(--divider-color); padding-top: 4px; margin-top: 2px;
}
.baseline-field {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
margin: 0 0 2px 24px; font-size: 12px; color: var(--secondary-text-color);
}
.baseline-field input {
width: 110px; padding: 3px 6px; font-size: 12px;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
`],d([y({attribute:!1})],o.prototype,"hass",2),d([l()],o.prototype,"_open",2),d([l()],o.prototype,"_loading",2),d([l()],o.prototype,"_adopting",2),d([l()],o.prototype,"_error",2),d([l()],o.prototype,"_setups",2),d([l()],o.prototype,"_selected",2),d([l()],o.prototype,"_baselines",2),d([l()],o.prototype,"_targets",2),d([l()],o.prototype,"_tasks",2),d([l()],o.prototype,"_objects",2),d([l()],o.prototype,"_offers",2),d([l()],o.prototype,"_offerTicks",2),d([l()],o.prototype,"_wiring",2);customElements.get("maintenance-suggested-setups-dialog")||customElements.define("maintenance-suggested-setups-dialog",o);export{o as MaintenanceSuggestedSetupsDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{c as a,d as b,e as c}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-UVFWQWIH.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-5TWB2KD3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-GMGHSBQJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-R77C34J4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DPJSGXJS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2BQZ33DM.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3LDXEWBF.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OWNF6K2Z.js";export{c as MaintenanceTaskDialog,b as applyTypeFields,a as typeFieldsFromConfig};
@@ -0,0 +1,127 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as w,b as $}from"./chunk-3P2KHV56.js";import{C as u,a as h,b as o,d as m,f as y,g as v,h as l,i as g,j as _,k as f,l as x,o as p}from"./chunk-ZALBVT7T.js";import{a as n,h as i}from"./chunk-ORLIHI47.js";var E=80,s=class extends y{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return f(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),_(this,t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load())}async _load(){let t=await u(this,{type:"maintenance_supporter/budget_status"},{onError:e=>{this._error=e}});t&&(this._status=t,x(t),this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1)}async _save(){if(!this._isAdmin)return;this._error="";let t=this._localMonthly.trim()===""?0:parseFloat(this._localMonthly),e=this._localYearly.trim()===""?0:parseFloat(this._localYearly),a={};!isNaN(t)&&t>=0&&(a.budget_monthly=t),!isNaN(e)&&e>=0&&(a.budget_yearly=e),await u(this,{type:"maintenance_supporter/global/update",settings:a},{busy:d=>{this._busy=d},reload:()=>this._load(),onError:d=>{this._error=d}})}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,e=this._status;if(!e)return o`<ha-card><div class="loading">${i("loading",t)}</div></ha-card>`;let a=g(e),d=e.alert_threshold_pct??E,k=[{label:i("budget_monthly",t),spent:e.monthly_spent||0,budget:e.monthly_budget||0},{label:i("budget_yearly",t),spent:e.yearly_spent||0,budget:e.yearly_budget||0}];return o`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">💰</span>
<span>${this._config.title||i("settings_budget",t)}</span>
</div>
<span class="currency">${a}</span>
</div>
${this._error?o`<div class="error">${this._error}</div>`:m}
${k.map(r=>{if(!(r.budget>0))return o`
<div class="track spent-only">
<div class="track-label-row">
<label>${r.label}</label>
<span class="track-numbers ok">${p(r.spent,a,t)}</span>
</div>
</div>
`;let c=Math.min(100,Math.max(0,r.spent/r.budget*100)),b=c>=100?"danger":c>=d?"warning":"ok";return o`
<div class="track">
<div class="track-label-row">
<label>${r.label}</label>
<span class="track-numbers ${b}">
${p(r.spent,void 0,t)} / ${p(r.budget,a,t)}
</span>
</div>
<div class="bar"><div class="bar-fill ${b}" style="width:${c}%"></div></div>
</div>
`})}
${this._isAdmin?o`
<div class="inputs-row">
<div class="input-field">
<label>${i("budget_monthly_set",t)}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localMonthly}
?disabled=${this._busy}
@input=${r=>{this._localMonthly=r.target.value,this._dirty=!0}} />
<span class="input-suffix">${a}</span>
</div>
</div>
<div class="input-field">
<label>${i("budget_yearly_set",t)}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localYearly}
?disabled=${this._busy}
@input=${r=>{this._localYearly=r.target.value,this._dirty=!0}} />
<span class="input-suffix">${a}</span>
</div>
</div>
</div>
<div class="actions">
<button class="btn ${this._dirty?"primary":"muted"}"
@click=${this._save}
?disabled=${this._busy||!this._dirty}>
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
${this._dirty?i("save",t):i("saved",t)}
</button>
<button class="btn link" @click=${this._onDeepLink}>
${i("budget_advanced",t)}
</button>
</div>
`:o`
<button class="btn link" @click=${this._onDeepLink}>
${i("budget_open_panel",t)}
</button>
`}
</div>
</ha-card>
`}};s.styles=[$,h`
.currency {
font-size: 14px; font-weight: 600;
color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 10px; border-radius: 999px;
}
.track { display: flex; flex-direction: column; gap: 4px; }
.track-label-row {
display: flex; align-items: center; justify-content: space-between;
}
.track-label-row label {
font-size: 12px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.5px;
}
.track-numbers { font-size: 13px; font-weight: 600; }
.track-numbers.ok { color: var(--primary-text-color); }
.track-numbers.warning { color: #ff9800; }
.track-numbers.danger { color: var(--error-color, #f44336); }
.bar {
height: 6px; background: var(--secondary-background-color);
border-radius: 3px; overflow: hidden;
}
.bar-fill { height: 100%; transition: width 0.3s; border-radius: 3px; }
.bar-fill.ok { background: var(--primary-color); }
.bar-fill.warning { background: #ff9800; }
.bar-fill.danger { background: var(--error-color, #f44336); }
/* minmax(0, …): a bare 1fr never shrinks below the number inputs'
natural width — the row was 420 px wide on a phone (2026-09-27). */
.inputs-row {
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px;
padding-top: 4px; border-top: 1px solid var(--divider-color);
}
.input-field { display: flex; flex-direction: column; gap: 4px; }
.input-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.input-wrap { position: relative; display: flex; align-items: center; }
.input-wrap input {
flex: 1; min-width: 0; width: 100%; box-sizing: border-box; padding: 6px 32px 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
.input-suffix {
position: absolute; right: 8px;
color: var(--secondary-text-color); font-size: 13px;
pointer-events: none;
}
.actions { display: flex; gap: 8px; align-items: center; }
`],n([v({attribute:!1})],s.prototype,"hass",2),n([l()],s.prototype,"_config",2),n([l()],s.prototype,"_status",2),n([l()],s.prototype,"_busy",2),n([l()],s.prototype,"_error",2),n([l()],s.prototype,"_localMonthly",2),n([l()],s.prototype,"_localYearly",2),n([l()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);w({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Edit the monthly and yearly budget right on the dashboard.",preview:!1});export{s as MaintenanceBudgetSectionCard};
@@ -0,0 +1,88 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as y,b as s,d,f as g,g as o,h as f,r as m}from"./chunk-ZALBVT7T.js";import{a,h as l}from"./chunk-ORLIHI47.js";function $(n){let t=m("2001-02-03",n).match(/\d+/g)||[],e=v=>t.findIndex(_=>Number(_)===v),h=e(2001),c=e(2),u=e(3);return h===0?"YMD":c>=0&&u>=0&&c<u?"MDY":"DMY"}function p(n,i,t){if(n<1e3||n>9999||i<1||i>12||t<1||t>31)return null;let e=new Date(n,i-1,t);return e.getFullYear()!==n||e.getMonth()!==i-1||e.getDate()!==t?null:`${n}-${String(i).padStart(2,"0")}-${String(t).padStart(2,"0")}`}function b(n,i){let t=n.trim(),e;if(e=t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return p(+e[1],+e[2],+e[3]);if(e=t.match(/^(\d{4})$/))return p(+e[1],1,1);if(e=t.match(/^(\d{1,2})[./](\d{4})$/))return p(+e[2],+e[1],1);if(e=t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return p(+e[3],+e[2],+e[1]);if(e=t.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let h=+e[1],c=+e[2],u=+e[3];return $(i)==="MDY"?p(u,h,c):p(u,c,h)}return null}function x(n,i){if(i)switch(n){case"date":return i.split("T")[0];case"time":return i.length===5?`${i}:00`:i;case"datetime":{let[t,e="00:00:00"]=i.split("T");return`${t} ${e.length===5?`${e}:00`:e}`}}}function k(n,i){if(typeof i!="string"||!i)return"";switch(n){case"date":return i.slice(0,10);case"time":return i.slice(0,5);case"datetime":{let[t,e="00:00:00"]=i.split(" ");return`${t}T${e.length===5?`${e}:00`:e}`}}}var r=class extends g{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let t=b(this._typed,this.lang);if(!t){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(t)}_onTypedKey(t){t.key==="Enter"?(t.preventDefault(),this._commitTyped()):t.key==="Escape"&&(t.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(t){t.stopPropagation(),this._emit(k(this.kind,t.detail?.value))}_clear(){this._emit("")}_emit(t){t!==this.value&&(this.value=t,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:t}})))}render(){return s`
<div class="field">
${this.label?s`<span class="label">${this.label}${this.required?s`<span class="req">*</span>`:d}</span>`:d}
<div class="row">
${this._typing?s`<input class="typed" type="text" inputmode="numeric" autocomplete="off"
.value=${this._typed}
placeholder=${m("1978-03-15",this.lang)}
aria-invalid=${this._typedInvalid?"true":"false"}
@input=${t=>{this._typed=t.target.value,this._typedInvalid=!1}}
@keydown=${this._onTypedKey}
@blur=${this._commitTyped} />`:s`<ha-selector
.hass=${this.hass}
.selector=${this._selector()}
.value=${x(this.kind,this.value)}
.required=${this.required}
.disabled=${this.disabled}
@value-changed=${this._onSelectorChange}
></ha-selector>`}
${this.kind==="date"&&!this.disabled&&!this._typing?s`<button type="button" class="type-toggle" title=${l("date_type_toggle",this.lang)} aria-label=${l("date_type_toggle",this.lang)} @click=${this._startTyping}>
<ha-icon icon="mdi:keyboard-outline"></ha-icon>
</button>`:d}
${this.clearable&&this.value&&!this.disabled&&!this._typing?s`<button type="button" class="clear" title=${l("clear",this.lang)} aria-label=${l("clear",this.lang)} @click=${this._clear}>
<ha-icon icon="mdi:close"></ha-icon>
</button>`:d}
</div>
${this._typedInvalid?s`<span class="helper invalid">${l("date_type_invalid",this.lang)}</span>`:this.helper?s`<span class="helper">${this.helper}</span>`:d}
</div>
`}};r.styles=y`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
.row {
display: flex;
align-items: center;
gap: 4px;
}
ha-selector {
flex: 1;
min-width: 0;
}
.clear, .type-toggle {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 20px;
}
.clear:hover, .type-toggle:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); }
.clear:focus-visible, .type-toggle:focus-visible { outline: 2px solid var(--primary-color); }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
.helper.invalid { color: var(--error-color, #f44336); font-style: normal; }
input.typed {
flex: 1;
min-width: 0;
height: 40px;
box-sizing: border-box;
padding: 0 12px;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font: inherit;
}
input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); }
input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; }
`,a([o({attribute:!1})],r.prototype,"hass",2),a([o()],r.prototype,"kind",2),a([o()],r.prototype,"label",2),a([o()],r.prototype,"value",2),a([o()],r.prototype,"helper",2),a([o({type:Boolean})],r.prototype,"clearable",2),a([o({type:Boolean})],r.prototype,"disabled",2),a([o({type:Boolean})],r.prototype,"required",2),a([o()],r.prototype,"lang",2),a([f()],r.prototype,"_typing",2),a([f()],r.prototype,"_typed",2),a([f()],r.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",r);
@@ -0,0 +1,60 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as t}from"./chunk-ZALBVT7T.js";function e(o){let r=window;r.customCards=r.customCards||[],r.customCards.some(a=>a.type===o.type)||r.customCards.push(o)}var d=t`
ha-card { overflow: hidden; }
.card-content {
padding: 16px;
display: flex; flex-direction: column;
gap: 12px;
}
.header {
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
}
.title {
display: flex; align-items: center; gap: 8px;
font-size: 16px; font-weight: 500;
}
.emoji { font-size: 20px; }
/* Button family — primary action / muted-saved-state / link / icon-with-text */
.btn {
padding: 6px 12px; font-size: 13px;
border-radius: 6px; cursor: pointer;
border: 1px solid var(--divider-color);
background: var(--secondary-background-color, transparent);
color: var(--primary-text-color);
font-weight: 500;
display: inline-flex; align-items: center; gap: 4px;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn.primary {
background: var(--primary-color);
color: var(--text-primary-color, white);
border-color: var(--primary-color);
}
.btn.primary[disabled] { opacity: 0.6; }
.btn.muted {
background: transparent;
color: var(--secondary-text-color);
border-style: dashed;
}
.btn.muted[disabled] { opacity: 1; cursor: default; }
.btn.muted ha-icon, .btn.primary ha-icon { --mdc-icon-size: 14px; }
.btn.link {
background: transparent; border: none; padding: 6px 4px;
color: var(--primary-color); margin-left: auto;
}
.btn.link:hover { background: transparent; text-decoration: underline; }
/* Error + loading states */
.error {
padding: 8px; border-radius: 6px;
background: rgba(211, 47, 47, 0.1);
color: var(--error-color, #d32f2f); font-size: 13px;
}
.loading {
padding: 24px; text-align: center;
color: var(--secondary-text-color);
}
`;export{e as a,d as b};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{q as l}from"./chunk-ORLIHI47.js";function c(r){if(!r)return[];let n=[],i=r.photo_doc_id;typeof i=="string"&&n.push(i);let o=r.photo_doc_ids;Array.isArray(o)&&n.push(...o);let t=[];for(let e of n){if(typeof e!="string")continue;let s=e.trim();if(!(!s||t.includes(s))&&(t.push(s),t.length>=10))break}return t}var f=20;function _(){let r=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:n=>n.map(()=>Math.floor(Math.random()*256))}).getRandomValues(r),Array.from(r,n=>n.toString(16).padStart(2,"0")).join("")}function d(r){let n=r?.reading_values;if(!Array.isArray(n))return[];let i=[],o=new Set;for(let t of n){if(!t||typeof t!="object")continue;let e=t;typeof e.id!="string"||!e.id||o.has(e.id)||typeof e.value!="number"||!Number.isFinite(e.value)||(o.add(e.id),i.push({id:e.id,name:typeof e.name=="string"?e.name:e.id,unit:typeof e.unit=="string"?e.unit:null,value:e.value}))}return i}function y(r){return r.filter(n=>n.type==="completed"&&(n.reading_value!=null||d(n).length>0)).sort((n,i)=>n.timestamp.localeCompare(i.timestamp))}function R(r){return y(r??[]).map(n=>({timestamp:n.timestamp,values:d(n)})).filter(n=>n.values.length>0)}function h(r,n,i){let o;for(let t of r){if(i!==void 0){let s=l(t.timestamp);if(!isNaN(s)&&s>=i)break}let e=t.values.find(s=>s.id===n);e&&(o=e)}return o}function k(r,n,i){let o=d(n).find(e=>e.id===i);if(!o)return null;let t=null;for(let e of y(r)){if(e.timestamp>=n.timestamp)break;let s=d(e).find(a=>a.id===i);s&&(t=s.value)}return t==null?null:o.value-t}function w(r){let n=new Set,i=new Set;for(let o of r){let t=(o.name||"").trim().toLowerCase();t&&(n.has(t)?i.add(o.id):n.add(t))}return i}function S(r){let n=[],i=new Set,o=new Set;for(let t of r){let e=(t.name||"").trim();if(!(!e||i.has(t.id)||o.has(e.toLowerCase()))&&(i.add(t.id),o.add(e.toLowerCase()),n.push({id:t.id,name:e,unit:(t.unit||"").trim()||null}),n.length>=f))break}return n}function g(r,n,i,o){let t=i,e=u=>typeof u=="string"?u:null,s=u=>typeof u=="number"?u:null,a=e(t.timestamp)??"";return{entry_id:r,task_id:n,original_timestamp:a,type:e(t.type)||"completed",timestamp:a,notes:e(t.notes),cost:s(t.cost),duration:s(t.duration),completed_by:e(t.completed_by),used_parts:Array.isArray(t.used_parts)?t.used_parts:null,photo_doc_ids:c(t),reading_value:s(t.reading_value),reading_values:d(t),readings:o?.readings??[],task_type:o?.type??null,reading_unit:o?.reading_unit??null}}async function v(r,n,i,o){let e=(await r.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:n})).tasks?.find(a=>a.id===i);if(!e)return null;let s=e.history?.find(a=>a.timestamp===o);return s||(s=(await r.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:n,task_id:i})).history?.find(u=>u.timestamp===o)),s?g(n,i,s,e):null}export{c as a,f as b,_ as c,d,R as e,h as f,k as g,w as h,S as i,g as j,v as k};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
var w=Object.defineProperty;var y=Object.getOwnPropertyDescriptor;var B=(n,e,t,r)=>{for(var o=r>1?void 0:r?y(e,t):e,s=n.length-1,i;s>=0;s--)(i=n[s])&&(o=(r?i(e,t,o):i(o))||o);return r&&o&&w(e,t,o),o};var W={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"},C=["overdue","triggered","due_soon","ok"],j=["overdue","triggered","due_soon"],V=Object.fromEntries(C.map((n,e)=>[n,e]));var Y={ok:"mdi:check-circle",due_soon:"mdi:alert-circle",overdue:"mdi:alert-octagon",triggered:"mdi:bell-alert",archived:"mdi:archive-outline",paused:"mdi:pause-circle-outline",completed:"mdi:check-circle",skipped:"mdi:skip-next",missed:"mdi:calendar-remove",reset:"mdi:refresh"};var D="2.103.0";function H(n){return n?.restart_pending&&n.installed_version?n.installed_version:null}var _="en",x=(()=>{let n=window;return n.__msLocales||(n.__msLocales={store:{},inflight:{}}),n.__msLocales})(),l=x.store,d=x.inflight;function J(n){l.en=Object.assign({},n,l.en??{})}var O=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),R="/maintenance_supporter_locales";function b(n){let e=(n||_).toLowerCase();return e.startsWith("pt")&&e.endsWith("br")?"pt-br":e.substring(0,2)}function Q(n,e,t){let r=b(e);return l[r]?.[n]??l.en?.[n]??t??n}function X(n){let e=b(n);return e===_||e in l}function nn(n){let e=b(n);return e===_||e in l||!O.has(e)?Promise.resolve():(e in d||(d[e]=fetch(`${R}/${e}.json?v=${D}`).then(t=>t.ok?t.json():null).then(t=>{t?l[e]=t:delete d[e]}).catch(()=>{delete d[e]})),d[e])}var A=window.__msDateTimePrefs??={},v=864e5,a=(n,e=2)=>String(n).padStart(e,"0"),$=new Map;function m(n){let e=$.get(n);if(e===void 0){try{e=new Intl.DateTimeFormat("en-US",{timeZone:n,hourCycle:"h23",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})}catch{e=null}$.set(n,e)}return e}var S;function L(){if(S===void 0){let n;try{n=new Intl.DateTimeFormat().resolvedOptions().timeZone}catch{}S=n&&m(n)?n:null}return S}function E(){return L()??"UTC"}function f(){let n=A.serverTimeZone;return n&&m(n)?n:E()}function tn(){let n=L();return A.timeZone!=="server"&&n?n:f()}function p(n,e){let t=m(e)??m(E())??m("UTC"),r={};for(let o of t.formatToParts(n))o.type!=="literal"&&(r[o.type]=Number(o.value));return{y:r.year,mo:r.month,d:r.day,h:r.hour%24,mi:r.minute,s:r.second}}var N=n=>`${a(n.y,4)}-${a(n.mo)}-${a(n.d)}`;function M(n,e){return N(p(+n,e))}function I(n,e){let t=p(+n,e);return`${N(t)}T${a(t.h)}:${a(t.mi)}`}function rn(){return M(Date.now(),f())}function on(){return I(Date.now(),f())}function h(n,e){let t=p(n,e);return Date.UTC(t.y,t.mo-1,t.d,t.h,t.mi,t.s)-Math.floor(n/1e3)*1e3}function P(n,e,t,r,o,s,i){let u=Date.UTC(n,e-1,t,r,o,s),c=h(u-v,i),g=h(u+v,i);for(let T of c===g?[c]:[c,g])if(h(u-T,i)===T)return u-T;return u-c}var U=/^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?)?$/;var Z=new Map,F=2e4;function k(n,e,t){if(n.has(e))return n.get(e);n.size>=F&&n.clear();let r=t();return n.set(e,r),r}function sn(n){if(typeof n!="string"||!n)return NaN;let e=U.exec(n);if(!e)return Date.parse(n);let t=f();return k(Z,`${t}|${n}`,()=>{let[r,o,s,i,u,c]=[e[1],e[2],e[3],e[4]??"0",e[5]??"0",e[6]??"0"].map(Number);if(o<1||o>12||new Date(Date.UTC(r,o-1,s)).getUTCDate()!==s||i>23||u>59||c>59)return NaN;let g=e[7]?Math.floor(+`0.${e[7]}`*1e3):0;return P(r,o,s,i,u,c,t)+g})}function un(n){if(typeof n!="string"||!n)return"";let e=U.exec(n);if(e)return`${e[1]}-${e[2]}-${e[3]}T${e[4]??"00"}:${e[5]??"00"}:${e[6]??"00"}`;let t=Date.parse(n);if(!Number.isFinite(t))return"";let r=p(t,f());return`${N(r)}T${a(r.h)}:${a(r.mi)}:${a(r.s)}`}export{B as a,W as b,j as c,Y as d,H as e,J as f,b as g,Q as h,X as i,nn as j,f as k,tn as l,M as m,I as n,rn as o,on as p,sn as q,un as r};
@@ -0,0 +1,66 @@
/*! maintenance_supporter frontend 2.103.0 */
import{A as c,a as u,b as s,d as r,f as _,g as m,h as o,k as d}from"./chunk-ZALBVT7T.js";import{a as i,h as p}from"./chunk-ORLIHI47.js";var g={default_warning_days:[0,365],default_consumable_threshold:[1,90],battery_low_percent:[1,90],battery_recovered_percent:[20,100],archive_oneoff_days:[0,3650],delete_archived_oneoff_days:[0,3650],notify_due_soon_interval_hours:[0,720],notify_overdue_interval_hours:[0,720],notify_triggered_interval_hours:[0,720],max_notifications_per_day:[0,1e3],notification_bundle_threshold:[2,20],snooze_duration_hours:[1,168],warranty_reminder_days:[1,365],budget_alert_threshold:[10,100],currency_decimals:[0,3]};var b=[0,14],x=[0,1440],y=[1,1e4],R=[0,365],A=15,E=[.1,.9],T=[0,365],$=[.01,1e4],G=[.01,1e5];var n=class extends _{constructor(){super(...arguments);this._open=!1;this._title="";this._message="";this._confirmText="";this._danger=!1;this._inputLabel="";this._inputType="";this._options=null;this._inputValue="";this._resolve=null;this._promptResolve=null}confirm(e){return this._title=e.title,this._message=e.message,this._confirmText=e.confirmText||"OK",this._danger=e.danger||!1,this._inputLabel="",this._inputType="",this._options=null,this._inputValue="",this._open=!0,new Promise(t=>{this._resolve=t,this._promptResolve=null})}prompt(e){return this._title=e.title,this._message=e.message,this._confirmText=e.confirmText||"OK",this._danger=e.danger||!1,this._inputLabel=e.inputLabel||"",this._inputType=e.inputType||"text",this._options=e.options&&e.options.length?e.options:null,this._inputValue=e.inputValue||"",this._open=!0,new Promise(t=>{this._promptResolve=t,this._resolve=null})}_cancel(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!1,value:""}),this._promptResolve=null),this._resolve?.(!1),this._resolve=null}_confirmAction(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!0,value:this._inputValue}),this._promptResolve=null),this._resolve?.(!0),this._resolve=null}render(){if(!this._open)return r;let e=d(this.hass);return s`
<ha-dialog open @closed=${this._cancel}>
<div class="dialog-title">${this._title}</div>
<div class="content">
${this._message}
${this._inputLabel?s`
<!-- Native <input> rather than <ha-textfield>: HA loads
ha-textfield lazily for its own panels, so inside this custom
panel it can be unregistered and render with zero height —
the prompt then shows no field at all (caught live testing
the pause/replace prompts; same fix as complete-dialog). -->
<label class="field">
<span class="field-label">${this._inputLabel}</span>
${this._options?s`<select class="field-input field-select"
@change=${t=>this._inputValue=t.target.value}>
${this._options.map(t=>s`<option value=${t.value} ?selected=${t.value===this._inputValue}>${t.label}</option>`)}
</select>`:s`<input class="field-input"
type="${this._inputType||"text"}"
.value=${this._inputValue}
@input=${t=>this._inputValue=t.target.value} />`}
</label>
`:r}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._cancel}>
${p("cancel",e)}
</ha-button>
<ha-button
class="${this._danger?"danger":""}"
@click=${this._confirmAction}
>
${this._confirmText}
</ha-button>
</div>
</ha-dialog>
`}};n.styles=[c,u`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
/* shared native-field scaffold from nativeFieldStyles; the prompt input
follows the message text, hence the extra top margin here */
.field { margin-top: 12px; }
.content {
padding: 8px 0;
min-width: 280px;
line-height: 1.5;
display: flex;
flex-direction: column;
gap: 12px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
ha-textfield {
display: block;
}
ha-button.danger {
--mdc-theme-primary: var(--error-color, #f44336);
}
`],i([m({attribute:!1})],n.prototype,"hass",2),i([o()],n.prototype,"_open",2),i([o()],n.prototype,"_title",2),i([o()],n.prototype,"_message",2),i([o()],n.prototype,"_confirmText",2),i([o()],n.prototype,"_danger",2),i([o()],n.prototype,"_inputLabel",2),i([o()],n.prototype,"_inputType",2),i([o()],n.prototype,"_options",2),i([o()],n.prototype,"_inputValue",2);customElements.get("maintenance-confirm-dialog")||customElements.define("maintenance-confirm-dialog",n);var h="maintenance-confirm-dialog",f="data-ms-lovelace-confirm";function v(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function V(l,a){let e=v(),t=e.querySelector(`${h}[${f}]`);return t||(t=document.createElement(h),t.setAttribute(f,""),e.appendChild(t)),t.hass=l,t.confirm(a)}export{g as a,b,x as c,y as d,R as e,A as f,E as g,T as h,$ as i,G as j,V as k};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.103.0 */
import{a as i}from"./chunk-SYZTQIIC.js";var r={operatorWriteEnabled:!1,operatorIds:[]};function f(e,t=r){return e?e.is_admin?!0:t.operatorWriteEnabled&&t.operatorIds.includes(e.id):!1}function l(e){return e==="use"?"use":"purchase"}var c=e=>typeof e=="number"&&Number.isFinite(e)?e:null;function b(e){let t=c(e?.cost);if(t===null)return null;let n=c(e?.package_size);return n&&n>0?t/n:t}var d=["buttons_compact","buttons","icons"];function _(e){let[t,n]=i.default_warning_days,s=e?.general?.default_warning_days,a=e?.general?.row_action_style;return{features:{...o.features,...e?.features??{}},defaultWarningDays:typeof s=="number"&&Number.isFinite(s)&&s>=t&&s<=n?s:o.defaultWarningDays,rowActionStyle:d.includes(a??"")?a:o.rowActionStyle,access:{operatorWriteEnabled:e?.operator_write_enabled===!0,operatorIds:Array.isArray(e?.admin_panel_user_ids)?e.admin_panel_user_ids.filter(p=>typeof p=="string"):[]},budget:e?.budget??null,rowActionNoticePending:e?.general?.row_action_notice_pending===!0,refsInLists:e?.general?.ref_numbers_in_lists===!0,objectsTableColumns:Array.isArray(e?.objects_table_columns)?e.objects_table_columns:void 0,partsCostMode:l(e?.budget?.parts_cost_mode)}}var o={features:{adaptive:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1,places:!1},defaultWarningDays:7,rowActionStyle:"buttons_compact",access:r,budget:null,rowActionNoticePending:!1,refsInLists:!1,objectsTableColumns:void 0,partsCostMode:"purchase"};function u(){let e=window;return e.__msSettingsCache??={promise:null}}function w(e){let t=u();if(t.promise)return t.promise;let n=e.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(s=>_(s)).catch(()=>(t.promise===n&&(t.promise=null),o));return t.promise=n,n}function h(){u().promise=null}export{r as a,f as b,b as c,w as d,h as e};
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More