147 files

This commit is contained in:
Home Assistant Version Control committed 2026-08-27 20:54:53 +00:00
1 parent 52cbcb4187
commit 69f4dc3f17
149 files changed
+13415 -1156

No files matched your search

+1 -1
View File
@@ -1 +1 @@
{"pid": 71, "version": 1, "ha_version": "2026.8.3", "start_ts": 1787776418.1303492}
{"pid": 72, "version": 1, "ha_version": "2026.8.3", "start_ts": 1787864016.8899596}
+25 -25
View File
@@ -81,117 +81,117 @@
},
{
"id": "88b90f2b1d6b45f0b44bee786ee1a525",
"url": "/taskmate/taskmate-attr-resolver.js?v=5.3.0",
"url": "/taskmate/taskmate-attr-resolver.js?v=5.4.1",
"type": "module"
},
{
"id": "0796c92931dd458e81dc9c2677248274",
"url": "/taskmate/taskmate-localize.js?v=5.3.0",
"url": "/taskmate/taskmate-localize.js?v=5.4.1",
"type": "module"
},
{
"id": "d2e281920ac7409583eafff1f1db69bd",
"url": "/taskmate/taskmate-design.js?v=5.3.0",
"url": "/taskmate/taskmate-design.js?v=5.4.1",
"type": "module"
},
{
"id": "9eae9632f0734480be5edd784b030673",
"url": "/taskmate/taskmate-badges-card.js?v=5.3.0",
"url": "/taskmate/taskmate-badges-card.js?v=5.4.1",
"type": "module"
},
{
"id": "5df49cd8a4eb420497b5df22cb31362c",
"url": "/taskmate/taskmate-child-card.js?v=5.3.0",
"url": "/taskmate/taskmate-child-card.js?v=5.4.1",
"type": "module"
},
{
"id": "b44c87d0d74f468cb96c5eabc340f9ee",
"url": "/taskmate/taskmate-rewards-card.js?v=5.3.0",
"url": "/taskmate/taskmate-rewards-card.js?v=5.4.1",
"type": "module"
},
{
"id": "787a96f9c30548708165a48457c99feb",
"url": "/taskmate/taskmate-approvals-card.js?v=5.3.0",
"url": "/taskmate/taskmate-approvals-card.js?v=5.4.1",
"type": "module"
},
{
"id": "84031597de424ad68e7c2bc66d06b72e",
"url": "/taskmate/taskmate-points-card.js?v=5.3.0",
"url": "/taskmate/taskmate-points-card.js?v=5.4.1",
"type": "module"
},
{
"id": "02613225844746b699bfbfac9642eca2",
"url": "/taskmate/taskmate-reorder-card.js?v=5.3.0",
"url": "/taskmate/taskmate-reorder-card.js?v=5.4.1",
"type": "module"
},
{
"id": "7a06a07e17ba4b31b712bc91b77360b4",
"url": "/taskmate/taskmate-overview-card.js?v=5.3.0",
"url": "/taskmate/taskmate-overview-card.js?v=5.4.1",
"type": "module"
},
{
"id": "08b42b53b33b4fa88db932455cf8d5aa",
"url": "/taskmate/taskmate-activity-card.js?v=5.3.0",
"url": "/taskmate/taskmate-activity-card.js?v=5.4.1",
"type": "module"
},
{
"id": "15907eff51784acdba88ff694b574509",
"url": "/taskmate/taskmate-streak-card.js?v=5.3.0",
"url": "/taskmate/taskmate-streak-card.js?v=5.4.1",
"type": "module"
},
{
"id": "1bd6bb5c0a6d4f0d916e3d82cb7fac39",
"url": "/taskmate/taskmate-weekly-card.js?v=5.3.0",
"url": "/taskmate/taskmate-weekly-card.js?v=5.4.1",
"type": "module"
},
{
"id": "289ce5943f5a4972a593697db4c96ece",
"url": "/taskmate/taskmate-graph-card.js?v=5.3.0",
"url": "/taskmate/taskmate-graph-card.js?v=5.4.1",
"type": "module"
},
{
"id": "d2904acee4c74a398dcc7b996c23e7e7",
"url": "/taskmate/taskmate-reward-progress-card.js?v=5.3.0",
"url": "/taskmate/taskmate-reward-progress-card.js?v=5.4.1",
"type": "module"
},
{
"id": "46d378736fd54a26be253f2dfb27202e",
"url": "/taskmate/taskmate-leaderboard-card.js?v=5.3.0",
"url": "/taskmate/taskmate-leaderboard-card.js?v=5.4.1",
"type": "module"
},
{
"id": "db11ae36457842fa826c7991e1d9c6f4",
"url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.3.0",
"url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.4.1",
"type": "module"
},
{
"id": "ce85f301b59a412e9abfe5ffd51cb9a2",
"url": "/taskmate/taskmate-penalties-card.js?v=5.3.0",
"url": "/taskmate/taskmate-penalties-card.js?v=5.4.1",
"type": "module"
},
{
"id": "1959e1629de2422daa8adaf87069c288",
"url": "/taskmate/taskmate-bonuses-card.js?v=5.3.0",
"url": "/taskmate/taskmate-bonuses-card.js?v=5.4.1",
"type": "module"
},
{
"id": "2548524b269c4959bc7e0e967df943af",
"url": "/taskmate/taskmate-points-display-card.js?v=5.3.0",
"url": "/taskmate/taskmate-points-display-card.js?v=5.4.1",
"type": "module"
},
{
"id": "36ada49368db4187a283fbbbef45454f",
"url": "/taskmate/taskmate-calendar-card.js?v=5.3.0",
"url": "/taskmate/taskmate-calendar-card.js?v=5.4.1",
"type": "module"
},
{
"id": "67c32687acd2497b90030ef141f4b6db",
"url": "/taskmate/taskmate-photo-gallery-card.js?v=5.3.0",
"url": "/taskmate/taskmate-photo-gallery-card.js?v=5.4.1",
"type": "module"
},
{
"id": "6f0c8348a91f4b74a9b846ab3f62621f",
"url": "/taskmate/taskmate-family-goal-card.js?v=5.3.0",
"url": "/taskmate/taskmate-family-goal-card.js?v=5.4.1",
"type": "module"
},
{
@@ -276,7 +276,7 @@
},
{
"id": "73dbd2de03de4530b451a55bfe6c46cf",
"url": "/taskmate/taskmate-routine-card.js?v=5.3.0",
"url": "/taskmate/taskmate-routine-card.js?v=5.4.1",
"type": "module"
},
{
@@ -315,7 +315,7 @@
"type": "module"
},
{
"id": "9227b25fc2ea4cb5be22e4436912268a",
"id": "17e81c8801114b2d941a7d5f22eb47e6",
"url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1",
"type": "module"
}
@@ -564,6 +564,12 @@ async def async_setup_entry(
_LOGGER.debug("Maintenance calendar entity created")
def _task_label(task: MaintenanceTask) -> str:
"""Task name, with the due cycle phase appended for phased tasks (#139)."""
phase = task.current_phase_name
return f"{task.name} · {phase}" if phase else task.name
class MaintenanceCalendar(CalendarEntity):
"""Calendar entity aggregating all maintenance tasks."""
@@ -713,7 +719,7 @@ class MaintenanceCalendar(CalendarEntity):
today = dt_util.now().date()
if start_d <= today <= end_d:
return CalendarEvent(
summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {task.name} ({object_name})",
summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {_task_label(task)} ({object_name})",
start=today,
end=today + timedelta(days=1),
description=_cal_t("manually_triggered", lang),
@@ -728,7 +734,7 @@ class MaintenanceCalendar(CalendarEntity):
today = dt_util.now().date()
if start_d <= today <= end_d:
return CalendarEvent(
summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {task.name} ({object_name})",
summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {_task_label(task)} ({object_name})",
start=today,
end=today + timedelta(days=1),
description=_cal_t("sensor_triggered", lang, name=task.name),
@@ -770,7 +776,7 @@ class MaintenanceCalendar(CalendarEntity):
pass # malformed schedule_time → fall back to all-day
return CalendarEvent(
summary=f"{prefix} {task.name} ({object_name})",
summary=f"{prefix} {_task_label(task)} ({object_name})",
start=start,
end=end,
description=(
@@ -143,6 +143,9 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow):
menu.append("remove_trigger")
if global_opts.get(CONF_ADVANCED_CHECKLISTS, False):
menu.append("edit_checklist")
# #139: cycle phases are not feature-gated (the panel editor isn't
# either) — the minimal flow editor is always reachable.
menu.append("edit_phases")
if global_opts.get(CONF_ADVANCED_ADAPTIVE, False):
menu.append("adaptive_scheduling")
menu.extend(["delete_task", "manage_tasks"])
@@ -539,6 +539,73 @@ class TaskCrudMixin:
},
)
async def async_step_edit_phases(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Minimal cycle-phase editor (#139): one textarea, one line per
sequence step, optional per-phase checklist as "Name: item; item".
Names matching existing phases keep their ids (history phase_id and
the Store cursor stay stable) AND their panel-only overrides (parts /
required fields). Sanitizing + storage go through the SAME
``_apply_phase_fields`` as the WS write path, and the Store cursor is
clamped the same way ws_update_task clamps it.
"""
from .helpers.phases import clamp_phase_cursor, parse_phases_text, phases_to_text
from .websocket.tasks_crud import _apply_phase_fields
tasks_data = self.config_entry.data.get(CONF_TASKS, {})
task = tasks_data.get(self._selected_task_id or "", {})
if user_input is not None:
if user_input.get("go_back"):
return self._show_task_action_menu()
raw_defs, raw_seq = parse_phases_text(
user_input.get("phases_text", ""), task.get("phases") or {}
)
new_data = dict(self.config_entry.data)
new_tasks = dict(new_data.get(CONF_TASKS, {}))
updated_task = dict(new_tasks.get(self._selected_task_id or "", {}))
_apply_phase_fields(self.hass, self.config_entry, updated_task, raw_defs, raw_seq)
new_tasks[self._selected_task_id or ""] = updated_task
new_data[CONF_TASKS] = new_tasks
self._update_config_entry(new_data)
# Clamp/clear the Store cursor against the edited sequence —
# the Store wins on restore, so an out-of-range cursor would
# otherwise survive this edit forever (same block as ws_update).
rd = getattr(self.config_entry, "runtime_data", None)
store = getattr(rd, "store", None)
if store is not None and self._selected_task_id:
seq = updated_task.get("phase_sequence") or []
if seq:
cur = store.get_task_state(self._selected_task_id).get("phase_cursor", 0)
store.set_phase_cursor(self._selected_task_id, clamp_phase_cursor(cur, len(seq)))
else:
store.get_task_state(self._selected_task_id).pop("phase_cursor", None)
store.async_delay_save()
return self._show_task_action_menu()
default_text = phases_to_text(task.get("phases") or {}, task.get("phase_sequence") or [])
return self.async_show_form(
step_id="edit_phases",
data_schema=vol.Schema(
{
vol.Optional("phases_text", default=default_text): selector.TextSelector(
selector.TextSelectorConfig(
type=selector.TextSelectorType.TEXT,
multiline=True,
)
),
vol.Optional("go_back", default=False): selector.BooleanSelector(),
}
),
description_placeholders={
"task_name": task.get("name", ""),
},
)
async def async_step_delete_task(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Confirm and delete a task."""
tasks_data = self.config_entry.data.get(CONF_TASKS, {})
@@ -59,6 +59,16 @@ from .storage import MaintenanceStore
_LOGGER = logging.getLogger(__name__)
def _notify_task_label(task: dict[str, Any]) -> str:
"""Task name for notifications — phased tasks (#139) name the due step,
so "Mower blades · Replace blades" tells the user what the work IS."""
from .helpers.phases import current_phase
name = task.get("name", "")
phase = current_phase(task)
return f"{name} · {phase['name']}" if phase else name
def _inert_task_result(task: MaintenanceTask, status: str, **extra: Any) -> dict[str, Any]:
"""Coordinator payload for a task that gets NO live evaluation (archived /
paused): due fields nulled, trigger off, only cost/history-derived fields
@@ -744,7 +754,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
tasks=[
{
"task_id": tid,
"task_name": tr.get("name", ""),
"task_name": _notify_task_label(tr),
"status": status,
"days_until_due": tr.get("_days_until_due"),
}
@@ -756,7 +766,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
await nm.async_task_status_changed(
entry_id=self.entry.entry_id,
task_id=task_id,
task_name=task_result.get("name", ""),
task_name=_notify_task_label(task_result),
object_name=obj_name,
new_status=new_status,
days_until_due=task_result.get("_days_until_due"),
@@ -862,6 +872,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
state["due_override"] = do
elif "due_override" in state:
del state["due_override"]
# Phase cursor (#139): to_dict emits it only for phase-carrying tasks.
pc = td.get("phase_cursor")
if pc is not None:
self._store.set_phase_cursor(task_id, pc)
self._store.set_history(task_id, td.get("history", []))
if task.adaptive_config:
self._store.set_adaptive_config(task_id, task.adaptive_config)
@@ -1061,8 +1075,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# will consume below — go on the record, so completions from the
# no-dialog surfaces (service, button, QR, to-do, voice, recovery)
# stay correctable through the history editor like dialog ones.
from .helpers.phases import effective_field as _phase_effective
record_links = (
used_parts if used_parts is not None else (merged[task_id].get("consumes_parts") or [])
used_parts if used_parts is not None else (_phase_effective(merged[task_id], "consumes_parts") or [])
)
enriched_used: list[dict[str, Any]] | None = None
if used_parts is not None or record_links:
@@ -134,6 +134,12 @@ def _build_export_object(
"required_completion_fields": tdata.get("required_completion_fields"),
"rotation_strategy": tdata.get("rotation_strategy"),
"reading_unit": tdata.get("reading_unit"),
# Task phases (#139): defs + cycle are static config; the cursor is
# Store-merged by merged_tasks, so a restore resumes mid-cycle
# instead of silently restarting at step one.
"phases": tdata.get("phases"),
"phase_sequence": tdata.get("phase_sequence"),
"phase_cursor": tdata.get("phase_cursor"),
# Spare parts: consumption links + the auto-buy-task marker.
"consumes_parts": tdata.get("consumes_parts"),
"part_ref": tdata.get("part_ref"),
@@ -40,6 +40,9 @@ export class MaintenanceCompleteDialog extends LitElement {
* enforces the same list at every completion surface; blocking Save here
* just means the user never has to meet that rejection. */
@property({ type: Array }) public requiredFields: string[] = [];
/** #139: "2/4 · Flip blades" — which phase of the cycle this completion
* records. Purely informative; the backend advances the cursor itself. */
@property() public phaseLabel = "";
@state() private _open = false;
@state() private _notes = "";
@state() private _cost = "";
@@ -286,6 +289,7 @@ export class MaintenanceCompleteDialog extends LitElement {
return html`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t("complete_title", L)}${this.taskName}</div>
${this.phaseLabel ? html`<div class="phase-line">${t("phase_current", L)}: ${this.phaseLabel}</div>` : nothing}
<div class="content">
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
${this.checklist.length > 0 ? html`
@@ -473,6 +477,12 @@ export class MaintenanceCompleteDialog extends LitElement {
font-weight: 500;
padding-bottom: 12px;
}
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-direction: column;
@@ -260,6 +260,18 @@ export class MaintenanceTaskDialog extends LitElement {
// Checklist (newline-separated steps, one per line)
@state() private _checklistText = "";
// Task phases (#139): editable defs + the cycle order. The v1 editor keeps
// each phase to name + checklist + at most ONE own-object part (the mower
// "14 new blades" case); richer per-phase config exists at the WS level.
// `carry` holds def fields the editor has no form for (notes,
// required_completion_fields, future keys) and `extraParts` any links
// beyond the first — both re-merged on save so a no-op edit never wipes
// them (#106 pattern; same passthrough idea as trigger_config's carry).
@state() private _phaseDefs: Array<{
id: string; name: string; checklistText: string; partId: string; partQty: string;
extraParts: TaskPartLink[]; carry: Record<string, unknown>;
}> = [];
@state() private _phaseSeq: string[] = [];
/** Details this task demands on completion — enforced by the backend on
* every surface, so a button press or voice command cannot bypass it. */
@state() private _requiredCompletion: string[] = [];
@@ -388,6 +400,19 @@ export class MaintenanceTaskDialog extends LitElement {
this._rotationStrategy = task.rotation_strategy || "";
this._checklistText = (task.checklist || []).join("\n");
this._phaseDefs = Object.entries(task.phases || {}).map(([id, def]) => {
const { name: _n, checklist: _c, consumes_parts: _p, ...carry } = def as unknown as Record<string, unknown>;
return {
id,
name: def.name || id,
checklistText: (def.checklist || []).join("\n"),
partId: def.consumes_parts?.[0]?.part_id || "",
partQty: def.consumes_parts?.[0]?.quantity != null ? String(def.consumes_parts[0].quantity) : "",
extraParts: (def.consumes_parts || []).slice(1),
carry,
};
});
this._phaseSeq = [...(task.phase_sequence || [])];
this._requiredCompletion = [...(task.required_completion_fields || [])];
this._scheduleTime = task.schedule_time || "";
@@ -506,6 +531,8 @@ export class MaintenanceTaskDialog extends LitElement {
this._assigneePool = [];
this._rotationStrategy = "";
this._checklistText = "";
this._phaseDefs = [];
this._phaseSeq = [];
this._requiredCompletion = [];
this._scheduleTime = "";
this._environmentalEntity = "";
@@ -993,6 +1020,103 @@ export class MaintenanceTaskDialog extends LitElement {
this._requiredCompletion = [...next];
}
// ── Task phases (#139) ───────────────────────────────────────────────
private _phaseSlug(name: string): string {
const base = name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 24) || "phase";
let slug = base;
let n = 2;
while (this._phaseDefs.some((d) => d.id === slug)) slug = `${base}-${n++}`;
return slug;
}
private _addPhaseDef(): void {
const id = this._phaseSlug(`phase-${this._phaseDefs.length + 1}`);
this._phaseDefs = [...this._phaseDefs, { id, name: "", checklistText: "", partId: "", partQty: "", extraParts: [], carry: {} }];
}
private _removePhaseDef(id: string): void {
this._phaseDefs = this._phaseDefs.filter((d) => d.id !== id);
this._phaseSeq = this._phaseSeq.filter((s) => s !== id);
}
private _patchPhaseDef(id: string, patch: Partial<{ name: string; checklistText: string; partId: string; partQty: string }>): void {
this._phaseDefs = this._phaseDefs.map((d) => (d.id === id ? { ...d, ...patch } : d));
}
private _renderPhasesEditor(L: string) {
const defName = (id: string) => this._phaseDefs.find((d) => d.id === id)?.name || id;
return html`
<h3>${t("phases_section", L)}</h3>
<div class="field-help">${t("phases_hint", L)}</div>
${this._phaseDefs.map((d) => html`
<div class="phase-def">
<div class="phase-def-head">
<ms-textfield
label="${t("phase_name", L)}"
.value=${d.name}
@input=${(e: Event) => this._patchPhaseDef(d.id, { name: (e.target as HTMLInputElement).value })}
></ms-textfield>
${this.parts.length ? html`
<select
class="phase-part"
.value=${d.partId}
@change=${(e: Event) => this._patchPhaseDef(d.id, { partId: (e.target as HTMLSelectElement).value })}
>
<option value="">—</option>
${this.parts.map((p) => html`<option value=${p.id} ?selected=${p.id === d.partId}>${p.name}</option>`)}
</select>
${d.partId ? html`
<input class="phase-qty" type="number" min="0.01" step="0.01" .value=${d.partQty || "1"}
@input=${(e: Event) => this._patchPhaseDef(d.id, { partQty: (e.target as HTMLInputElement).value })} />
` : nothing}
` : nothing}
<mwc-icon-button class="phase-remove" @click=${() => this._removePhaseDef(d.id)}>
<ha-icon icon="mdi:delete-outline"></ha-icon>
</mwc-icon-button>
</div>
${this.checklistsEnabled ? html`
<textarea
class="checklist-textarea phase-checklist"
rows="2"
placeholder="${t("checklist_placeholder", L)}"
.value=${d.checklistText}
@input=${(e: Event) => this._patchPhaseDef(d.id, { checklistText: (e.target as HTMLTextAreaElement).value })}
></textarea>
` : nothing}
</div>
`)}
<ha-button appearance="plain" @click=${this._addPhaseDef}>
<ha-icon icon="mdi:plus"></ha-icon> ${t("phase_add", L)}
</ha-button>
${this._phaseDefs.some((d) => d.name.trim()) ? html`
<div class="phase-seq-label">${t("phase_sequence_label", L)}</div>
<div class="phase-seq">
${this._phaseSeq.map((id, i) => html`
<span class="phase-chip">
${i + 1}. ${defName(id)}
<button class="phase-chip-x" @click=${() => {
this._phaseSeq = this._phaseSeq.filter((_, idx) => idx !== i);
}}>✕</button>
</span>
`)}
<select
class="phase-seq-add"
.value=${""}
@change=${(e: Event) => {
const id = (e.target as HTMLSelectElement).value;
if (id) this._phaseSeq = [...this._phaseSeq, id];
(e.target as HTMLSelectElement).value = "";
}}
>
<option value="">+ ${t("phase_sequence_add_step", L)}</option>
${this._phaseDefs.filter((d) => d.name.trim()).map((d) => html`<option value=${d.id}>${d.name}</option>`)}
</select>
</div>
` : nothing}
`;
}
private async _save(): Promise<void> {
if (this._loading) return; // synchronous re-entry guard (double-click)
if (!this._name.trim()) return;
@@ -1073,6 +1197,27 @@ export class MaintenanceTaskDialog extends LitElement {
data.last_performed = this._lastPerformed || null;
data.nfc_tag_id = this._nfcTagId || null;
data.reading_unit = this._readingUnit.trim() || null;
// Task phases (#139): always sent — null clears a removed cycle.
{
const defs: Record<string, unknown> = {};
for (const d of this._phaseDefs) {
if (!d.name.trim()) continue;
const def: Record<string, unknown> = { ...d.carry, name: d.name.trim() };
const items = d.checklistText.split("\n").map((s) => s.trim()).filter(Boolean);
if (items.length) def.checklist = items;
const links: TaskPartLink[] = [];
if (d.partId) {
const qty = parseFloat(d.partQty);
links.push({ part_id: d.partId, quantity: Number.isFinite(qty) && qty > 0 ? qty : 1 });
}
links.push(...d.extraParts);
if (links.length) def.consumes_parts = links;
defs[d.id] = def;
}
const seq = this._phaseSeq.filter((id) => id in defs);
data.phases = Object.keys(defs).length && seq.length ? defs : null;
data.phase_sequence = data.phases ? seq : null;
}
// Only send when a picker was actually rendered. A failed parts load
// leaves both lists empty, and sending [] then would silently wipe links
// the user never saw. entry_id is written ONLY for a foreign pick, so an
@@ -2463,6 +2608,7 @@ export class MaintenanceTaskDialog extends LitElement {
></textarea>
<div class="field-help">${t("checklist_help", L)}</div>
` : nothing}
${this._renderPhasesEditor(L)}
<h3>${t("require_on_completion", L)}</h3>
<div class="required-completion">
${REQUIRED_COMPLETION_KEYS.map((field) => html`
@@ -2742,6 +2888,82 @@ export class MaintenanceTaskDialog extends LitElement {
gap: 8px;
padding: 2px 0;
}
.phase-def {
border: 1px solid var(--divider-color);
border-radius: 6px;
padding: 8px;
margin: 6px 0;
}
.phase-def-head {
display: flex;
align-items: center;
gap: 8px;
}
.phase-def-head ms-textfield {
flex: 1;
min-width: 0;
}
.phase-part {
max-width: 160px;
padding: 6px;
border: 1px solid var(--divider-color);
border-radius: 4px;
background: var(--card-background-color);
color: var(--primary-text-color);
}
.phase-qty {
width: 64px;
padding: 6px;
border: 1px solid var(--divider-color);
border-radius: 4px;
background: var(--card-background-color);
color: var(--primary-text-color);
}
.phase-checklist {
min-height: 56px;
margin-top: 6px;
}
.phase-remove {
--mdc-icon-button-size: 36px;
color: var(--secondary-text-color);
}
.phase-seq-label {
font-size: 12px;
color: var(--secondary-text-color);
margin-top: 8px;
}
.phase-seq {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 4px 0;
}
.phase-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 12px;
background: var(--secondary-background-color);
font-size: 13px;
}
.phase-chip-x {
border: none;
background: none;
color: var(--secondary-text-color);
cursor: pointer;
padding: 0 2px;
font-size: 12px;
}
.phase-seq-add {
padding: 4px 8px;
border: 1px dashed var(--divider-color);
border-radius: 12px;
background: transparent;
color: var(--secondary-text-color);
font-size: 13px;
}
.consumes-check {
display: flex;
align-items: center;
@@ -18,6 +18,7 @@ import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInter
import { describeWsError } from "../ws-errors";
import { isoDateLocal } from "../helpers/calendar-bucket";
import { partsForCompletion } from "../helpers/shared-parts";
import { effectivePhase, phaseLabel } from "../helpers/phases";
import { renderWeibullSection } from "../renderers/weibull";
import { renderPredictionSection } from "../renderers/prediction";
import { renderRecommendationBars } from "../renderers/recommendation";
@@ -158,13 +159,17 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
import("../dialog-mount").then(async ({ openCompleteDialog }) => {
const task = this._task!;
const isBuy = !!(task as { part_ref?: string }).part_ref;
// #139: a phased task completes the phase currently due, so the phase's
// checklist/parts/required fields replace the task-level ones here.
const phase = effectivePhase(task);
const links = phase ? phase.consumesParts : (task.consumes_parts || []);
let parts: Parameters<typeof openCompleteDialog>[0]["parts"] = [];
if (!isBuy) {
try {
const r = await this.hass.connection.sendMessagePromise<{
objects: MaintenanceObjectResponse[];
}>({ type: "maintenance_supporter/objects", compact: true });
parts = partsForCompletion(task, this._entryId!, r.objects || [], this._lang);
parts = partsForCompletion({ consumes_parts: links }, this._entryId!, r.objects || [], this._lang);
} catch {
// Parts stay empty — the dialog still completes without them.
}
@@ -173,13 +178,14 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
entry_id: this._entryId!,
task_id: this._taskId!,
task_name: task.name,
checklist: task.checklist || [],
checklist: phase ? phase.checklist : (task.checklist || []),
adaptive_enabled: !!task.adaptive_config?.enabled,
required_completion_fields: task.required_completion_fields || [],
required_completion_fields: phase ? phase.requiredFields : (task.required_completion_fields || []),
task_type: task.type || "",
reading_unit: (task as { reading_unit?: string }).reading_unit || "",
parts,
consumes_parts: isBuy ? [] : (task.consumes_parts || []),
consumes_parts: isBuy ? [] : links,
phase_label: phaseLabel(task),
});
if (ok) {
this._notifyChanged("complete");
@@ -542,6 +548,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
${(task.schedule?.kind && !["manual", "one_time"].includes(task.schedule.kind)) || task.interval_days != null
? html`<span><strong>${t("interval", L) || "Interval"}:</strong> ${formatRecurrence(task, L)}</span>`
: nothing}
${phaseLabel(task)
? html`<span><strong>${t("phase_current", L)}:</strong> ${phaseLabel(task)}</span>`
: nothing}
</div>
</div>
@@ -289,6 +289,8 @@ export function openCompleteDialog(args: {
/** #99/#111: per-completion parts selection incl. shared pools. */
parts?: MaintenanceCompleteDialog["parts"];
consumes_parts?: MaintenanceCompleteDialog["consumesParts"];
/** #139: "2/4 · Flip blades" — the phase this completion records. */
phase_label?: string;
}): boolean {
const dlg = getOrCreate<MaintenanceCompleteDialog>(COMPLETE_DIALOG_TAG);
if (!syncHass(dlg)) return false;
@@ -304,6 +306,7 @@ export function openCompleteDialog(args: {
dlg.readingUnit = args.reading_unit ?? "";
dlg.parts = args.parts ?? [];
dlg.consumesParts = args.consumes_parts ?? [];
dlg.phaseLabel = args.phase_label ?? "";
dlg.lang = (getHass()?.language) || "en";
dlg.open();
return true;
@@ -889,5 +889,14 @@
"service_record_entries": "záznamů",
"completed_by": "Dokončil",
"date_from": "Od",
"date_to": "Do"
"date_to": "Do",
"phases_section": "Fáze cyklu (volitelné)",
"phases_hint": "Různé práce ve stejném rytmu — každé dokončení posune na další krok (např. malý, malý, velký servis).",
"phase_add": "Přidat fázi",
"phase_name": "Název fáze",
"phase_sequence_label": "Pořadí cyklu",
"phase_sequence_add_step": "Přidat krok",
"phase_current": "Aktuální fáze",
"phase_set": "Nastavit jako aktuální",
"chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "poster",
"completed_by": "Udført af",
"date_from": "Fra",
"date_to": "Til"
"date_to": "Til",
"phases_section": "Cyklusfaser (valgfrit)",
"phases_hint": "Forskelligt arbejde i samme rytme — hver udførelse går videre til næste trin (f.eks. lille, lille, stor service).",
"phase_add": "Tilføj fase",
"phase_name": "Fasenavn",
"phase_sequence_label": "Cyklusrækkefølge",
"phase_sequence_add_step": "Tilføj trin",
"phase_current": "Aktuel fase",
"phase_set": "Sæt som aktuel",
"chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "Einträge",
"completed_by": "Erledigt von",
"date_from": "Von",
"date_to": "Bis"
"date_to": "Bis",
"phases_section": "Zyklus-Phasen (optional)",
"phases_hint": "Unterschiedliche Arbeiten im gleichen Takt — jede Erledigung schaltet zum nächsten Schritt (z. B. klein, klein, groß).",
"phase_add": "Phase hinzufügen",
"phase_name": "Phasenname",
"phase_sequence_label": "Zyklus-Reihenfolge",
"phase_sequence_add_step": "Schritt anhängen",
"phase_current": "Aktuelle Phase",
"phase_set": "Als aktuell setzen",
"chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "entries",
"completed_by": "Completed by",
"date_from": "From",
"date_to": "To"
"date_to": "To",
"phases_section": "Cycle phases (optional)",
"phases_hint": "Different work on one shared schedule — each completion moves to the next step (e.g. small service, small service, big service).",
"phase_add": "Add phase",
"phase_name": "Phase name",
"phase_sequence_label": "Cycle order",
"phase_sequence_add_step": "Add step",
"phase_current": "Current phase",
"phase_set": "Set as current",
"chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "entradas",
"completed_by": "Realizado por",
"date_from": "Desde",
"date_to": "Hasta"
"date_to": "Hasta",
"phases_section": "Fases del ciclo (opcional)",
"phases_hint": "Trabajos distintos con un mismo ritmo: cada finalización pasa al siguiente paso (p. ej. pequeño, pequeño, grande).",
"phase_add": "Añadir fase",
"phase_name": "Nombre de la fase",
"phase_sequence_label": "Orden del ciclo",
"phase_sequence_add_step": "Añadir paso",
"phase_current": "Fase actual",
"phase_set": "Establecer como actual",
"chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "merkintää",
"completed_by": "Suorittanut",
"date_from": "Alkaen",
"date_to": "Asti"
"date_to": "Asti",
"phases_section": "Syklin vaiheet (valinnainen)",
"phases_hint": "Eri työt samassa tahdissa — jokainen suoritus siirtyy seuraavaan vaiheeseen (esim. pieni, pieni, iso huolto).",
"phase_add": "Lisää vaihe",
"phase_name": "Vaiheen nimi",
"phase_sequence_label": "Syklin järjestys",
"phase_sequence_add_step": "Lisää askel",
"phase_current": "Nykyinen vaihe",
"phase_set": "Aseta nykyiseksi",
"chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "entrées",
"completed_by": "Réalisé par",
"date_from": "Du",
"date_to": "Au"
"date_to": "Au",
"phases_section": "Phases du cycle (optionnel)",
"phases_hint": "Des travaux différents sur un même rythme — chaque réalisation passe à l'étape suivante (p. ex. petit, petit, grand entretien).",
"phase_add": "Ajouter une phase",
"phase_name": "Nom de la phase",
"phase_sequence_label": "Ordre du cycle",
"phase_sequence_add_step": "Ajouter une étape",
"phase_current": "Phase actuelle",
"phase_set": "Définir comme actuelle",
"chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "प्रविष्टियाँ",
"completed_by": "द्वारा पूर्ण",
"date_from": "से",
"date_to": "तक"
"date_to": "तक",
"phases_section": "चक्र चरण (वैकल्पिक)",
"phases_hint": "एक ही लय में अलग-अलग कार्य — प्रत्येक पूर्णता अगले चरण पर ले जाती है (जैसे छोटा, छोटा, बड़ा)।",
"phase_add": "चरण जोड़ें",
"phase_name": "चरण का नाम",
"phase_sequence_label": "चक्र क्रम",
"phase_sequence_add_step": "कदम जोड़ें",
"phase_current": "वर्तमान चरण",
"phase_set": "वर्तमान बनाएँ",
"chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "bejegyzés",
"completed_by": "Elvégezte",
"date_from": "Ettől",
"date_to": "Eddig"
"date_to": "Eddig",
"phases_section": "Ciklusfázisok (opcionális)",
"phases_hint": "Különböző munkák azonos ütemben — minden elvégzés a következő lépésre vált (pl. kis, kis, nagy szerviz).",
"phase_add": "Fázis hozzáadása",
"phase_name": "Fázis neve",
"phase_sequence_label": "Ciklus sorrendje",
"phase_sequence_add_step": "Lépés hozzáadása",
"phase_current": "Aktuális fázis",
"phase_set": "Beállítás aktuálisként",
"chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "voci",
"completed_by": "Completato da",
"date_from": "Dal",
"date_to": "Al"
"date_to": "Al",
"phases_section": "Fasi del ciclo (opzionale)",
"phases_hint": "Lavori diversi sullo stesso ritmo — ogni completamento passa al passo successivo (es. piccolo, piccolo, grande).",
"phase_add": "Aggiungi fase",
"phase_name": "Nome della fase",
"phase_sequence_label": "Ordine del ciclo",
"phase_sequence_add_step": "Aggiungi passo",
"phase_current": "Fase attuale",
"phase_set": "Imposta come attuale",
"chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "件",
"completed_by": "実施者",
"date_from": "開始",
"date_to": "終了"
"date_to": "終了",
"phases_section": "サイクルフェーズ(任意)",
"phases_hint": "同じ周期で異なる作業を行います。完了するたびに次のステップへ進みます(例:小・小・大整備)。",
"phase_add": "フェーズを追加",
"phase_name": "フェーズ名",
"phase_sequence_label": "サイクルの順序",
"phase_sequence_add_step": "ステップを追加",
"phase_current": "現在のフェーズ",
"phase_set": "現在に設定",
"chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "건",
"completed_by": "수행자",
"date_from": "시작",
"date_to": "종료"
"date_to": "종료",
"phases_section": "주기 단계(선택)",
"phases_hint": "같은 주기에 다른 작업을 수행합니다. 완료할 때마다 다음 단계로 넘어갑니다(예: 소·소·대 정비).",
"phase_add": "단계 추가",
"phase_name": "단계 이름",
"phase_sequence_label": "주기 순서",
"phase_sequence_add_step": "스텝 추가",
"phase_current": "현재 단계",
"phase_set": "현재로 설정",
"chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "oppføringer",
"completed_by": "Utført av",
"date_from": "Fra",
"date_to": "Til"
"date_to": "Til",
"phases_section": "Syklusfaser (valgfritt)",
"phases_hint": "Ulikt arbeid i samme rytme — hver fullføring går videre til neste trinn (f.eks. liten, liten, stor service).",
"phase_add": "Legg til fase",
"phase_name": "Fasenavn",
"phase_sequence_label": "Syklusrekkefølge",
"phase_sequence_add_step": "Legg til trinn",
"phase_current": "Gjeldende fase",
"phase_set": "Sett som gjeldende",
"chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "items",
"completed_by": "Voltooid door",
"date_from": "Van",
"date_to": "Tot"
"date_to": "Tot",
"phases_section": "Cyclusfasen (optioneel)",
"phases_hint": "Verschillend werk op één ritme — elke afronding schuift naar de volgende stap (bijv. klein, klein, groot).",
"phase_add": "Fase toevoegen",
"phase_name": "Fasenaam",
"phase_sequence_label": "Cyclusvolgorde",
"phase_sequence_add_step": "Stap toevoegen",
"phase_current": "Huidige fase",
"phase_set": "Instellen als huidige",
"chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "wpisów",
"completed_by": "Wykonane przez",
"date_from": "Od",
"date_to": "Do"
"date_to": "Do",
"phases_section": "Fazy cyklu (opcjonalnie)",
"phases_hint": "Różne prace w jednym rytmie — każde ukończenie przechodzi do następnego kroku (np. mały, mały, duży).",
"phase_add": "Dodaj fazę",
"phase_name": "Nazwa fazy",
"phase_sequence_label": "Kolejność cyklu",
"phase_sequence_add_step": "Dodaj krok",
"phase_current": "Bieżąca faza",
"phase_set": "Ustaw jako bieżącą",
"chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "registros",
"completed_by": "Concluído por",
"date_from": "De",
"date_to": "Até"
"date_to": "Até",
"phases_section": "Fases do ciclo (opcional)",
"phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o próximo passo (ex.: pequeno, pequeno, grande).",
"phase_add": "Adicionar fase",
"phase_name": "Nome da fase",
"phase_sequence_label": "Ordem do ciclo",
"phase_sequence_add_step": "Adicionar passo",
"phase_current": "Fase atual",
"phase_set": "Definir como atual",
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "registos",
"completed_by": "Concluído por",
"date_from": "De",
"date_to": "Até"
"date_to": "Até",
"phases_section": "Fases do ciclo (opcional)",
"phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o passo seguinte (ex.: pequeno, pequeno, grande).",
"phase_add": "Adicionar fase",
"phase_name": "Nome da fase",
"phase_sequence_label": "Ordem do ciclo",
"phase_sequence_add_step": "Adicionar passo",
"phase_current": "Fase atual",
"phase_set": "Definir como atual",
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "записей",
"completed_by": "Выполнил",
"date_from": "С",
"date_to": "По"
"date_to": "По",
"phases_section": "Фазы цикла (необязательно)",
"phases_hint": "Разные работы в одном ритме — каждое выполнение переходит к следующему шагу (напр., малое, малое, большое ТО).",
"phase_add": "Добавить фазу",
"phase_name": "Название фазы",
"phase_sequence_label": "Порядок цикла",
"phase_sequence_add_step": "Добавить шаг",
"phase_current": "Текущая фаза",
"phase_set": "Сделать текущей",
"chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "poster",
"completed_by": "Utförd av",
"date_from": "Från",
"date_to": "Till"
"date_to": "Till",
"phases_section": "Cykelfaser (valfritt)",
"phases_hint": "Olika arbeten i samma rytm — varje slutförande går vidare till nästa steg (t.ex. liten, liten, stor service).",
"phase_add": "Lägg till fas",
"phase_name": "Fasnamn",
"phase_sequence_label": "Cykelordning",
"phase_sequence_add_step": "Lägg till steg",
"phase_current": "Aktuell fas",
"phase_set": "Ange som aktuell",
"chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "kayıt",
"completed_by": "Tamamlayan",
"date_from": "Başlangıç",
"date_to": "Bitiş"
"date_to": "Bitiş",
"phases_section": "Döngü aşamaları (isteğe bağlı)",
"phases_hint": "Aynı ritimde farklı işler — her tamamlama bir sonraki adıma geçer (örn. küçük, küçük, büyük bakım).",
"phase_add": "Aşama ekle",
"phase_name": "Aşama adı",
"phase_sequence_label": "Döngü sırası",
"phase_sequence_add_step": "Adım ekle",
"phase_current": "Geçerli aşama",
"phase_set": "Geçerli olarak ayarla",
"chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "записів",
"completed_by": "Виконав",
"date_from": "Від",
"date_to": "До"
"date_to": "До",
"phases_section": "Фази циклу (необов'язково)",
"phases_hint": "Різні роботи в одному ритмі — кожне виконання переходить до наступного кроку (напр., мале, мале, велике ТО).",
"phase_add": "Додати фазу",
"phase_name": "Назва фази",
"phase_sequence_label": "Порядок циклу",
"phase_sequence_add_step": "Додати крок",
"phase_current": "Поточна фаза",
"phase_set": "Зробити поточною",
"chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "条记录",
"completed_by": "完成人",
"date_from": "从",
"date_to": "至"
"date_to": "至",
"phases_section": "循环阶段(可选)",
"phases_hint": "同一节奏下的不同工作——每次完成后进入下一步(例如:小保养、小保养、大保养)。",
"phase_add": "添加阶段",
"phase_name": "阶段名称",
"phase_sequence_label": "循环顺序",
"phase_sequence_add_step": "添加步骤",
"phase_current": "当前阶段",
"phase_set": "设为当前",
"chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)"
}
@@ -19,6 +19,7 @@ import type {
} from "./types";
import { UserService } from "./user-service";
import { partsForCompletion } from "./helpers/shared-parts";
import { effectivePhase, phaseLabel } from "./helpers/phases";
import "./maintenance-card-editor";
import "./components/complete-dialog";
// The Battery Fleet card ships in this globally-loaded bundle so it is
@@ -469,7 +470,7 @@ export class MaintenanceSupporterCard extends LitElement {
</div>
${!compact
? html`<div class="task-meta">
${object_name} · ${t(task.type, L)}${this._assigneeName(task)
${object_name} · ${t(task.type, L)}${phaseLabel(task) ? html` · ${phaseLabel(task)}` : nothing}${this._assigneeName(task)
? html` · <span class="assignee"
><ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(task)}</span
>`
@@ -514,14 +515,19 @@ export class MaintenanceSupporterCard extends LitElement {
// Stop the row's open-task handler from also firing
e.stopPropagation();
const dlg = this.shadowRoot!.querySelector("maintenance-complete-dialog") as any;
// #139: a phased task completes the phase
// currently due — its fields override the
// task-level ones (fallthrough included).
const phase = effectivePhase(task);
dlg.entryId = entry_id;
dlg.taskId = task.id;
dlg.taskName = task.name;
dlg.checklist = task.checklist || [];
dlg.checklist = phase ? phase.checklist : (task.checklist || []);
dlg.adaptiveEnabled = !!task.adaptive_config?.enabled;
dlg.taskType = task.type || "";
dlg.readingUnit = (task as any).reading_unit || "";
dlg.requiredFields = task.required_completion_fields || [];
dlg.requiredFields = phase ? phase.requiredFields : (task.required_completion_fields || []);
dlg.phaseLabel = phaseLabel(task);
dlg.lang = L;
// #99: editable per-completion parts selection
// (skip on buy tasks — those restock instead).
@@ -530,8 +536,9 @@ export class MaintenanceSupporterCard extends LitElement {
// owner — resolving against the object's own
// parts alone left a foreign link invisible.
const isBuy = !!(task as any).part_ref;
dlg.parts = isBuy ? [] : partsForCompletion(task, entry_id, this._objects, L);
dlg.consumesParts = isBuy ? [] : (task.consumes_parts || []);
const links = phase ? phase.consumesParts : (task.consumes_parts || []);
dlg.parts = isBuy ? [] : partsForCompletion({ consumes_parts: links }, entry_id, this._objects, L);
dlg.consumesParts = isBuy ? [] : links;
dlg.open();
}}
>
@@ -17,6 +17,7 @@ import { OBJECT_COLUMNS, DEFAULT_OBJECTS_TABLE_COLUMNS, sanitizeColumns } from "
import { downloadTextFile } from "./helpers/download";
import { buildTaskWorksheetHtml, type WorksheetExcerpt, type WorksheetLabels } from "./helpers/worksheet";
import { describePartLink, partsForCompletion } from "./helpers/shared-parts";
import { effectivePhase, phaseLabel } from "./helpers/phases";
import { describeWsError } from "./ws-errors";
import { panelStyles } from "./panel-styles";
import type {
@@ -1861,7 +1862,9 @@ export class MaintenanceSupporterPanel extends LitElement {
const task = obj?.tasks.find((x) => x.id === taskId);
if (!task) return;
const state: Record<string, boolean> = {};
for (const step of task.checklist || []) {
// #139: a phased task ticks the checklist of the phase currently due.
const steps = effectivePhase(task)?.checklist ?? (task.checklist || []);
for (const step of steps) {
const current = task.checklist_progress?.[step] ?? false;
state[step] = step === item ? done : current;
}
@@ -1888,11 +1891,20 @@ export class MaintenanceSupporterPanel extends LitElement {
const tk = this._objects
.find((o) => o.entry_id === entryId)
?.tasks.find((tsk) => tsk.id === taskId);
// #139: a phased task completes the phase currently due — its checklist,
// parts and required fields override the task-level values (fallthrough
// is already baked into effectivePhase). Resolved HERE so every call
// site of _openCompleteDialog rotates for free.
const phase = tk ? effectivePhase(tk) : null;
dlg.phaseLabel = phase ? phaseLabel(tk) : "";
dlg.taskType = tk?.type || "";
dlg.readingUnit = tk?.reading_unit || "";
// #73: ticks recorded during the cycle prefill the dialog's checklist.
dlg.checklistPrefill = tk?.checklist_progress || {};
dlg.requiredFields = tk?.required_completion_fields || [];
dlg.requiredFields = phase ? phase.requiredFields : (tk?.required_completion_fields || []);
if (phase) {
dlg.checklist = this._features.checklists ? phase.checklist : [];
}
// Spare parts: a buy task gets an editable restock-qty field; a consuming
// task shows what it will decrement (incl. the storage location).
const objParts = this._objects.find((o) => o.entry_id === entryId)?.parts || [];
@@ -1904,15 +1916,16 @@ export class MaintenanceSupporterPanel extends LitElement {
dlg.currencySymbol = this._currencySymbol;
// #111: a link may point at another object's pool — name that object, and
// never drop a line that fails to resolve (the old .filter(Boolean) hid it).
dlg.consumesInfo = (tk?.consumes_parts || []).map((link) =>
const links = phase ? phase.consumesParts : (tk?.consumes_parts || []);
dlg.consumesInfo = links.map((link) =>
describePartLink(link, entryId, this._objects, this._lang),
);
// #99: editable per-completion parts selection (not on buy tasks — those
// RESTOCK via the qty field instead of consuming). The list carries the
// object's own parts plus the shared pools this task draws on, so a foreign
// link is visible and untickable rather than silently absent.
dlg.parts = tk?.part_ref ? [] : partsForCompletion(tk, entryId, this._objects, this._lang);
dlg.consumesParts = tk?.part_ref ? [] : (tk?.consumes_parts || []);
dlg.parts = tk?.part_ref ? [] : partsForCompletion({ consumes_parts: links }, entryId, this._objects, this._lang);
dlg.consumesParts = tk?.part_ref ? [] : links;
dlg.open();
}
@@ -3535,6 +3548,7 @@ export class MaintenanceSupporterPanel extends LitElement {
lang: this._lang,
detailStatsData: this._detailStatsData,
hasStatsService: !!this._statsService,
historyFallbackIds: this._statsService?.historyFallbackIds,
isCounterEntity: (tc) => this._isCounterEntity(tc),
rangeDays: this._chartRangeDays,
setRangeDays: (days) => this._setChartRange(days),
@@ -3579,6 +3593,10 @@ export class MaintenanceSupporterPanel extends LitElement {
setSearch: (s) => { this._historySearch = s; },
openEdit: (entry) => this._openHistoryEdit(entry),
readingUnit: task?.reading_unit ?? null,
// #139: name the phase badge on entries stamped with a phase_id.
phaseNames: Object.fromEntries(
Object.entries(task?.phases || {}).map(([id, def]) => [id, def.name]),
),
readingDelta: (entry) => {
const idx = readings.findIndex((h) => h.timestamp === entry.timestamp);
if (idx <= 0) return null;
@@ -3604,6 +3622,12 @@ export class MaintenanceSupporterPanel extends LitElement {
objectManualDocs: obj?.object?.manual_docs ?? [],
openManualDoc: (doc) => this._openManualDoc(doc),
setChecklistItem: (item, done) => this._setChecklistItem(entryId, taskId, item, done),
setPhaseCursor: (cursor) => {
void this._runAction({
type: "maintenance_supporter/task/set_phase",
entry_id: entryId, task_id: taskId, cursor,
});
},
isOperator: this._isOperator,
actionLoading: this._actionLoading,
moreMenuOpen: this._moreMenuOpen,
@@ -12,12 +12,16 @@
"playwright": "1.62"
},
"devDependencies": {
"@mdi/js": "^7.4.47",
"@open-wc/testing": "^5.0.0",
"@types/react": "^19.2.18",
"@web/dev-server-esbuild": "^2.0.0",
"@web/test-runner": "^1.0.0",
"@web/test-runner-playwright": "^1.0.0",
"esbuild": "^0.28.1",
"lit": "^3.3.3",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"typescript": "^7.0.2"
}
},
@@ -550,6 +554,13 @@
"@lit-labs/ssr-dom-shim": "^1.5.0"
}
},
"node_modules/@mdi/js": {
"version": "7.4.47",
"resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz",
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@mdn/browser-compat-data": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz",
@@ -1586,6 +1597,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.2.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -2921,6 +2942,13 @@
"node": ">= 8"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT"
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
@@ -4981,6 +5009,29 @@
"node": ">= 0.8"
}
},
"node_modules/react": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
"integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.8",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
"integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.8"
}
},
"node_modules/readdirp": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -5221,6 +5272,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@@ -9,12 +9,16 @@
"test:watch": "web-test-runner --config web-test-runner.config.mjs --watch"
},
"devDependencies": {
"@mdi/js": "^7.4.47",
"@open-wc/testing": "^5.0.0",
"@types/react": "^19.2.18",
"@web/dev-server-esbuild": "^2.0.0",
"@web/test-runner": "^1.0.0",
"@web/test-runner-playwright": "^1.0.0",
"esbuild": "^0.28.1",
"lit": "^3.3.3",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"typescript": "^7.0.2"
},
"dependencies": {
@@ -1053,6 +1053,53 @@ export const panelStyles = css`
border-color: var(--primary-color);
}
/* #139: cycle-phase strip in the task overview */
.phases-card {
background: var(--card-background-color, #fff);
border-radius: 8px;
padding: 12px 16px;
border: 1px solid var(--divider-color);
margin-top: 8px;
}
.phases-card-header {
display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 500;
margin-bottom: 8px;
}
.phases-card-header ha-icon {
--mdc-icon-size: 18px;
color: var(--secondary-text-color);
}
.phases-strip {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.phase-step {
display: flex;
flex-direction: column;
gap: 2px;
padding: 6px 12px;
border-radius: 8px;
border: 1px solid var(--divider-color);
font-size: 13px;
cursor: pointer;
}
.phase-step.current {
border-color: var(--primary-color);
background: color-mix(in srgb, var(--primary-color) 12%, transparent);
font-weight: 500;
cursor: default;
}
.phase-step-last {
font-size: 11px;
color: var(--secondary-text-color);
font-weight: 400;
}
/* Checklist preview card (read-only display in task overview) */
.checklist-preview-card {
background: var(--card-background-color, #fff);
@@ -27,6 +27,8 @@ export interface HistoryContext {
/** v2.20 (#83): unit + delta-vs-previous for reading-task entries. */
readingUnit?: string | null;
readingDelta?: (entry: HistoryEntry) => number | null;
/** #139: phase id → display name, for entries stamped with a phase_id. */
phaseNames?: Record<string, string>;
}
// Every HistoryEntryType is filterable (drift audit 2026-08: the repair-flow
@@ -96,6 +98,7 @@ export function renderHistoryEntry(entry: HistoryEntry, ctx: HistoryContext) {
<div class="history-content">
<div class="history-row">
<strong>${t(entry.type, L)}</strong>
${entry.phase_id ? html`<span class="history-phase-badge">${ctx.phaseNames?.[entry.phase_id] || entry.phase_id}</span>` : nothing}
${entry.auto ? html`<span class="history-auto-badge">${t("history_auto", L)}</span>` : nothing}
${editable
? html`<button class="history-edit-btn"
@@ -22,6 +22,11 @@ export interface SparklineContext {
lang: string;
detailStatsData: Map<string, StatisticsPoint[]>;
hasStatsService: boolean;
/** #141: entities whose series is recorder STATE HISTORY (no long-term
* statistics exist — binary sensors etc.). Changes the footnote and
* suppresses the trigger_current_value tail point (for a state_change
* trigger that is the change COUNTER, not the plotted state). */
historyFallbackIds?: Set<string>;
isCounterEntity: (tc: TriggerConfig) => boolean;
rangeDays: number;
setRangeDays: (days: number) => void;
@@ -252,7 +257,11 @@ function rawStatsPoints(task: MaintenanceTask, ctx: SparklineContext): ChartPoin
}
}
}
if (task.trigger_current_value != null) {
// #141: a history-fallback series already runs to "now" and plots the
// STATE — appending trigger_current_value (a change counter on
// state_change triggers) would graft a wrong-dimension point onto it.
const fromHistory = !!entityId && !!ctx.historyFallbackIds?.has(entityId) && statsPoints.length >= 2;
if (task.trigger_current_value != null && !fromHistory) {
points.push({ ts: Date.now(), val: task.trigger_current_value });
}
points.sort((a, b) => a.ts - b.ts);
@@ -364,11 +373,23 @@ function renderChart(task: MaintenanceTask, unit: string, ctx: SparklineContext)
@range-change=${(e: CustomEvent<{ days: number }>) => ctx.setRangeDays(e.detail.days)}
@outlier-toggle=${(e: CustomEvent<{ hide: boolean }>) => ctx.setHideOutliers(e.detail.hide)}
></maintenance-trigger-chart>
${statsFetchedEmpty && !loading
? html`<div class="chart-note">
${(() => {
if (loading) return nothing;
// #141: the series came from recorder state history (binary sensors
// never have long-term statistics) — say so, incl. the shorter window.
if (entityId && ctx.historyFallbackIds?.has(entityId) && !statsFetchedEmpty) {
return html`<div class="chart-note">
<ha-icon icon="mdi:information-outline"></ha-icon>
${t("chart_history_fallback", ctx.lang)}
</div>`;
}
if (statsFetchedEmpty) {
return html`<div class="chart-note">
<ha-icon icon="mdi:information-outline"></ha-icon>
${t("chart_no_stats", ctx.lang)}
</div>`
: nothing}
</div>`;
}
return nothing;
})()}
`;
}
@@ -21,6 +21,7 @@ import { renderSeasonalCardCompact, renderSeasonalCardExpanded } from "./seasona
import { renderCostDurationCard } from "./charts";
import { renderDaysProgress } from "./progress";
import { renderHistoryFilters, renderHistoryList, type HistoryContext } from "./history";
import { clampPhaseCursor, effectivePhase, hasPhases } from "../helpers/phases";
import "../components/task-documents";
export interface TaskDetailContext {
@@ -39,6 +40,8 @@ export interface TaskDetailContext {
openManualDoc: (doc: ManualDocRef) => void;
/** #73: persist one checklist tick (panel sends the full state via WS). */
setChecklistItem: (item: string, done: boolean) => void;
/** #139: re-point the phase cursor (operator repair / mid-cycle adoption). */
setPhaseCursor: (cursor: number) => void;
isOperator: boolean;
actionLoading: boolean;
moreMenuOpen: boolean;
@@ -190,9 +193,51 @@ function collapsible(key: string, titleKey: string, body: unknown, ctx: TaskDeta
* reloads, prefills the complete dialog) and resets when the task is
* completed or skipped. Only rendered when the Checklists feature is
* enabled and steps are set. */
/** #139: the cycle strip — every step of the phase sequence as a chip, the
* step currently due highlighted, each phase's last completion underneath.
* Operators can re-point the cursor (mis-click repair, mid-cycle adoption). */
function renderPhasesCard(task: MaintenanceTask, ctx: TaskDetailContext) {
if (!hasPhases(task)) return nothing;
const L = ctx.lang;
const seq = task.phase_sequence!;
const cursor = clampPhaseCursor(task.phase_cursor, seq.length);
// Last completion per phase id — history is append-ordered, so walk it
// backwards and keep the first hit.
const lastByPhase = new Map<string, string>();
for (let i = task.history.length - 1; i >= 0; i--) {
const h = task.history[i];
if (h.phase_id && h.type === "completed" && !lastByPhase.has(h.phase_id)) {
lastByPhase.set(h.phase_id, h.timestamp);
}
}
return html`
<div class="phases-card">
<div class="phases-card-header">
<ha-icon icon="mdi:rotate-right"></ha-icon>
<span>${t("phase_sequence_label", L)}</span>
</div>
<div class="phases-strip">
${seq.map((id, i) => {
const name = task.phases?.[id]?.name || id;
const last = lastByPhase.get(id);
return html`
<div class="phase-step ${i === cursor ? "current" : ""}"
title=${i === cursor ? t("phase_current", L) : (ctx.isOperator ? t("phase_set", L) : "")}
@click=${() => { if (ctx.isOperator && i !== cursor) ctx.setPhaseCursor(i); }}>
<span class="phase-step-name">${i + 1}. ${name}</span>
${last ? html`<span class="phase-step-last">${formatDate(last, L)}</span>` : nothing}
</div>
`;
})}
</div>
</div>
`;
}
function renderChecklistCard(task: MaintenanceTask, ctx: TaskDetailContext) {
if (!ctx.features.checklists) return nothing;
const items = task.checklist || [];
// #139: a phased task shows the checklist of the phase currently due.
const items = effectivePhase(task)?.checklist ?? (task.checklist || []);
if (items.length === 0) return nothing;
const L = ctx.lang;
const progress = task.checklist_progress || {};
@@ -439,6 +484,7 @@ export function renderOverviewTab(task: MaintenanceTask, ctx: TaskDetailContext)
</div>
`, ctx)
: nothing}
${renderPhasesCard(task, ctx)}
${renderChecklistCard(task, ctx)}
${renderRecentActivities(task, ctx)}
</div>
@@ -8,11 +8,27 @@ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
const DETAIL_DAYS = 30;
const MINI_DAYS = 14;
/** history/history_during_period rows — compressed keys over WS ({s, lu});
* the uncompressed REST shape is tolerated defensively. */
interface HAHistoryRow {
s?: string;
lu?: number;
state?: string;
last_updated?: string | number;
last_changed?: string | number;
}
export class StatisticsService {
private _hass: HomeAssistant;
private _cache = new Map<string, EntityStatisticsCache>();
private _pending = new Map<string, Promise<StatisticsPoint[]>>();
/** #141: entities whose DETAIL series came from recorder state history
* instead of long-term statistics (binary sensors and other entities
* without a state_class never have statistics) — the chart footnote
* tells the two apart. */
public readonly historyFallbackIds = new Set<string>();
constructor(hass: HomeAssistant) {
this._hass = hass;
}
@@ -141,7 +157,23 @@ export class StatisticsService {
})) as Record<string, HAStatisticsRow[]>;
const rows = result[entityId] || [];
const points = this._normalizeRows(rows, isCounter);
let points = this._normalizeRows(rows, isCounter);
// #141: entities without long-term statistics (binary sensors, sensors
// without a state_class) always come back empty here — fall back to
// recorder STATE HISTORY (typically ~10 days) so a problem sensor's
// on/off timeline draws as a 0/1 step line instead of a flat value.
if (points.length < 2) {
const historyPoints = await this._fetchHistoryFallback(entityId, startTime);
if (historyPoints.length >= 2) {
points = historyPoints;
this.historyFallbackIds.add(entityId);
} else {
this.historyFallbackIds.delete(entityId);
}
} else {
this.historyFallbackIds.delete(entityId);
}
this._cache.set(cacheKey, {
entityId,
@@ -157,6 +189,60 @@ export class StatisticsService {
}
}
/** Recorder state history as chart points. Binary states map to 0/1, and
* every value CHANGE gets a doubled point ((t, old), (t, new)) so the
* line renderer draws a step instead of a slope between samples. */
private async _fetchHistoryFallback(entityId: string, startTime: string): Promise<StatisticsPoint[]> {
try {
const result = (await this._hass.connection.sendMessagePromise({
type: "history/history_during_period",
start_time: startTime,
end_time: new Date().toISOString(),
entity_ids: [entityId],
minimal_response: true,
no_attributes: true,
})) as Record<string, HAHistoryRow[]>;
let rows = result?.[entityId] || [];
// A chatty numeric entity can return thousands of rows — stride-sample
// BEFORE the step expansion (the expansion preserves the edges that
// survive sampling).
if (rows.length > 1000) {
const stride = Math.ceil(rows.length / 500);
rows = rows.filter((_, i) => i % stride === 0 || i === rows.length - 1);
}
const points: StatisticsPoint[] = [];
let prev: number | null = null;
for (const row of rows) {
const state = row.s ?? row.state;
if (state == null || state === "unknown" || state === "unavailable") continue;
let val: number;
if (state === "on" || state === "open" || state === "true") val = 1;
else if (state === "off" || state === "closed" || state === "false") val = 0;
else {
val = parseFloat(state);
if (!Number.isFinite(val)) continue;
}
const raw = row.lu ?? row.last_updated ?? row.last_changed;
const ts = typeof raw === "number" ? raw * 1000 : raw != null ? Date.parse(raw) : NaN;
if (!Number.isFinite(ts)) continue;
if (prev != null && prev !== val) points.push({ ts, val: prev });
points.push({ ts, val });
prev = val;
}
points.sort((a, b) => a.ts - b.ts);
// Extend the last known state to "now" so the line reaches the right
// edge (the caller must NOT append trigger_current_value here — for a
// state_change trigger that is the change COUNTER, not the state).
if (points.length && prev != null) points.push({ ts: Date.now(), val: prev });
return points;
} catch (err) {
console.warn(`[maintenance-supporter] History fallback failed for ${entityId}:`, err);
return [];
}
}
private async _fetchBatch(
entityIds: string[],
period: "hour" | "day",
@@ -604,6 +604,21 @@ export const sharedStyles = css`
color: var(--secondary-text-color);
white-space: nowrap;
}
/* #139 — names the cycle phase a completion recorded. Shares the auto-badge
look; carries the left-pinning margin itself unless the auto badge (which
already has it) follows. */
.history-phase-badge {
margin-right: auto;
font-size: 11px;
padding: 1px 8px;
border-radius: 10px;
background: var(--secondary-background-color);
color: var(--secondary-text-color);
white-space: nowrap;
}
.history-phase-badge:has(+ .history-auto-badge) {
margin-right: 0;
}
.history-edit-btn {
background: transparent;
color: var(--secondary-text-color);
@@ -103,6 +103,9 @@ export interface HistoryEntry {
/** v2.37: completion recorded by the system itself (trigger recovered),
* not performed by a user in the UI. */
auto?: boolean;
/** #139: which cycle phase this LATEST completion recorded (backfills
* carry none — they never advanced the cursor). */
phase_id?: string | null;
}
export interface AdaptiveConfig {
@@ -269,6 +272,22 @@ export interface MaintenanceTask {
consumes_parts?: TaskPartLink[] | null;
/** Present on an auto-created "buy" reminder: the owning part. */
part_ref?: { part_id: string } | null;
/** Task phases (#139): cyclic content rotation on one shared cadence. */
phases?: Record<string, TaskPhaseDef> | null;
phase_sequence?: string[] | null;
phase_cursor?: number;
/** Backend-resolved phase currently due (null for phase-less tasks). */
current_phase?: { id: string; name: string; index: number; count: number } | null;
}
/** One phase definition (#139). A set field OVERRIDES the task-level field
* for completions performed while this phase is due; unset falls through. */
export interface TaskPhaseDef {
name: string;
notes?: string;
checklist?: string[];
consumes_parts?: TaskPartLink[];
required_completion_fields?: string[];
}
/** One entry of a task's `consumes_parts`.
@@ -889,5 +889,14 @@
"service_record_entries": "záznamů",
"completed_by": "Dokončil",
"date_from": "Od",
"date_to": "Do"
"date_to": "Do",
"phases_section": "Fáze cyklu (volitelné)",
"phases_hint": "Různé práce ve stejném rytmu — každé dokončení posune na další krok (např. malý, malý, velký servis).",
"phase_add": "Přidat fázi",
"phase_name": "Název fáze",
"phase_sequence_label": "Pořadí cyklu",
"phase_sequence_add_step": "Přidat krok",
"phase_current": "Aktuální fáze",
"phase_set": "Nastavit jako aktuální",
"chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "poster",
"completed_by": "Udført af",
"date_from": "Fra",
"date_to": "Til"
"date_to": "Til",
"phases_section": "Cyklusfaser (valgfrit)",
"phases_hint": "Forskelligt arbejde i samme rytme — hver udførelse går videre til næste trin (f.eks. lille, lille, stor service).",
"phase_add": "Tilføj fase",
"phase_name": "Fasenavn",
"phase_sequence_label": "Cyklusrækkefølge",
"phase_sequence_add_step": "Tilføj trin",
"phase_current": "Aktuel fase",
"phase_set": "Sæt som aktuel",
"chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "Einträge",
"completed_by": "Erledigt von",
"date_from": "Von",
"date_to": "Bis"
"date_to": "Bis",
"phases_section": "Zyklus-Phasen (optional)",
"phases_hint": "Unterschiedliche Arbeiten im gleichen Takt — jede Erledigung schaltet zum nächsten Schritt (z. B. klein, klein, groß).",
"phase_add": "Phase hinzufügen",
"phase_name": "Phasenname",
"phase_sequence_label": "Zyklus-Reihenfolge",
"phase_sequence_add_step": "Schritt anhängen",
"phase_current": "Aktuelle Phase",
"phase_set": "Als aktuell setzen",
"chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "entries",
"completed_by": "Completed by",
"date_from": "From",
"date_to": "To"
"date_to": "To",
"phases_section": "Cycle phases (optional)",
"phases_hint": "Different work on one shared schedule — each completion moves to the next step (e.g. small service, small service, big service).",
"phase_add": "Add phase",
"phase_name": "Phase name",
"phase_sequence_label": "Cycle order",
"phase_sequence_add_step": "Add step",
"phase_current": "Current phase",
"phase_set": "Set as current",
"chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "entradas",
"completed_by": "Realizado por",
"date_from": "Desde",
"date_to": "Hasta"
"date_to": "Hasta",
"phases_section": "Fases del ciclo (opcional)",
"phases_hint": "Trabajos distintos con un mismo ritmo: cada finalización pasa al siguiente paso (p. ej. pequeño, pequeño, grande).",
"phase_add": "Añadir fase",
"phase_name": "Nombre de la fase",
"phase_sequence_label": "Orden del ciclo",
"phase_sequence_add_step": "Añadir paso",
"phase_current": "Fase actual",
"phase_set": "Establecer como actual",
"chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "merkintää",
"completed_by": "Suorittanut",
"date_from": "Alkaen",
"date_to": "Asti"
"date_to": "Asti",
"phases_section": "Syklin vaiheet (valinnainen)",
"phases_hint": "Eri työt samassa tahdissa — jokainen suoritus siirtyy seuraavaan vaiheeseen (esim. pieni, pieni, iso huolto).",
"phase_add": "Lisää vaihe",
"phase_name": "Vaiheen nimi",
"phase_sequence_label": "Syklin järjestys",
"phase_sequence_add_step": "Lisää askel",
"phase_current": "Nykyinen vaihe",
"phase_set": "Aseta nykyiseksi",
"chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "entrées",
"completed_by": "Réalisé par",
"date_from": "Du",
"date_to": "Au"
"date_to": "Au",
"phases_section": "Phases du cycle (optionnel)",
"phases_hint": "Des travaux différents sur un même rythme — chaque réalisation passe à l'étape suivante (p. ex. petit, petit, grand entretien).",
"phase_add": "Ajouter une phase",
"phase_name": "Nom de la phase",
"phase_sequence_label": "Ordre du cycle",
"phase_sequence_add_step": "Ajouter une étape",
"phase_current": "Phase actuelle",
"phase_set": "Définir comme actuelle",
"chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "प्रविष्टियाँ",
"completed_by": "द्वारा पूर्ण",
"date_from": "से",
"date_to": "तक"
"date_to": "तक",
"phases_section": "चक्र चरण (वैकल्पिक)",
"phases_hint": "एक ही लय में अलग-अलग कार्य — प्रत्येक पूर्णता अगले चरण पर ले जाती है (जैसे छोटा, छोटा, बड़ा)।",
"phase_add": "चरण जोड़ें",
"phase_name": "चरण का नाम",
"phase_sequence_label": "चक्र क्रम",
"phase_sequence_add_step": "कदम जोड़ें",
"phase_current": "वर्तमान चरण",
"phase_set": "वर्तमान बनाएँ",
"chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "bejegyzés",
"completed_by": "Elvégezte",
"date_from": "Ettől",
"date_to": "Eddig"
"date_to": "Eddig",
"phases_section": "Ciklusfázisok (opcionális)",
"phases_hint": "Különböző munkák azonos ütemben — minden elvégzés a következő lépésre vált (pl. kis, kis, nagy szerviz).",
"phase_add": "Fázis hozzáadása",
"phase_name": "Fázis neve",
"phase_sequence_label": "Ciklus sorrendje",
"phase_sequence_add_step": "Lépés hozzáadása",
"phase_current": "Aktuális fázis",
"phase_set": "Beállítás aktuálisként",
"chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "voci",
"completed_by": "Completato da",
"date_from": "Dal",
"date_to": "Al"
"date_to": "Al",
"phases_section": "Fasi del ciclo (opzionale)",
"phases_hint": "Lavori diversi sullo stesso ritmo — ogni completamento passa al passo successivo (es. piccolo, piccolo, grande).",
"phase_add": "Aggiungi fase",
"phase_name": "Nome della fase",
"phase_sequence_label": "Ordine del ciclo",
"phase_sequence_add_step": "Aggiungi passo",
"phase_current": "Fase attuale",
"phase_set": "Imposta come attuale",
"chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "件",
"completed_by": "実施者",
"date_from": "開始",
"date_to": "終了"
"date_to": "終了",
"phases_section": "サイクルフェーズ(任意)",
"phases_hint": "同じ周期で異なる作業を行います。完了するたびに次のステップへ進みます(例:小・小・大整備)。",
"phase_add": "フェーズを追加",
"phase_name": "フェーズ名",
"phase_sequence_label": "サイクルの順序",
"phase_sequence_add_step": "ステップを追加",
"phase_current": "現在のフェーズ",
"phase_set": "現在に設定",
"chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "건",
"completed_by": "수행자",
"date_from": "시작",
"date_to": "종료"
"date_to": "종료",
"phases_section": "주기 단계(선택)",
"phases_hint": "같은 주기에 다른 작업을 수행합니다. 완료할 때마다 다음 단계로 넘어갑니다(예: 소·소·대 정비).",
"phase_add": "단계 추가",
"phase_name": "단계 이름",
"phase_sequence_label": "주기 순서",
"phase_sequence_add_step": "스텝 추가",
"phase_current": "현재 단계",
"phase_set": "현재로 설정",
"chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "oppføringer",
"completed_by": "Utført av",
"date_from": "Fra",
"date_to": "Til"
"date_to": "Til",
"phases_section": "Syklusfaser (valgfritt)",
"phases_hint": "Ulikt arbeid i samme rytme — hver fullføring går videre til neste trinn (f.eks. liten, liten, stor service).",
"phase_add": "Legg til fase",
"phase_name": "Fasenavn",
"phase_sequence_label": "Syklusrekkefølge",
"phase_sequence_add_step": "Legg til trinn",
"phase_current": "Gjeldende fase",
"phase_set": "Sett som gjeldende",
"chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "items",
"completed_by": "Voltooid door",
"date_from": "Van",
"date_to": "Tot"
"date_to": "Tot",
"phases_section": "Cyclusfasen (optioneel)",
"phases_hint": "Verschillend werk op één ritme — elke afronding schuift naar de volgende stap (bijv. klein, klein, groot).",
"phase_add": "Fase toevoegen",
"phase_name": "Fasenaam",
"phase_sequence_label": "Cyclusvolgorde",
"phase_sequence_add_step": "Stap toevoegen",
"phase_current": "Huidige fase",
"phase_set": "Instellen als huidige",
"chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "wpisów",
"completed_by": "Wykonane przez",
"date_from": "Od",
"date_to": "Do"
"date_to": "Do",
"phases_section": "Fazy cyklu (opcjonalnie)",
"phases_hint": "Różne prace w jednym rytmie — każde ukończenie przechodzi do następnego kroku (np. mały, mały, duży).",
"phase_add": "Dodaj fazę",
"phase_name": "Nazwa fazy",
"phase_sequence_label": "Kolejność cyklu",
"phase_sequence_add_step": "Dodaj krok",
"phase_current": "Bieżąca faza",
"phase_set": "Ustaw jako bieżącą",
"chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "registros",
"completed_by": "Concluído por",
"date_from": "De",
"date_to": "Até"
"date_to": "Até",
"phases_section": "Fases do ciclo (opcional)",
"phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o próximo passo (ex.: pequeno, pequeno, grande).",
"phase_add": "Adicionar fase",
"phase_name": "Nome da fase",
"phase_sequence_label": "Ordem do ciclo",
"phase_sequence_add_step": "Adicionar passo",
"phase_current": "Fase atual",
"phase_set": "Definir como atual",
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "registos",
"completed_by": "Concluído por",
"date_from": "De",
"date_to": "Até"
"date_to": "Até",
"phases_section": "Fases do ciclo (opcional)",
"phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o passo seguinte (ex.: pequeno, pequeno, grande).",
"phase_add": "Adicionar fase",
"phase_name": "Nome da fase",
"phase_sequence_label": "Ordem do ciclo",
"phase_sequence_add_step": "Adicionar passo",
"phase_current": "Fase atual",
"phase_set": "Definir como atual",
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "записей",
"completed_by": "Выполнил",
"date_from": "С",
"date_to": "По"
"date_to": "По",
"phases_section": "Фазы цикла (необязательно)",
"phases_hint": "Разные работы в одном ритме — каждое выполнение переходит к следующему шагу (напр., малое, малое, большое ТО).",
"phase_add": "Добавить фазу",
"phase_name": "Название фазы",
"phase_sequence_label": "Порядок цикла",
"phase_sequence_add_step": "Добавить шаг",
"phase_current": "Текущая фаза",
"phase_set": "Сделать текущей",
"chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "poster",
"completed_by": "Utförd av",
"date_from": "Från",
"date_to": "Till"
"date_to": "Till",
"phases_section": "Cykelfaser (valfritt)",
"phases_hint": "Olika arbeten i samma rytm — varje slutförande går vidare till nästa steg (t.ex. liten, liten, stor service).",
"phase_add": "Lägg till fas",
"phase_name": "Fasnamn",
"phase_sequence_label": "Cykelordning",
"phase_sequence_add_step": "Lägg till steg",
"phase_current": "Aktuell fas",
"phase_set": "Ange som aktuell",
"chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "kayıt",
"completed_by": "Tamamlayan",
"date_from": "Başlangıç",
"date_to": "Bitiş"
"date_to": "Bitiş",
"phases_section": "Döngü aşamaları (isteğe bağlı)",
"phases_hint": "Aynı ritimde farklı işler — her tamamlama bir sonraki adıma geçer (örn. küçük, küçük, büyük bakım).",
"phase_add": "Aşama ekle",
"phase_name": "Aşama adı",
"phase_sequence_label": "Döngü sırası",
"phase_sequence_add_step": "Adım ekle",
"phase_current": "Geçerli aşama",
"phase_set": "Geçerli olarak ayarla",
"chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "записів",
"completed_by": "Виконав",
"date_from": "Від",
"date_to": "До"
"date_to": "До",
"phases_section": "Фази циклу (необов'язково)",
"phases_hint": "Різні роботи в одному ритмі — кожне виконання переходить до наступного кроку (напр., мале, мале, велике ТО).",
"phase_add": "Додати фазу",
"phase_name": "Назва фази",
"phase_sequence_label": "Порядок циклу",
"phase_sequence_add_step": "Додати крок",
"phase_current": "Поточна фаза",
"phase_set": "Зробити поточною",
"chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)"
}
@@ -889,5 +889,14 @@
"service_record_entries": "条记录",
"completed_by": "完成人",
"date_from": "从",
"date_to": "至"
"date_to": "至",
"phases_section": "循环阶段(可选)",
"phases_hint": "同一节奏下的不同工作——每次完成后进入下一步(例如:小保养、小保养、大保养)。",
"phase_add": "添加阶段",
"phase_name": "阶段名称",
"phase_sequence_label": "循环顺序",
"phase_sequence_add_step": "添加步骤",
"phase_current": "当前阶段",
"phase_set": "设为当前",
"chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)"
}
File diff suppressed because it is too large. Load diff
@@ -1,9 +1,9 @@
/*! maintenance_supporter frontend 2.64.0 */
var Tt=Object.defineProperty;var Xi=Object.getOwnPropertyDescriptor;var w=(a,s,e)=>()=>{if(e)throw e[0];try{return a&&(s=a(a=0)),s}catch(t){throw e=[t],t}};var es=(a,s)=>{for(var e in s)Tt(a,e,{get:s[e],enumerable:!0})};var c=(a,s,e,t)=>{for(var i=t>1?void 0:t?Xi(s,e):s,n=a.length-1,l;n>=0;n--)(l=a[n])&&(i=(t?l(s,e,i):l(i))||i);return t&&i&&Tt(s,e,i),i};var Pe,je,Ye,Ct,me,It,A,Lt,Qe,Je=w(()=>{Pe=globalThis,je=Pe.ShadowRoot&&(Pe.ShadyCSS===void 0||Pe.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Ye=Symbol(),Ct=new WeakMap,me=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Ye)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=s,this.t=e}get styleSheet(){let s=this.o,e=this.t;if(je&&s===void 0){let t=e!==void 0&&e.length===1;t&&(s=Ct.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&Ct.set(e,s))}return s}toString(){return this.cssText}},It=a=>new me(typeof a=="string"?a:a+"",void 0,Ye),A=(a,...s)=>{let e=a.length===1?a[0]:s.reduce((t,i,n)=>t+(l=>{if(l._$cssResult$===!0)return l.cssText;if(typeof l=="number")return l;throw Error("Value passed to 'css' function must be a 'css' function result: "+l+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new me(e,a,Ye)},Lt=(a,s)=>{if(je)a.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of s){let t=document.createElement("style"),i=Pe.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Qe=je?a=>a:a=>a instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return It(e)})(a):a});var ts,is,ss,rs,as,ns,Re,Pt,os,ls,fe,ve,Ne,jt,Y,be=w(()=>{Je();Je();({is:ts,defineProperty:is,getOwnPropertyDescriptor:ss,getOwnPropertyNames:rs,getOwnPropertySymbols:as,getPrototypeOf:ns}=Object),Re=globalThis,Pt=Re.trustedTypes,os=Pt?Pt.emptyScript:"",ls=Re.reactiveElementPolyfillSupport,fe=(a,s)=>a,ve={toAttribute(a,s){switch(s){case Boolean:a=a?os:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,s){let e=a;switch(s){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},Ne=(a,s)=>!ts(a,s),jt={attribute:!0,type:String,converter:ve,reflect:!1,useDefault:!1,hasChanged:Ne};Symbol.metadata??=Symbol("metadata"),Re.litPropertyMetadata??=new WeakMap;Y=class extends HTMLElement{static addInitializer(s){this._$Ei(),(this.l??=[]).push(s)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(s,e=jt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(s)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(s,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(s,t,e);i!==void 0&&is(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=ss(this.prototype,s)??{get(){return this[e]},set(l){this[e]=l}};return{get:i,set(l){let d=i?.call(this);n?.call(this,l),this.requestUpdate(s,d,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??jt}static _$Ei(){if(this.hasOwnProperty(fe("elementProperties")))return;let s=ns(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(fe("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(fe("properties"))){let e=this.properties,t=[...rs(e),...as(e)];for(let i of t)this.createProperty(i,e[i])}let s=this[Symbol.metadata];if(s!==null){let e=litPropertyMetadata.get(s);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(s){let e=[];if(Array.isArray(s)){let t=new Set(s.flat(1/0).reverse());for(let i of t)e.unshift(Qe(i))}else s!==void 0&&e.push(Qe(s));return e}static _$Eu(s,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof s=="string"?s.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(s=>this.enableUpdating=s),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(s=>s(this))}addController(s){(this._$EO??=new Set).add(s),this.renderRoot!==void 0&&this.isConnected&&s.hostConnected?.()}removeController(s){this._$EO?.delete(s)}_$E_(){let s=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(s.set(t,this[t]),delete this[t]);s.size>0&&(this._$Ep=s)}createRenderRoot(){let s=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Lt(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=thiLine truncated
\f\r]`,ye=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ht=/-->/g,qt=/>/g,se=RegExp(`>|${Ze}(?:([^\\s"'>=/]+)(${Ze}*=${Ze}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`,"g"),Ot=/'/g,Mt=/"/g,Ut=/^(?:script|style|textarea|title)$/i,nt=a=>(s,...e)=>({_$litType$:a,strings:s,values:e}),o=nt(1),pe=nt(2),gr=nt(3),ne=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),Dt=new WeakMap,re=ae.createTreeWalker(ae,129);ps=(a,s)=>{let e=a.length-1,t=[],i,n=s===2?"<svg>":s===3?"<math>":"",l=ye;for(let d=0;d<e;d++){let p=a[d],_,v,m=-1,y=0;for(;y<p.length&&(l.lastIndex=y,v=l.exec(p),v!==null);)y=l.lastIndex,l===ye?v[1]==="!--"?l=Ht:v[1]!==void 0?l=qt:v[2]!==void 0?(Ut.test(v[2])&&(i=RegExp("</"+v[2],"g")),l=se):v[3]!==void 0&&(l=se):l===se?v[0]===">"?(l=i??ye,m=-1):v[1]===void 0?m=-2:(m=l.lastIndex-v[2].length,_=v[1],l=v[3]===void 0?se:v[3]==='"'?Mt:Ot):l===Mt||l===Ot?l=se:l===Ht||l===qt?l=ye:(l=se,i=void 0);let x=l===se&&a[d+1].startsWith("/>")?" ":"";n+=l===ye?p+cs:m>=0?(t.push(_),p.slice(0,m)+zt+p.slice(m)+te+x):p+te+(m===-2?d:x)}return[Bt(a,n+(a[e]||"<?>")+(s===2?"</svg>":s===3?"</math>":"")),t]},$e=class a{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,l=0,d=s.length-1,p=this.parts,[_,v]=ps(s,e);if(this.el=a.createElement(_,t),re.currentNode=this.el.content,e===2||e===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes)}for(;(i=re.nextNode())!==null&&p.length<d;){if(i.nodeType===1){if(i.hasAttributes())for(let m of i.getAttributeNames())if(m.endsWith(zt)){let y=v[l++],x=i.getAttribute(m).split(te),f=/([.?@])?(.*)/.exec(y);p.push({type:1,index:n,name:f[2],strings:x,ctor:f[1]==="."?et:f[1]==="?"?tt:f[1]==="@"?it:de}),i.removeAttribute(m)}else m.startsWith(te)&&(p.push({type:6,index:n}),i.removeAttribute(m));if(Ut.test(i.tagName)){let m=i.textContent.split(te),y=m.length-1;if(y>0){i.textContent=He?He.emptyScript:"";for(let x=0;x<y;x++)i.append(m[x],xe()),re.nextNode(),p.push({type:2,index:++n});i.append(m[y],xe())}}}else if(i.nodeType===8)if(i.data===Ft)p.push({type:2,index:n});else{let m=-1;for(;(m=i.data.indexOf(te,m+1))!==-1;)p.push({type:7,index:n}),m+=te.length-1}n++}}static createElement(s,e){let t=ae.createElement("template");return t.innerHTML=s,t}};Xe=class{constructor(s,e){this._$AV=[],this._$AN=void 0,this._$AD=s,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(s){let{el:{content:e},parts:t}=this._$AD,i=(s?.creationScope??ae).importNode(e,!0);re.currentNode=i;let n=re.nextNode(),l=0,d=0,p=t[0];for(;p!==void 0;){if(l===p.index){let _;p.type===2?_=new ke(n,n.nextSibling,this,s):p.type===1?_=new p.ctor(n,p.name,p.strings,this,s):p.type===6&&(_=new st(n,this,s)),this._$AV.push(_),p=t[++d]}l!==p?.index&&(n=re.nextNode(),l++)}return re.currentNode=ae,i}p(s){let e=0;for(let t of this._$AV)t!==void 0&&(t.strings!==void 0?(t._$AI(s,t,e),e+=t.strings.length-2):t._$AI(s[e])),e++}},ke=class a{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(s,e,t,i){this.type=2,this._$AH=u,this._$AN=void 0,this._$AA=s,this._$AB=e,this._$AM=t,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let s=this._$AA.parentNode,e=this._$AM;return e!==void 0&&s?.nodeType===11&&(s=e.parentNode),s}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(s,e=this){s=ce(this,s,e),we(s)?s===u||s==null||s===""?(this._$AH!==u&&this._$AR(),this._$AH=u):s!==this._$AH&&s!==ne&&this._(s):s._$litType$!==void 0?this.$(s):s.nodeType!==void 0?this.T(s):ds(s)?this.k(s):this._(s)}O(s){return this._$AA.parentNode.insertBefore(s,this._$AB)}T(s){this._$AH!==s&&(this._$AR(),this._$AH=this.O(s))}_(s){this._$AH!==u&&we(this._$AH)?this._$AA.nextSibling.data=s:this.T(ae.createTextNode(s)),this._$AH=s}$(s){let{values:e,_$litType$:t}=s,i=typeof t=="number"?this._$AC(s):(t.el===void 0&&(t.el=$e.createElement(Bt(t.h,t.h[0]),this.options)),t);if(this._$AH?._$AD===i)this._$AH.p(e);else{let n=new Xe(i,this),l=n.u(this.options);n.p(e),this.T(l),this._$AH=n}}_$AC(s){let e=Dt.get(s.strings);return e===void 0&&Dt.set(s.strings,e=new $e(s)),e}k(s){at(this._$AH)||(this._$AH=[],this._$AR());let e=this._$AH,t,i=0;for(let n of s)i===e.length?e.push(t=new a(this.O(xe()),this.O(xe()),this,this.options)):t=e[i],t._$AI(n),i++;i<e.length&&(this._$AR(t&&t._$AB.nextSibling,i),e.length=i)}_$AR(s=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);s!==this._$AB;){let t=Rt(s).nextSibling;Rt(s).remove(),s=t}}setConnected(s){this._$AM===void 0&&(this._$Cv=s,this._$AP?.(s))}},de=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(s,e,t,i,n){this.type=1,this._$AH=u,this._$AN=void 0,this.element=s,this.name=e,this._$AM=i,this.options=n,t.length>2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=u}_$AI(s,e=this,t,i){let n=this.strings,l=!1;if(n===void 0)s=ce(this,s,e,0),l=!we(s)||s!==this._$AH&&s!==ne,l&&(this._$AH=s);else{let d=s,p,_;for(s=n[0],p=0;p<n.length-1;p++)_=ce(this,d[t+p],e,p),_===ne&&(_=this._$AH[p]),l||=!we(_)||_!==this._$AH[p],_===u?s=u:s!==u&&(s+=(_??"")+n[p+1]),this._$AH[p]=_}l&&!i&&this.j(s)}j(s){s===u?this.element.removeAttribute(this.naLine truncated
/*! maintenance_supporter frontend 2.65.0 */
var Pt=Object.defineProperty;var is=Object.getOwnPropertyDescriptor;var w=(a,s,e)=>()=>{if(e)throw e[0];try{return a&&(s=a(a=0)),s}catch(t){throw e=[t],t}};var ss=(a,s)=>{for(var e in s)Pt(a,e,{get:s[e],enumerable:!0})};var c=(a,s,e,t)=>{for(var i=t>1?void 0:t?is(s,e):s,n=a.length-1,l;n>=0;n--)(l=a[n])&&(i=(t?l(s,e,i):l(i))||i);return t&&i&&Pt(s,e,i),i};var je,Ne,Je,Lt,fe,Rt,A,jt,Ze,Xe=w(()=>{je=globalThis,Ne=je.ShadowRoot&&(je.ShadyCSS===void 0||je.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Je=Symbol(),Lt=new WeakMap,fe=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Je)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=s,this.t=e}get styleSheet(){let s=this.o,e=this.t;if(Ne&&s===void 0){let t=e!==void 0&&e.length===1;t&&(s=Lt.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&Lt.set(e,s))}return s}toString(){return this.cssText}},Rt=a=>new fe(typeof a=="string"?a:a+"",void 0,Je),A=(a,...s)=>{let e=a.length===1?a[0]:s.reduce((t,i,n)=>t+(l=>{if(l._$cssResult$===!0)return l.cssText;if(typeof l=="number")return l;throw Error("Value passed to 'css' function must be a 'css' function result: "+l+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new fe(e,a,Je)},jt=(a,s)=>{if(Ne)a.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of s){let t=document.createElement("style"),i=je.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Ze=Ne?a=>a:a=>a instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return Rt(e)})(a):a});var rs,as,ns,os,ls,cs,qe,Nt,ds,ps,ve,be,He,qt,Y,ye=w(()=>{Xe();Xe();({is:rs,defineProperty:as,getOwnPropertyDescriptor:ns,getOwnPropertyNames:os,getOwnPropertySymbols:ls,getPrototypeOf:cs}=Object),qe=globalThis,Nt=qe.trustedTypes,ds=Nt?Nt.emptyScript:"",ps=qe.reactiveElementPolyfillSupport,ve=(a,s)=>a,be={toAttribute(a,s){switch(s){case Boolean:a=a?ds:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,s){let e=a;switch(s){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},He=(a,s)=>!rs(a,s),qt={attribute:!0,type:String,converter:be,reflect:!1,useDefault:!1,hasChanged:He};Symbol.metadata??=Symbol("metadata"),qe.litPropertyMetadata??=new WeakMap;Y=class extends HTMLElement{static addInitializer(s){this._$Ei(),(this.l??=[]).push(s)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(s,e=qt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(s)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(s,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(s,t,e);i!==void 0&&as(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=ns(this.prototype,s)??{get(){return this[e]},set(l){this[e]=l}};return{get:i,set(l){let d=i?.call(this);n?.call(this,l),this.requestUpdate(s,d,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??qt}static _$Ei(){if(this.hasOwnProperty(ve("elementProperties")))return;let s=cs(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(ve("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(ve("properties"))){let e=this.properties,t=[...os(e),...ls(e)];for(let i of t)this.createProperty(i,e[i])}let s=this[Symbol.metadata];if(s!==null){let e=litPropertyMetadata.get(s);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(s){let e=[];if(Array.isArray(s)){let t=new Set(s.flat(1/0).reverse());for(let i of t)e.unshift(Ze(i))}else s!==void 0&&e.push(Ze(s));return e}static _$Eu(s,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof s=="string"?s.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(s=>this.enableUpdating=s),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(s=>s(this))}addController(s){(this._$EO??=new Set).add(s),this.renderRoot!==void 0&&this.isConnected&&s.hostConnected?.()}removeController(s){this._$EO?.delete(s)}_$E_(){let s=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(s.set(t,this[t]),delete this[t]);s.size>0&&(this._$Ep=s)}createRenderRoot(){let s=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return jt(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=thiLine truncated
\f\r]`,xe=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Mt=/-->/g,Dt=/>/g,re=RegExp(`>|${et}(?:([^\\s"'>=/]+)(${et}*=${et}*(?:[^
\f\r"'\`<>=]|("|')|))|$)`,"g"),zt=/'/g,Ft=/"/g,Wt=/^(?:script|style|textarea|title)$/i,lt=a=>(s,...e)=>({_$litType$:a,strings:s,values:e}),o=lt(1),he=lt(2),yr=lt(3),oe=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),Ut=new WeakMap,ae=ne.createTreeWalker(ne,129);_s=(a,s)=>{let e=a.length-1,t=[],i,n=s===2?"<svg>":s===3?"<math>":"",l=xe;for(let d=0;d<e;d++){let p=a[d],_,v,m=-1,b=0;for(;b<p.length&&(l.lastIndex=b,v=l.exec(p),v!==null);)b=l.lastIndex,l===xe?v[1]==="!--"?l=Mt:v[1]!==void 0?l=Dt:v[2]!==void 0?(Wt.test(v[2])&&(i=RegExp("</"+v[2],"g")),l=re):v[3]!==void 0&&(l=re):l===re?v[0]===">"?(l=i??xe,m=-1):v[1]===void 0?m=-2:(m=l.lastIndex-v[2].length,_=v[1],l=v[3]===void 0?re:v[3]==='"'?Ft:zt):l===Ft||l===zt?l=re:l===Mt||l===Dt?l=xe:(l=re,i=void 0);let y=l===re&&a[d+1].startsWith("/>")?" ":"";n+=l===xe?p+hs:m>=0?(t.push(_),p.slice(0,m)+Bt+p.slice(m)+te+y):p+te+(m===-2?d:y)}return[Kt(a,n+(a[e]||"<?>")+(s===2?"</svg>":s===3?"</math>":"")),t]},ke=class a{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,l=0,d=s.length-1,p=this.parts,[_,v]=_s(s,e);if(this.el=a.createElement(_,t),ae.currentNode=this.el.content,e===2||e===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes)}for(;(i=ae.nextNode())!==null&&p.length<d;){if(i.nodeType===1){if(i.hasAttributes())for(let m of i.getAttributeNames())if(m.endsWith(Bt)){let b=v[l++],y=i.getAttribute(m).split(te),f=/([.?@])?(.*)/.exec(b);p.push({type:1,index:n,name:f[2],strings:y,ctor:f[1]==="."?it:f[1]==="?"?st:f[1]==="@"?rt:pe}),i.removeAttribute(m)}else m.startsWith(te)&&(p.push({type:6,index:n}),i.removeAttribute(m));if(Wt.test(i.tagName)){let m=i.textContent.split(te),b=m.length-1;if(b>0){i.textContent=Oe?Oe.emptyScript:"";for(let y=0;y<b;y++)i.append(m[y],we()),ae.nextNode(),p.push({type:2,index:++n});i.append(m[b],we())}}}else if(i.nodeType===8)if(i.data===Vt)p.push({type:2,index:n});else{let m=-1;for(;(m=i.data.indexOf(te,m+1))!==-1;)p.push({type:7,index:n}),m+=te.length-1}n++}}static createElement(s,e){let t=ne.createElement("template");return t.innerHTML=s,t}};tt=class{constructor(s,e){this._$AV=[],this._$AN=void 0,this._$AD=s,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(s){let{el:{content:e},parts:t}=this._$AD,i=(s?.creationScope??ne).importNode(e,!0);ae.currentNode=i;let n=ae.nextNode(),l=0,d=0,p=t[0];for(;p!==void 0;){if(l===p.index){let _;p.type===2?_=new Ee(n,n.nextSibling,this,s):p.type===1?_=new p.ctor(n,p.name,p.strings,this,s):p.type===6&&(_=new at(n,this,s)),this._$AV.push(_),p=t[++d]}l!==p?.index&&(n=ae.nextNode(),l++)}return ae.currentNode=ne,i}p(s){let e=0;for(let t of this._$AV)t!==void 0&&(t.strings!==void 0?(t._$AI(s,t,e),e+=t.strings.length-2):t._$AI(s[e])),e++}},Ee=class a{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(s,e,t,i){this.type=2,this._$AH=u,this._$AN=void 0,this._$AA=s,this._$AB=e,this._$AM=t,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let s=this._$AA.parentNode,e=this._$AM;return e!==void 0&&s?.nodeType===11&&(s=e.parentNode),s}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(s,e=this){s=de(this,s,e),$e(s)?s===u||s==null||s===""?(this._$AH!==u&&this._$AR(),this._$AH=u):s!==this._$AH&&s!==oe&&this._(s):s._$litType$!==void 0?this.$(s):s.nodeType!==void 0?this.T(s):us(s)?this.k(s):this._(s)}O(s){return this._$AA.parentNode.insertBefore(s,this._$AB)}T(s){this._$AH!==s&&(this._$AR(),this._$AH=this.O(s))}_(s){this._$AH!==u&&$e(this._$AH)?this._$AA.nextSibling.data=s:this.T(ne.createTextNode(s)),this._$AH=s}$(s){let{values:e,_$litType$:t}=s,i=typeof t=="number"?this._$AC(s):(t.el===void 0&&(t.el=ke.createElement(Kt(t.h,t.h[0]),this.options)),t);if(this._$AH?._$AD===i)this._$AH.p(e);else{let n=new tt(i,this),l=n.u(this.options);n.p(e),this.T(l),this._$AH=n}}_$AC(s){let e=Ut.get(s.strings);return e===void 0&&Ut.set(s.strings,e=new ke(s)),e}k(s){ot(this._$AH)||(this._$AH=[],this._$AR());let e=this._$AH,t,i=0;for(let n of s)i===e.length?e.push(t=new a(this.O(we()),this.O(we()),this,this.options)):t=e[i],t._$AI(n),i++;i<e.length&&(this._$AR(t&&t._$AB.nextSibling,i),e.length=i)}_$AR(s=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);s!==this._$AB;){let t=Ht(s).nextSibling;Ht(s).remove(),s=t}}setConnected(s){this._$AM===void 0&&(this._$Cv=s,this._$AP?.(s))}},pe=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(s,e,t,i,n){this.type=1,this._$AH=u,this._$AN=void 0,this.element=s,this.name=e,this._$AM=i,this.options=n,t.length>2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=u}_$AI(s,e=this,t,i){let n=this.strings,l=!1;if(n===void 0)s=de(this,s,e,0),l=!$e(s)||s!==this._$AH&&s!==oe,l&&(this._$AH=s);else{let d=s,p,_;for(s=n[0],p=0;p<n.length-1;p++)_=de(this,d[t+p],e,p),_===oe&&(_=this._$AH[p]),l||=!$e(_)||_!==this._$AH[p],_===u?s=u:s!==u&&(s+=(_??"")+n[p+1]),this._$AH[p]=_}l&&!i&&this.j(s)}j(s){s===u?this.element.removeAttribute(this.naLine truncated
Replace seal
Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_future_error:"The completion date cannot be in the future.",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",m Line truncated
Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_future_error:"The completion date cannot be in the future.",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",m Line truncated
.field { display: flex; flex-direction: column; gap: 4px; }
.field-label { font-size: 12px; color: var(--secondary-text-color); }
.field-input {
@@ -14,7 +14,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
font-family: inherit; width: 100%; box-sizing: border-box;
}
.field-input:focus { outline: none; border-color: var(--primary-color); }
`,Fe=A`
`,Be=A`
:host {
--maint-ok-color: var(--success-color, #4caf50);
--maint-due-soon-color: var(--warning-color, #ff9800);
@@ -258,6 +258,21 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
color: var(--secondary-text-color);
white-space: nowrap;
}
/* #139 — names the cycle phase a completion recorded. Shares the auto-badge
look; carries the left-pinning margin itself unless the auto badge (which
already has it) follows. */
.history-phase-badge {
margin-right: auto;
font-size: 11px;
padding: 1px 8px;
border-radius: 10px;
background: var(--secondary-background-color);
color: var(--secondary-text-color);
white-space: nowrap;
}
.history-phase-badge:has(+ .history-auto-badge) {
margin-right: 0;
}
.history-edit-btn {
background: transparent;
color: var(--secondary-text-color);
@@ -1129,13 +1144,14 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.stat-item .stat-label { font-size: 11px; white-space: normal; text-align: center; line-height: 1.2; }
.stat-value { font-size: 20px; }
}
`});function gi(a,s,e){let t=new Blob([a],{type:e}),i=URL.createObjectURL(t),n=document.createElement("a");n.href=i,n.download=s,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(i),6e4)}var ut=w(()=>{"use strict"});function Ue(a){return!!a&&/^https?:\/\//i.test(a)}var _t=w(()=>{"use strict"});var ue,gt=w(()=>{"use strict";ue=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 e=Date.now();if(!s&&this.usersCache&&e-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=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,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}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.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}}});function V(a){return`${a.entry_id??""}\0${a.part_id}`}function Ls(a,s,e,t){let i=!!a.entry_id&&a.entry_id!==s,n=i?a.entry_id:s,l=e.find(v=>v.entry_id===n),d=(l?.parts||[]).find(v=>v.id===a.part_id)||null,p=i&&l?.object?.name||"",_=d?.name||r("shared_part_unknown",t);return{part:d,foreign:i,ownerName:p,label:p?`${_} (${p})`:_}}function Ve(a,s,e,t){let n=(e.find(d=>d.entry_id===s)?.parts||[]).map(d=>({...d})),l=new Set(n.map(d=>V({part_id:d.id})));for(let d of a?.consumes_parts||[]){if(!d.entry_id||d.entry_id===s)continue;let p=V(d);if(l.has(p))continue;l.add(p);let{part:_,ownerName:v}=Ls(d,s,e,t);n.push({id:d.part_id,name:_?.name||r("shared_part_unknown",t),unit:_?.unit,stock:_?.stock??null,storage_location:_?.storage_location,entry_id:d.entry_id,owner_name:v})}return n}var Ie=w(()=>{"use strict";R()});function Rs(a,s){let e=js[a];if(!e)return a;let t=r(e,s);return t&&t!==e?t:a}function Ns(a){let e=a.match(/data\['([^']+)'\]/)?.[1],t;return(t=a.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=a.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=a.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=a.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(a)?{field:e,rule:"required"}:(t=a.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(a)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(a)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function T(a,s,e){if(e=e??r("action_error",s),typeof a=="string")return a;if(typeof a!="object"||a===null)return e;let t=a,i=t.message||t.error?.message||"";if(!i)return e;let n=Ns(i),l=n.field?Rs(n.field,s):"",d=p=>r(p,s).replace("{field}",l).replace("{n}",n.param??"");switch(n.rule){case"too_long":return d("err_too_long");case"too_short":return d("err_too_short");case"value_too_high":return d("err_value_too_high");case"value_too_low":return d("err_value_too_low");case"required":return d("err_required");case"wrong_type":return d("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return d("err_invalid_choice");case"invalid_value":return d("err_invalid_value");default:return i||e}}var js,ie=w(()=>{"use strict";R();js={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",tri Line truncated
`});function vi(a,s,e){let t=new Blob([a],{type:e}),i=URL.createObjectURL(t),n=document.createElement("a");n.href=i,n.download=s,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(i),6e4)}var gt=w(()=>{"use strict"});function Ve(a){return!!a&&/^https?:\/\//i.test(a)}var mt=w(()=>{"use strict"});var _e,ft=w(()=>{"use strict";_e=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 e=Date.now();if(!s&&this.usersCache&&e-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=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,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}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.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}}});function V(a){return`${a.entry_id??""}\0${a.part_id}`}function js(a,s,e,t){let i=!!a.entry_id&&a.entry_id!==s,n=i?a.entry_id:s,l=e.find(v=>v.entry_id===n),d=(l?.parts||[]).find(v=>v.id===a.part_id)||null,p=i&&l?.object?.name||"",_=d?.name||r("shared_part_unknown",t);return{part:d,foreign:i,ownerName:p,label:p?`${_} (${p})`:_}}function Ke(a,s,e,t){let n=(e.find(d=>d.entry_id===s)?.parts||[]).map(d=>({...d})),l=new Set(n.map(d=>V({part_id:d.id})));for(let d of a?.consumes_parts||[]){if(!d.entry_id||d.entry_id===s)continue;let p=V(d);if(l.has(p))continue;l.add(p);let{part:_,ownerName:v}=js(d,s,e,t);n.push({id:d.part_id,name:_?.name||r("shared_part_unknown",t),unit:_?.unit,stock:_?.stock??null,storage_location:_?.storage_location,entry_id:d.entry_id,owner_name:v})}return n}var Pe=w(()=>{"use strict";j()});function Ns(a,s){if(s<=0)return 0;let e=typeof a=="number"&&Number.isFinite(a)?Math.trunc(a):0;return e<0?0:e%s}function qs(a){return!!(a?.phases&&a.phase_sequence&&a.phase_sequence.length>0)}function Le(a){if(!a||!qs(a))return null;let s=a.phase_sequence,e=Ns(a.phase_cursor,s.length),t=s[e],i=a.phases?.[t];return i?{id:t,name:i.name,index:e,count:s.length,notes:i.notes,checklist:i.checklist!==void 0?i.checklist:a.checklist??[],consumesParts:i.consumes_parts!==void 0?i.consumes_parts:a.consumes_parts??[],requiredFields:i.required_completion_fields!==void 0?i.required_completion_fields:a.required_completion_fields??[]}:null}function ie(a){let s=Le(a);return s?`${s.index+1}/${s.count} \xB7 ${s.name}`:""}var vt=w(()=>{"use strict"});function Ms(a,s){let e=Os[a];if(!e)return a;let t=r(e,s);return t&&t!==e?t:a}function Ds(a){let e=a.match(/data\['([^']+)'\]/)?.[1],t;return(t=a.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=a.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=a.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=a.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(a)?{field:e,rule:"required"}:(t=a.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(a)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(a)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function T(a,s,e){if(e=e??r("action_error",s),typeof a=="string")return a;if(typeof a!="object"||a===null)return e;let t=a,i=t.message||t.error?.message||"";if(!i)return e;let n=Ds(i),l=n.field?Ms(n.field,s):"",d=p=>r(p,s).replace("{field}",l).replace("{n}",n.param??"");switch(n.rule){case"too_long":return d("err_too_long");case"too_short":return d("err_too_short");case"value_too_high":return d("err_value_too_high");case"value_too_low":return d("err_value_too_low");case"required":return d("err_required");case"wrong_type":return d("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return d("err_invalid_choice");case"invalid_value":return d("err_invalid_value");default:return i||e}}var Os,se=w(()=>{"use strict";j();Os={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nf Line truncated
type="button"
class="cost-suggestion"
@click=${()=>this._cost=t.toFixed(2)}
>${r("cost_from_parts",e).replace("{amount}",i)}</button>`}_close(){this._open=!1}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en";return o`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div>
${this.phaseLabel?o`<div class="phase-line">${r("phase_current",e)}: ${this.phaseLabel}</div>`:u}
<div class="content">
${this._error?o`<div class="error">${this._error}</div>`:u}
${this.checklist.length>0?o`
@@ -1255,13 +1271,13 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-button
@click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(We[t]??t,e))).join(" \xB7 "):""}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(Ge[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};k.styles=[_i,A`
`}};k.styles=[fi,A`
.req-mark {
color: var(--error-color, #f44336);
margin-left: 2px;
@@ -1285,6 +1301,12 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
font-weight: 500;
padding-bottom: 12px;
}
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-direction: column;
@@ -1426,7 +1448,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
`],c([b({attribute:!1})],k.prototype,"hass",2),c([b()],k.prototype,"entryId",2),c([b()],k.prototype,"taskId",2),c([b()],k.prototype,"taskName",2),c([b()],k.prototype,"lang",2),c([b({type:Array})],k.prototype,"checklist",2),c([b({type:Boolean})],k.prototype,"adaptiveEnabled",2),c([b()],k.prototype,"taskType",2),c([b()],k.prototype,"readingUnit",2),c([b({attribute:!1})],k.prototype,"restockDefault",2),c([b({attribute:!1})],k.prototype,"restockUnitCost",2),c([b()],k.prototype,"currencySymbol",2),c([b({attribute:!1})],k.prototype,"parts",2),c([b({attribute:!1})],k.prototype,"consumesParts",2),c([b({type:Array})],k.prototype,"consumesInfo",2),c([b({type:Array})],k.prototype,"requiredFields",2),c([h()],k.prototype,"_open",2),c([h()],k.prototype,"_notes",2),c([h()],k.prototype,"_cost",2),c([h()],k.prototype,"_duration",2),c([h()],k.prototype,"_loading",2),c([h()],k.prototype,"_error",2),c([h()],k.prototype,"_checklistState",2),c([h()],k.prototype,"_feedback",2),c([h()],k.prototype,"_photoDocId",2),c([h()],k.prototype,"_photoPreview",2),c([h()],k.prototype,"_photoUploading",2),c([h()],k.prototype,"_readingValue",2),c([h()],k.prototype,"_restockQty",2),c([h()],k.prototype,"_completedAt",2),c([h()],k.prototype,"_usedParts",2),c([b({attribute:!1})],k.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",k)});var H,yt=w(()=>{"use strict";P();O();H=class extends S{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return o`
`],c([x({attribute:!1})],k.prototype,"hass",2),c([x()],k.prototype,"entryId",2),c([x()],k.prototype,"taskId",2),c([x()],k.prototype,"taskName",2),c([x()],k.prototype,"lang",2),c([x({type:Array})],k.prototype,"checklist",2),c([x({type:Boolean})],k.prototype,"adaptiveEnabled",2),c([x()],k.prototype,"taskType",2),c([x()],k.prototype,"readingUnit",2),c([x({attribute:!1})],k.prototype,"restockDefault",2),c([x({attribute:!1})],k.prototype,"restockUnitCost",2),c([x()],k.prototype,"currencySymbol",2),c([x({attribute:!1})],k.prototype,"parts",2),c([x({attribute:!1})],k.prototype,"consumesParts",2),c([x({type:Array})],k.prototype,"consumesInfo",2),c([x({type:Array})],k.prototype,"requiredFields",2),c([x()],k.prototype,"phaseLabel",2),c([h()],k.prototype,"_open",2),c([h()],k.prototype,"_notes",2),c([h()],k.prototype,"_cost",2),c([h()],k.prototype,"_duration",2),c([h()],k.prototype,"_loading",2),c([h()],k.prototype,"_error",2),c([h()],k.prototype,"_checklistState",2),c([h()],k.prototype,"_feedback",2),c([h()],k.prototype,"_photoDocId",2),c([h()],k.prototype,"_photoPreview",2),c([h()],k.prototype,"_photoUploading",2),c([h()],k.prototype,"_readingValue",2),c([h()],k.prototype,"_restockQty",2),c([h()],k.prototype,"_completedAt",2),c([h()],k.prototype,"_usedParts",2),c([x({attribute:!1})],k.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",k)});var q,$t=w(()=>{"use strict";L();M();q=class extends S{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return o`
<label class="field">
${this.label?o`<span class="label">${this.label}${this.required?o`<span class="req">*</span>`:u}</span>`:u}
<input
@@ -1444,7 +1466,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
/>
${this.helper?o`<span class="helper">${this.helper}</span>`:u}
</label>
`}};H.styles=A`
`}};q.styles=A`
:host { display: block; }
.field {
display: flex;
@@ -1478,7 +1500,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
color: var(--secondary-text-color);
font-style: italic;
}
`,c([b()],H.prototype,"label",2),c([b()],H.prototype,"value",2),c([b()],H.prototype,"placeholder",2),c([b()],H.prototype,"type",2),c([b({type:Boolean})],H.prototype,"required",2),c([b({type:Boolean})],H.prototype,"disabled",2),c([b()],H.prototype,"step",2),c([b()],H.prototype,"min",2),c([b()],H.prototype,"max",2),c([b()],H.prototype,"pattern",2),c([b()],H.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",H)});var C,yi=w(()=>{"use strict";P();O();R();ie();yt();C=class extends S{constructor(){super(...arguments);this.objects=[];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._entryId=null}get _lang(){return j(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._error="",this._open=!0}openEdit(e,t){this._entryId=e,this._name=t.name||"",this._manufacturer=t.manufacturer||"",this._model=t.model||"",this._serialNumber=t.serial_number||"",this._areaId=t.area_id||"",this._installationDate=t.installation_date||"",this._warrantyExpiry=t.warranty_expiry||"",this._documentationUrl=t.documentation_url||"",this._notes=t.notes||"",this._haDeviceId=t.ha_device_id||"",this._parentEntryId=t.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,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}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",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._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=T(e,this._lang,r("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return o``;let e=this._lang,t=this._entryId?r("edit_object",e):r("new_object",e);return o`
`,c([x()],q.prototype,"label",2),c([x()],q.prototype,"value",2),c([x()],q.prototype,"placeholder",2),c([x()],q.prototype,"type",2),c([x({type:Boolean})],q.prototype,"required",2),c([x({type:Boolean})],q.prototype,"disabled",2),c([x()],q.prototype,"step",2),c([x()],q.prototype,"min",2),c([x()],q.prototype,"max",2),c([x()],q.prototype,"pattern",2),c([x()],q.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",q)});var C,$i=w(()=>{"use strict";L();M();j();se();$t();C=class extends S{constructor(){super(...arguments);this.objects=[];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._entryId=null}get _lang(){return R(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._error="",this._open=!0}openEdit(e,t){this._entryId=e,this._name=t.name||"",this._manufacturer=t.manufacturer||"",this._model=t.model||"",this._serialNumber=t.serial_number||"",this._areaId=t.area_id||"",this._installationDate=t.installation_date||"",this._warrantyExpiry=t.warranty_expiry||"",this._documentationUrl=t.documentation_url||"",this._notes=t.notes||"",this._haDeviceId=t.ha_device_id||"",this._parentEntryId=t.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,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}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",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._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=T(e,this._lang,r("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return o``;let e=this._lang,t=this._entryId?r("edit_object",e):r("new_object",e);return o`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t}</div>
<div class="content">
@@ -1619,8 +1641,9 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
color: var(--error-color, #f44336);
font-size: 13px;
}
`,c([b({attribute:!1})],C.prototype,"hass",2),c([b({attribute:!1})],C.prototype,"objects",2),c([h()],C.prototype,"_open",2),c([h()],C.prototype,"_loading",2),c([h()],C.prototype,"_error",2),c([h()],C.prototype,"_name",2),c([h()],C.prototype,"_manufacturer",2),c([h()],C.prototype,"_model",2),c([h()],C.prototype,"_serialNumber",2),c([h()],C.prototype,"_areaId",2),c([h()],C.prototype,"_installationDate",2),c([h()],C.prototype,"_warrantyExpiry",2),c([h()],C.prototype,"_documentationUrl",2),c([h()],C.prototype,"_notes",2),c([h()],C.prototype,"_haDeviceId",2),c([h()],C.prototype,"_parentEntryId",2),c([h()],C.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",C)});var xt,xi,wi,$i=w(()=>{"use strict";xt=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock"],xi=["sensor"],wi=["temperature","humidity","pressure"]});function Ds(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",carry:{}}}function Fs(a){return{entityIds:(a.entity_ids||(a.entity_id?[a.entity_id]:[])).join(", "),type:a.type||"threshold",attribute:a.attribute||"",above:a.trigger_above?.toString()??"",below:a.trigger_below?.toString()??"",equals:a.trigger_equals?.toString()??"",notEquals:a.trigger_not_equals?.toString()??"",forMinutes:a.trigger_for_minutes?.toString()??"0",targetValue:a.trigger_target_value?.toString()??"",deltaMode:a.trigger_delta_mode||!1,fromState:a.trigger_from_state||"",toState:a.trigger_to_state||"",targetChanges:a.trigger_target_changes?.toString()??"",runtimeHours:a.trigger_runtime_hours?.toString()??"",onStates:(a.trigger_on_states||[]).join(", "),carry:Object.fromEntries(Object.entries(a).filter(([e])=>!zs.has(e)&&!e.startsWith("_")))}}function Us(a){let s=a.entityIds.split(",").map(t=>t.trim()).filter(Boolean);if(s.length===0)return null;let e={...a.carry||{},entity_id:s[0],entity_ids:s,type:a.type};if(a.attribute&&(e.attribute=a.attribute),a.type==="threshold"){let t=parseFloat(a.above);isNaN(t)||(e.trigger_above=t);let i=parseFloat(a.below);isNaN(i)||(e.trigger_below=i);let n=parseFloat(a.equals);isNaN(n)||(e.trigger_equals=n);let l=parseFloat(a.notEquals);isNaN(l)||(e.trigger_not_equals=l);let d=parseInt(a.forMinutes,10);isNaN(d)||(e.trigger_for_minutes=d)}else if(a.type==="counter"){let t=parseFloat(a.targetValue);isNaN(t)||(e.trigger_target_value=t),e.trigger_delta_mode=a.deltaMode}else if(a.type==="state_change"){a.fromState&&(e.trigger_from_state=a.fromState),a.toState&&(e.trigger_to_state=a.toState);let t=parseInt(a.targetChanges,10);isNaN(t)||(e.trigger_target_changes=t)}else if(a.type==="runtime"){let t=parseFloat(a.runtimeHours);isNaN(t)||(e.trigger_runtime_hours=t);let i=(a.onStates||"").split(",").map(n=>n.trim()).filter(Boolean);i.length>0&&(e.trigger_on_states=i)}return e}function Bs(a){return Array.from({length:7},(s,e)=>Te(e,a,"short"))}function Vs(a){let s=new Intl.DateTimeFormat(a||"en",{month:"short"});return Array.from({length:12},(e,t)=>s.format(new Date(2021,t,1)))}var Hs,qs,Os,wt,ki,Ms,J,zs,g,$t,Ei=w(()=>{"use strict";P();O();R();gt();Ie();$i();ie();mt();yt();Hs=["cleaning","inspection","replacement","calibration","service","reading","custom"],qs=["low","normal","high"],Os=["time_based","weekdays","nth_weekday","day_of_month","sensor_based","one_time","manual"],wt=["weekdays","nth_weekday","day_of_month"],ki=["threshold","counter","state_change","runtime"],Ms=[...ki,"compound"],J={alpha:"0.3",min:"7",max:"365"};zs=new Set(["entity_id","entity_ids","type","attribute","trigger_above","trigger_below","trigger_equals","trigger_not_equals","trigger_for_minutes","trigger_target_value","trigger_delta_mode","trigger_from_state","trigger_to_state","trigger_target_changes","trigger_runtime_hours","trigger_on_states"]);g=class g extends S{constructor(){super(...arguments);this.checklistsEnabled=!1;this.scheduleTimeEnabled=!1;this.completionActionsEnabled=!1;this.defaultWarningDays=7;this.parts=[];this._foreignOwners=[];this._open=!1;this._entityPickerFallback=!1;this._pickerProbeStrikes=0;this._loading=!1;this._error="";this._entryId="";this._taskId=null;this._objectChoices=[];this._name="";this._type="custom";this._scheduleType="time_based";this._intervalDays="30";this._intervalUnit="days";this._dueDate="";this._warningDays="7";this._earliestCompletionDays="";this._intervalAnchor="completion";this._weekdays=[];this._nth="1";this._nthWeekday="5";this._domDay="1";this._domLastDay=!1;this._domBusiness=!1;this._calOffset="0";this._seasonMonths=[];this._endsMode="never";this._endsCount="";this._endsUntil="";this._schedulePreview=[];this._schedulePreviewEnded=!1;this._previewSeq=0;this._notes="";this._documentationUrl="";this._customIcon="";t Line truncated
`),this._requiredCompletion=[...t.required_completion_fields||[]],this._scheduleTime=t.schedule_time||"";let l=t.on_complete_action;if(l&&l.service){this._actionService=l.service;let _=l.target?.entity_id;this._actionTargetEntity=Array.isArray(_)?_[0]||"":_||"",this._actionData=l.data&&typeof l.data=="object"?{...l.data}:{},this._actionDataJsonFallback=""}else this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="";let d=t.quick_complete_defaults;this._qcNotes=d?.notes||"",this._qcCost=d?.cost!=null?String(d.cost):"",this._qcDuration=d?.duration!=null?String(d.duration):"",this._qcFeedback=d?.feedback||"";let p=t.adaptive_config||{};if(this._environmentalEntity=p.environmental_entity||"",this._environmentalAttribute=p.environmental_attribute||"",this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute,this._adaptiveEnabled=!!p.enabled,this._adaptiveAlpha=p.ewa_alpha?.toString()??J.alpha,this._adaptiveMin=p.min_interval_days?.toString()??J.min,this._adaptiveMax=p.max_interval_days?.toString()??J.max,this._adaptiveSeasonal=p.seasonal_enabled!==!1,this._adaptivePrediction=p.sensor_prediction_enabled!==!1,this._adaptiveInitial=this._adaptiveSnapshot(),t.trigger_config){let _=t.trigger_config;this._triggerEntityId=_.entity_id||_.entity_ids&&_.entity_ids[0]||"",this._triggerEntityIds=_.entity_ids||(_.entity_id?[_.entity_id]:[]),this._triggerEntityLogic=_.entity_logic||"any",this._triggerAttribute=_.attribute||"",this._triggerType=_.type||"threshold",this._triggerAbove=_.trigger_above?.toString()||"",this._triggerBelow=_.trigger_below?.toString()||"",this._triggerEquals=_.trigger_equals?.toString()||"",this._triggerNotEquals=_.trigger_not_equals?.toString()||"",this._triggerForMinutes=_.trigger_for_minutes?.toString()||"0",this._triggerCombinator=_.trigger_combinator==="all"?"all":"any",this._triggerTargetValue=_.trigger_target_value?.toString()||"",this._triggerDeltaMode=_.trigger_delta_mode||!1,this._triggerBaselineValue=_.trigger_baseline_value?.toString()||"",this._liveBaselineValue=t.trigger_baseline_value??null,this._autoCompleteOnRecovery=_.auto_complete_on_recovery||!1,this._triggerFromState=_.trigger_from_state||"",this._triggerToState=_.trigger_to_state||"",this._triggerTargetChanges=_.trigger_target_changes?.toString()||"",this._triggerRuntimeHours=_.trigger_runtime_hours?.toString()||"",this._triggerOnStates=(_.trigger_on_states||[]).join(", "),_.type==="compound"?(this._compoundLogic=_.compound_logic==="OR"?"OR":"AND",this._compoundConditions=(_.conditions||[]).map(Fs)):(this._compoundLogic="AND",this._compoundConditions=[])}else this._resetTriggerFields();this._triggerEntityId&&this._fetchEntityAttributes(this._triggerEntityId),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}_resetFields(){this._name="",this._type="custom",this._scheduleType="time_based",this._intervalDays="30",this._intervalUnit="days",this._dueDate="",this._warningDays=String(this.defaultWarningDays),this._earliestCompletionDays="",this._intervalAnchor="completion",this._weekdays=[],this._nth="1",this._nthWeekday="5",this._domDay="1",this._domLastDay=!1,this._domBusiness=!1,this._calOffset="0",this._seasonMonths=[],this._endsMode="never",this._endsCount="",this._endsUntil="",this._notes="",this._documentationUrl="",this._customIcon="",this._priority="normal",this._labels="",this._enabled=!0,this._lastPerformed="",this._nfcTagId="",this._readingUnit="",this._consumesParts={},this._responsibleUserId=null,this._assigneePool=[],this._rotationStrategy="",this._checklistText="",this._requiredCompletion=[],this._scheduleTime="",this._environmentalEntity="",this._environmentalAttribute="",this._environmentalInitial="",this._environmentalAttributeInitial="",this._adaptiveEnabled=!1,this._adaptiveAlpha=J.alpha,this._adaptiveMin=J.min,this._adaptiveMax=J.max,this._adaptiveSeasonal=!0,this._adaptivePrediction=!0,this._adaptiveInitial=this._adaptiveSnapshot(),this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="",this._actionTesting=!1,this._actionTestResult="",this._qcNotes="",this._qcCost="",this._qcDuration="",this._qcFeedback="",this._resetTriggerFields()}_resetTriggerFields(){this._triggerEntityId="",this._triggerEntityIds=[],this._triggerEntityLogic="any",this._triggerAttribute="",this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="",this._triggerType="threshold",this._triggerAbove="",this._triggerBelow="",this._triggerEquals="",this._triggerNotEquals="",this._triggerForMinutes="0",this._triggerCombinator="any",this._triggerTargetValue="",this._triggerDeltaMode=!1,this._triggerBaselineValue="",this._liveBaselineValue=null,this._autoCompleteOnRecovery=!1,this._triggerFromState="",this._triggerToState="",this._triggerTargetChanges="",this._triggerRuntimeHours="",this._triggerOnStates="",this._comp Line truncated
`,c([x({attribute:!1})],C.prototype,"hass",2),c([x({attribute:!1})],C.prototype,"objects",2),c([h()],C.prototype,"_open",2),c([h()],C.prototype,"_loading",2),c([h()],C.prototype,"_error",2),c([h()],C.prototype,"_name",2),c([h()],C.prototype,"_manufacturer",2),c([h()],C.prototype,"_model",2),c([h()],C.prototype,"_serialNumber",2),c([h()],C.prototype,"_areaId",2),c([h()],C.prototype,"_installationDate",2),c([h()],C.prototype,"_warrantyExpiry",2),c([h()],C.prototype,"_documentationUrl",2),c([h()],C.prototype,"_notes",2),c([h()],C.prototype,"_haDeviceId",2),c([h()],C.prototype,"_parentEntryId",2),c([h()],C.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",C)});var kt,ki,Ei,Si=w(()=>{"use strict";kt=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock"],ki=["sensor"],Ei=["temperature","humidity","pressure"]});function Vs(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",carry:{}}}function Ks(a){return{entityIds:(a.entity_ids||(a.entity_id?[a.entity_id]:[])).join(", "),type:a.type||"threshold",attribute:a.attribute||"",above:a.trigger_above?.toString()??"",below:a.trigger_below?.toString()??"",equals:a.trigger_equals?.toString()??"",notEquals:a.trigger_not_equals?.toString()??"",forMinutes:a.trigger_for_minutes?.toString()??"0",targetValue:a.trigger_target_value?.toString()??"",deltaMode:a.trigger_delta_mode||!1,fromState:a.trigger_from_state||"",toState:a.trigger_to_state||"",targetChanges:a.trigger_target_changes?.toString()??"",runtimeHours:a.trigger_runtime_hours?.toString()??"",onStates:(a.trigger_on_states||[]).join(", "),carry:Object.fromEntries(Object.entries(a).filter(([e])=>!Ws.has(e)&&!e.startsWith("_")))}}function Gs(a){let s=a.entityIds.split(",").map(t=>t.trim()).filter(Boolean);if(s.length===0)return null;let e={...a.carry||{},entity_id:s[0],entity_ids:s,type:a.type};if(a.attribute&&(e.attribute=a.attribute),a.type==="threshold"){let t=parseFloat(a.above);isNaN(t)||(e.trigger_above=t);let i=parseFloat(a.below);isNaN(i)||(e.trigger_below=i);let n=parseFloat(a.equals);isNaN(n)||(e.trigger_equals=n);let l=parseFloat(a.notEquals);isNaN(l)||(e.trigger_not_equals=l);let d=parseInt(a.forMinutes,10);isNaN(d)||(e.trigger_for_minutes=d)}else if(a.type==="counter"){let t=parseFloat(a.targetValue);isNaN(t)||(e.trigger_target_value=t),e.trigger_delta_mode=a.deltaMode}else if(a.type==="state_change"){a.fromState&&(e.trigger_from_state=a.fromState),a.toState&&(e.trigger_to_state=a.toState);let t=parseInt(a.targetChanges,10);isNaN(t)||(e.trigger_target_changes=t)}else if(a.type==="runtime"){let t=parseFloat(a.runtimeHours);isNaN(t)||(e.trigger_runtime_hours=t);let i=(a.onStates||"").split(",").map(n=>n.trim()).filter(Boolean);i.length>0&&(e.trigger_on_states=i)}return e}function Ys(a){return Array.from({length:7},(s,e)=>Ce(e,a,"short"))}function Qs(a){let s=new Intl.DateTimeFormat(a||"en",{month:"short"});return Array.from({length:12},(e,t)=>s.format(new Date(2021,t,1)))}var zs,Fs,Us,Et,Ai,Bs,J,Ws,g,St,Ti=w(()=>{"use strict";L();M();j();ft();Pe();Si();se();bt();$t();zs=["cleaning","inspection","replacement","calibration","service","reading","custom"],Fs=["low","normal","high"],Us=["time_based","weekdays","nth_weekday","day_of_month","sensor_based","one_time","manual"],Et=["weekdays","nth_weekday","day_of_month"],Ai=["threshold","counter","state_change","runtime"],Bs=[...Ai,"compound"],J={alpha:"0.3",min:"7",max:"365"};Ws=new Set(["entity_id","entity_ids","type","attribute","trigger_above","trigger_below","trigger_equals","trigger_not_equals","trigger_for_minutes","trigger_target_value","trigger_delta_mode","trigger_from_state","trigger_to_state","trigger_target_changes","trigger_runtime_hours","trigger_on_states"]);g=class g extends S{constructor(){super(...arguments);this.checklistsEnabled=!1;this.scheduleTimeEnabled=!1;this.completionActionsEnabled=!1;this.defaultWarningDays=7;this.parts=[];this._foreignOwners=[];this._open=!1;this._entityPickerFallback=!1;this._pickerProbeStrikes=0;this._loading=!1;this._error="";this._entryId="";this._taskId=null;this._objectChoices=[];this._name="";this._type="custom";this._scheduleType="time_based";this._intervalDays="30";this._intervalUnit="days";this._dueDate="";this._warningDays="7";this._earliestCompletionDays="";this._intervalAnchor="completion";this._weekdays=[];this._nth="1";this._nthWeekday="5";this._domDay="1";this._domLastDay=!1;this._domBusiness=!1;this._calOffset="0";this._seasonMonths=[];this._endsMode="never";this._endsCount="";this._endsUntil="";this._schedulePreview=[];this._schedulePreviewEnded=!1;this._previewSeq=0;this._notes="";this._documentationUrl="";this._customIcon="";t Line truncated
`),this._phaseDefs=Object.entries(t.phases||{}).map(([_,v])=>{let{name:m,checklist:b,consumes_parts:y,...f}=v;return{id:_,name:v.name||_,checklistText:(v.checklist||[]).join(`
`),partId:v.consumes_parts?.[0]?.part_id||"",partQty:v.consumes_parts?.[0]?.quantity!=null?String(v.consumes_parts[0].quantity):"",extraParts:(v.consumes_parts||[]).slice(1),carry:f}}),this._phaseSeq=[...t.phase_sequence||[]],this._requiredCompletion=[...t.required_completion_fields||[]],this._scheduleTime=t.schedule_time||"";let l=t.on_complete_action;if(l&&l.service){this._actionService=l.service;let _=l.target?.entity_id;this._actionTargetEntity=Array.isArray(_)?_[0]||"":_||"",this._actionData=l.data&&typeof l.data=="object"?{...l.data}:{},this._actionDataJsonFallback=""}else this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="";let d=t.quick_complete_defaults;this._qcNotes=d?.notes||"",this._qcCost=d?.cost!=null?String(d.cost):"",this._qcDuration=d?.duration!=null?String(d.duration):"",this._qcFeedback=d?.feedback||"";let p=t.adaptive_config||{};if(this._environmentalEntity=p.environmental_entity||"",this._environmentalAttribute=p.environmental_attribute||"",this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute,this._adaptiveEnabled=!!p.enabled,this._adaptiveAlpha=p.ewa_alpha?.toString()??J.alpha,this._adaptiveMin=p.min_interval_days?.toString()??J.min,this._adaptiveMax=p.max_interval_days?.toString()??J.max,this._adaptiveSeasonal=p.seasonal_enabled!==!1,this._adaptivePrediction=p.sensor_prediction_enabled!==!1,this._adaptiveInitial=this._adaptiveSnapshot(),t.trigger_config){let _=t.trigger_config;this._triggerEntityId=_.entity_id||_.entity_ids&&_.entity_ids[0]||"",this._triggerEntityIds=_.entity_ids||(_.entity_id?[_.entity_id]:[]),this._triggerEntityLogic=_.entity_logic||"any",this._triggerAttribute=_.attribute||"",this._triggerType=_.type||"threshold",this._triggerAbove=_.trigger_above?.toString()||"",this._triggerBelow=_.trigger_below?.toString()||"",this._triggerEquals=_.trigger_equals?.toString()||"",this._triggerNotEquals=_.trigger_not_equals?.toString()||"",this._triggerForMinutes=_.trigger_for_minutes?.toString()||"0",this._triggerCombinator=_.trigger_combinator==="all"?"all":"any",this._triggerTargetValue=_.trigger_target_value?.toString()||"",this._triggerDeltaMode=_.trigger_delta_mode||!1,this._triggerBaselineValue=_.trigger_baseline_value?.toString()||"",this._liveBaselineValue=t.trigger_baseline_value??null,this._autoCompleteOnRecovery=_.auto_complete_on_recovery||!1,this._triggerFromState=_.trigger_from_state||"",this._triggerToState=_.trigger_to_state||"",this._triggerTargetChanges=_.trigger_target_changes?.toString()||"",this._triggerRuntimeHours=_.trigger_runtime_hours?.toString()||"",this._triggerOnStates=(_.trigger_on_states||[]).join(", "),_.type==="compound"?(this._compoundLogic=_.compound_logic==="OR"?"OR":"AND",this._compoundConditions=(_.conditions||[]).map(Ks)):(this._compoundLogic="AND",this._compoundConditions=[])}else this._resetTriggerFields();this._triggerEntityId&&this._fetchEntityAttributes(this._triggerEntityId),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}_resetFields(){this._name="",this._type="custom",this._scheduleType="time_based",this._intervalDays="30",this._intervalUnit="days",this._dueDate="",this._warningDays=String(this.defaultWarningDays),this._earliestCompletionDays="",this._intervalAnchor="completion",this._weekdays=[],this._nth="1",this._nthWeekday="5",this._domDay="1",this._domLastDay=!1,this._domBusiness=!1,this._calOffset="0",this._seasonMonths=[],this._endsMode="never",this._endsCount="",this._endsUntil="",this._notes="",this._documentationUrl="",this._customIcon="",this._priority="normal",this._labels="",this._enabled=!0,this._lastPerformed="",this._nfcTagId="",this._readingUnit="",this._consumesParts={},this._responsibleUserId=null,this._assigneePool=[],this._rotationStrategy="",this._checklistText="",this._phaseDefs=[],this._phaseSeq=[],this._requiredCompletion=[],this._scheduleTime="",this._environmentalEntity="",this._environmentalAttribute="",this._environmentalInitial="",this._environmentalAttributeInitial="",this._adaptiveEnabled=!1,this._adaptiveAlpha=J.alpha,this._adaptiveMin=J.min,this._adaptiveMax=J.max,this._adaptiveSeasonal=!0,this._adaptivePrediction=!0,this._adaptiveInitial=this._adaptiveSnapshot(),this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="",this._actionTesting=!1,this._actionTestResult="",this._qcNotes="",this._qcCost="",this._qcDuration="",this._qcFeedback="",this._resetTriggerFields()}_resetTriggerFields(){this._triggerEntityId="",this._triggerEntityIds=[],this._triggerEntityLogic="any",this._triggerAttribute="",this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="",this._triggerType="threshold",this._triggerAbove="",this._triggerBelow="",this._triggerEquals="",this._triggerNotEquals="",this._triggerForMinutes="0",this._triggerCombinator="any",this._triggerTargetValue="",t Line truncated
<details class="ca-section">
<summary>${r("on_complete_action_title",e)}</summary>
<p class="field-help">${r("on_complete_action_desc",e)}</p>
@@ -1716,8 +1739,71 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
@input=${d=>{let p=parseFloat(d.target.value);this._consumesParts={...this._consumesParts,[i]:{...l,quantity:Number.isFinite(p)&&p>=.01?p:1}}}}
/>`:u}
</div>
`}_toggleRequired(e,t){let i=new Set(this._requiredCompletion);t?i.add(e):i.delete(e),this._requiredCompletion=[...i]}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${r("adaptive_min_interval",this._lang)} > ${r("adaptive_max_interval",this._lang)}`;return}}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();if(e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):wt.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(d=>d.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.reading_unit=this._readingUnit.trim()||null,(this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(d=>d.entry_id?{part_id:d.part_id,quantity:d.quantity,entry_id:d.entry_id}:{part_id:d.part_id,quantity:d.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let d=this._compoundConditions.map(Us).filter(p=>p!==null);if(d.length>0){let p={type:"compound",compound_logic:this._compoundLogic,conditions:d};this._autoCompleteOnRecovery&&(p.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(p.trigger_combinator="all"),e.trigger_config=p}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let d=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],p={entity_id:d[0],entity_ids:d,type:this._triggerType};if(this._triggerAttribute&&(p.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(p.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(p.trigger_combinator="all"),d.length>1&&(p.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let _=parseFloat(this._triggerAbove);isNaN(_)||(p.trigger_above=_)}if(this._triggerBelow){let _=parseFloat(this._triggerBelow);isNaN(_)||(p.trigger_below=_)}if(this._triggerEquals){let _=parseFloat(this._triggerEquals);isNaN(_)||(p.trigger_equals=_)}if(this._triggerNotEquals){let _=parseFloat(this._triggerNotEquals);isNaN(_)||(p.trigger_not_equals=_)}if(this._triggerForMinutes){let _=parseInt(this._triggerForMinutes,10);isNaN(_)||(p.trigger_for_minutes=_)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let _=parseFloat(this._triggerTargetValue);isNaN(_)||(p.trigger_target_value=_)}if(p.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let _=parseFloat(this._triggerBaselineValue);!isNaN(_)&&_>=0&&(p.trigger_baseline_value=_)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(p.trigger_from_state=this._triggerFromState),this._triggerToState&&(p.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let _=parseInt(this._triggerTargetChanges,10);isNaN(_)||(p.trigger_target_changes=_)}if(this._triggerForMinutes){let _=parseInt(this._triggerForMinutes,10);isNaN(_)||(p.trigger_for_minutes=_)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let v=parseFloat(this._triggerRuntimeHours);isNaN(v)||(p.trigger_runtime_hours=v)}let _=this._triggerOnStates.split(",").map(v=>v.trim()).filter(Boolean);_.length>0&&(p.trigger_on_states=_)}e.trigger_config=p}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&this._scheduleType==="time_based"){let d=this._scheduleTime.trim();e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(d)?d:null}if(this.checklistsEnabled){let d=this._checklistText.split(`
`).map(p=>p.trim()).filter(Boolean).slice(0,100);e.checklist=d.length?d:null}if(this.completionActionsEnabled){let d=this._actionService.trim();if(d&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(d)){let m={service:d},y=this._actionTargetEntity.trim();y&&(m.target={entity_id:y});let x=this._buildActionData();Object.keys(x).length>0&&(m.data=x),e.on_complete_action=m}else e.on_complete_action=null;let p={};this._qcNotes.trim()&&(p.notes=this._qcNotes.trim());let _=parseFloat(this._qcCost);!isNaN(_)&&_>=0&&(p.cost=_);let v=parseInt(this._qcDuration,10);!isNaN(v)&&v>=0&&(p.duration=v),this._qcFeedback&&(p.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(p).length?p:null}let i=await this.hass.connection.sendMessagePromise(e),n=this._taskId||i?.task_id,l=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(n&&this._scheduleType==="sensor_based"&&l)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:n,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null}),this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute}catch{}if(n&&this._adaptiveSnapshot()!==this._adaptiveInitial){let d=parseFloat(this._adaptiveAlpha),p=parseInt(this._adaptiveMin,10),_=parseInt(this._adaptiveMax,10);try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:n,enabled:this._adaptiveEnabled,...d>=.1&&d<=.9?{ewa_alpha:d}:{},...!isNaN(p)&&p>=1?{min_interval_days:p}:{},...!isNaN(_)&&_>=1?{max_interval_days:_}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction}),this._adaptiveInitial=this._adaptiveSnapshot()}catch{}}this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=T(e,this._lang,r("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return u;let e=this._lang,t=this._triggerType==="compound";return o`
`}_toggleRequired(e,t){let i=new Set(this._requiredCompletion);t?i.add(e):i.delete(e),this._requiredCompletion=[...i]}_phaseSlug(e){let t=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"phase",i=t,n=2;for(;this._phaseDefs.some(l=>l.id===i);)i=`${t}-${n++}`;return i}_addPhaseDef(){let e=this._phaseSlug(`phase-${this._phaseDefs.length+1}`);this._phaseDefs=[...this._phaseDefs,{id:e,name:"",checklistText:"",partId:"",partQty:"",extraParts:[],carry:{}}]}_removePhaseDef(e){this._phaseDefs=this._phaseDefs.filter(t=>t.id!==e),this._phaseSeq=this._phaseSeq.filter(t=>t!==e)}_patchPhaseDef(e,t){this._phaseDefs=this._phaseDefs.map(i=>i.id===e?{...i,...t}:i)}_renderPhasesEditor(e){let t=i=>this._phaseDefs.find(n=>n.id===i)?.name||i;return o`
<h3>${r("phases_section",e)}</h3>
<div class="field-help">${r("phases_hint",e)}</div>
${this._phaseDefs.map(i=>o`
<div class="phase-def">
<div class="phase-def-head">
<ms-textfield
label="${r("phase_name",e)}"
.value=${i.name}
@input=${n=>this._patchPhaseDef(i.id,{name:n.target.value})}
></ms-textfield>
${this.parts.length?o`
<select
class="phase-part"
.value=${i.partId}
@change=${n=>this._patchPhaseDef(i.id,{partId:n.target.value})}
>
<option value="">—</option>
${this.parts.map(n=>o`<option value=${n.id} ?selected=${n.id===i.partId}>${n.name}</option>`)}
</select>
${i.partId?o`
<input class="phase-qty" type="number" min="0.01" step="0.01" .value=${i.partQty||"1"}
@input=${n=>this._patchPhaseDef(i.id,{partQty:n.target.value})} />
`:u}
`:u}
<mwc-icon-button class="phase-remove" @click=${()=>this._removePhaseDef(i.id)}>
<ha-icon icon="mdi:delete-outline"></ha-icon>
</mwc-icon-button>
</div>
${this.checklistsEnabled?o`
<textarea
class="checklist-textarea phase-checklist"
rows="2"
placeholder="${r("checklist_placeholder",e)}"
.value=${i.checklistText}
@input=${n=>this._patchPhaseDef(i.id,{checklistText:n.target.value})}
></textarea>
`:u}
</div>
`)}
<ha-button appearance="plain" @click=${this._addPhaseDef}>
<ha-icon icon="mdi:plus"></ha-icon> ${r("phase_add",e)}
</ha-button>
${this._phaseDefs.some(i=>i.name.trim())?o`
<div class="phase-seq-label">${r("phase_sequence_label",e)}</div>
<div class="phase-seq">
${this._phaseSeq.map((i,n)=>o`
<span class="phase-chip">
${n+1}. ${t(i)}
<button class="phase-chip-x" @click=${()=>{this._phaseSeq=this._phaseSeq.filter((l,d)=>d!==n)}}>✕</button>
</span>
`)}
<select
class="phase-seq-add"
.value=${""}
@change=${i=>{let n=i.target.value;n&&(this._phaseSeq=[...this._phaseSeq,n]),i.target.value=""}}
>
<option value="">+ ${r("phase_sequence_add_step",e)}</option>
${this._phaseDefs.filter(i=>i.name.trim()).map(i=>o`<option value=${i.id}>${i.name}</option>`)}
</select>
</div>
`:u}
`}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${r("adaptive_min_interval",this._lang)} > ${r("adaptive_max_interval",this._lang)}`;return}}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):Et.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(d=>d.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.reading_unit=this._readingUnit.trim()||null;{let d={};for(let _ of this._phaseDefs){if(!_.name.trim())continue;let v={..._.carry,name:_.name.trim()},m=_.checklistText.split(`
`).map(y=>y.trim()).filter(Boolean);m.length&&(v.checklist=m);let b=[];if(_.partId){let y=parseFloat(_.partQty);b.push({part_id:_.partId,quantity:Number.isFinite(y)&&y>0?y:1})}b.push(..._.extraParts),b.length&&(v.consumes_parts=b),d[_.id]=v}let p=this._phaseSeq.filter(_=>_ in d);e.phases=Object.keys(d).length&&p.length?d:null,e.phase_sequence=e.phases?p:null}if((this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(d=>d.entry_id?{part_id:d.part_id,quantity:d.quantity,entry_id:d.entry_id}:{part_id:d.part_id,quantity:d.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let d=this._compoundConditions.map(Gs).filter(p=>p!==null);if(d.length>0){let p={type:"compound",compound_logic:this._compoundLogic,conditions:d};this._autoCompleteOnRecovery&&(p.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(p.trigger_combinator="all"),e.trigger_config=p}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let d=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],p={entity_id:d[0],entity_ids:d,type:this._triggerType};if(this._triggerAttribute&&(p.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(p.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(p.trigger_combinator="all"),d.length>1&&(p.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let _=parseFloat(this._triggerAbove);isNaN(_)||(p.trigger_above=_)}if(this._triggerBelow){let _=parseFloat(this._triggerBelow);isNaN(_)||(p.trigger_below=_)}if(this._triggerEquals){let _=parseFloat(this._triggerEquals);isNaN(_)||(p.trigger_equals=_)}if(this._triggerNotEquals){let _=parseFloat(this._triggerNotEquals);isNaN(_)||(p.trigger_not_equals=_)}if(this._triggerForMinutes){let _=parseInt(this._triggerForMinutes,10);isNaN(_)||(p.trigger_for_minutes=_)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let _=parseFloat(this._triggerTargetValue);isNaN(_)||(p.trigger_target_value=_)}if(p.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let _=parseFloat(this._triggerBaselineValue);!isNaN(_)&&_>=0&&(p.trigger_baseline_value=_)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(p.trigger_from_state=this._triggerFromState),this._triggerToState&&(p.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let _=parseInt(this._triggerTargetChanges,10);isNaN(_)||(p.trigger_target_changes=_)}if(this._triggerForMinutes){let _=parseInt(this._triggerForMinutes,10);isNaN(_)||(p.trigger_for_minutes=_)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let v=parseFloat(this._triggerRuntimeHours);isNaN(v)||(p.trigger_runtime_hours=v)}let _=this._triggerOnStates.split(",").map(v=>v.trim()).filter(Boolean);_.length>0&&(p.trigger_on_states=_)}e.trigger_config=p}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&this._scheduleType==="time_based"){let d=this._scheduleTime.trim();e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(d)?d:null}if(this.checklistsEnabled){let d=this._checklistText.split(`
`).map(p=>p.trim()).filter(Boolean).slice(0,100);e.checklist=d.length?d:null}if(this.completionActionsEnabled){let d=this._actionService.trim();if(d&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(d)){let m={service:d},b=this._actionTargetEntity.trim();b&&(m.target={entity_id:b});let y=this._buildActionData();Object.keys(y).length>0&&(m.data=y),e.on_complete_action=m}else e.on_complete_action=null;let p={};this._qcNotes.trim()&&(p.notes=this._qcNotes.trim());let _=parseFloat(this._qcCost);!isNaN(_)&&_>=0&&(p.cost=_);let v=parseInt(this._qcDuration,10);!isNaN(v)&&v>=0&&(p.duration=v),this._qcFeedback&&(p.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(p).length?p:null}let i=await this.hass.connection.sendMessagePromise(e),n=this._taskId||i?.task_id,l=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(n&&this._scheduleType==="sensor_based"&&l)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:n,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null}),this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute}catch{}if(n&&this._adaptiveSnapshot()!==this._adaptiveInitial){let d=parseFloat(this._adaptiveAlpha),p=parseInt(this._adaptiveMin,10),_=parseInt(this._adaptiveMax,10);try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:n,enabled:this._adaptiveEnabled,...d>=.1&&d<=.9?{ewa_alpha:d}:{},...!isNaN(p)&&p>=1?{min_interval_days:p}:{},...!isNaN(_)&&_>=1?{max_interval_days:_}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction}),this._adaptiveInitial=this._adaptiveSnapshot()}catch{}}this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=T(e,this._lang,r("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return u;let e=this._lang,t=this._triggerType==="compound";return o`
<h3>${r("trigger_configuration",e)}</h3>
<div class="select-row">
<label>${r("trigger_type",e)}</label>
@@ -1725,7 +1811,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._triggerType}
@change=${i=>this._triggerType=i.target.value}
>
${Ms.map(i=>o`<option value=${i} ?selected=${i===this._triggerType}>${r(i,e)}</option>`)}
${Bs.map(i=>o`<option value=${i} ?selected=${i===this._triggerType}>${r(i,e)}</option>`)}
</select>
</div>
${t?this._renderCompoundEditor():o`
@@ -1739,7 +1825,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-form
class="entity-picker-form"
.hass=${this.hass}
.schema=${[{name:"trigger_entities",selector:{entity:{multiple:!0,domain:xt}}}]}
.schema=${[{name:"trigger_entities",selector:{entity:{multiple:!0,domain:kt}}}]}
.data=${{trigger_entities:this._triggerEntityIds.length>0?this._triggerEntityIds:this._triggerEntityId?[this._triggerEntityId]:[]}}
.computeLabel=${()=>r("entity_id",e)}
@value-changed=${i=>{let n=(i.detail.value.trigger_entities||[]).filter(Boolean);this._triggerEntityId=n[0]||"",this._triggerEntityIds=n,n[0]?this._fetchEntityAttributes(n[0]):this._fetchEntityAttributes("")}}
@@ -1787,7 +1873,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
</select>
</div>
`:u}
`}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Ds()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return o`
`}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Vs()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return o`
<div class="select-row">
<label>${r("compound_logic",e)}</label>
<select
@@ -1824,7 +1910,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-form
class="entity-picker-form"
.hass=${this.hass}
.schema=${[{name:"condition_entities",selector:{entity:{multiple:!0,domain:xt}}}]}
.schema=${[{name:"condition_entities",selector:{entity:{multiple:!0,domain:kt}}}]}
.data=${{condition_entities:e.entityIds.split(",").map(l=>l.trim()).filter(Boolean)}}
.computeLabel=${()=>r("entity_id",i)}
@value-changed=${l=>{let d=(l.detail.value.condition_entities||[]).filter(Boolean);this._patchCondition(t,{entityIds:d.join(", ")})}}
@@ -1836,7 +1922,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${e.type}
@change=${l=>this._patchCondition(t,{type:l.target.value})}
>
${ki.map(l=>o`<option value=${l} ?selected=${l===e.type}>${r(l,i)}</option>`)}
${Ai.map(l=>o`<option value=${l} ?selected=${l===e.type}>${r(l,i)}</option>`)}
</select>
</div>
${this._renderConditionTypeFields(e,t)}
@@ -1980,12 +2066,12 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
>
${["days","weeks","months","years"].map(t=>o`<option value=${t} ?selected=${t===this._intervalUnit}>${r("unit_"+t,e)}</option>`)}
</select>
</div>`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(wt.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(g._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(p,_,v=0)=>{if(!(!p||v>10)){(p.tagName?.toLowerCase()??"")==="ha-entity-picker"&&_.push(p);for(let m of[p.shadowRoot,p])if(m)for(let y of Array.from(m.children??[]))i(y,_,v+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],l=[];for(let p of n)i(p,l);let d=l.length===0||l.some(p=>p.offsetHeight===0);if(e.offsetHeight===0||d){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return u;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((l,d)=>{let p=new Date(`${l}T12:00:00`).getDay();return`${Te(p===0?6:p-1,e,"short")} ${Q(l,e)}${d===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?o`<div class="field-help">${r("schedule_preview_ontime",e)}</div>`:u;return o`
</div>`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(Et.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(g._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(p,_,v=0)=>{if(!(!p||v>10)){(p.tagName?.toLowerCase()??"")==="ha-entity-picker"&&_.push(p);for(let m of[p.shadowRoot,p])if(m)for(let b of Array.from(m.children??[]))i(b,_,v+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],l=[];for(let p of n)i(p,l);let d=l.length===0||l.some(p=>p.offsetHeight===0);if(e.offsetHeight===0||d){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return u;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((l,d)=>{let p=new Date(`${l}T12:00:00`).getDay();return`${Ce(p===0?6:p-1,e,"short")} ${Q(l,e)}${d===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?o`<div class="field-help">${r("schedule_preview_ontime",e)}</div>`:u;return o`
<div class="trigger-live-hint schedule-preview">
${r("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?o` <span class="field-help">${r("schedule_preview_ends",e)}</span>`:u}
${n}
</div>
`}_buildSchedule(){let e=i=>{let n=parseInt(this._calOffset,10)||0;return n&&(i.offset=Math.max(-15,Math.min(n,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,n)=>i-n)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||wt.includes(this._scheduleType)))return u;let i=Vs(e);return o`
`}_buildSchedule(){let e=i=>{let n=parseInt(this._calOffset,10)||0;return n&&(i.offset=Math.max(-15,Math.min(n,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,n)=>i-n)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||Et.includes(this._scheduleType)))return u;let i=Qs(e);return o`
<label class="field-label">${r("season_window_label",e)}</label>
<div class="field-help">${r("season_window_hint",e)}</div>
<div class="weekday-chips season-chips">
@@ -2020,7 +2106,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._endsUntil}
@input=${n=>this._endsUntil=n.target.value}
></ms-textfield>`:u}
`}_renderCalendarFields(){let e=this._lang,t=Bs(e);if(this._scheduleType==="weekdays")return o`
`}_renderCalendarFields(){let e=this._lang,t=Ys(e);if(this._scheduleType==="weekdays")return o`
<label class="field-label">${r("recurrence_on_days",e)}</label>
<div class="weekday-chips">
${t.map((i,n)=>o`
@@ -2072,7 +2158,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
max="15"
.value=${this._calOffset}
@input=${t=>this._calOffset=t.target.value}
></ms-textfield>`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return u;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return u;let t=this.hass.states[e];if(!t)return u;let i=this._lang,n=t.attributes?.unit_of_measurement,l=typeof n=="string"&&n?` ${n}`:"",d=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,p=typeof d=="number"?d:parseFloat(String(d)),_=d!=="unknown"&&d!=="unavailable"&&d!=null&&!isNaN(p),v=y=>Number.isInteger(y)?String(y):String(Math.round(y*10)/10),m=[];if(this._triggerType==="threshold"){let y=parseFloat(this._triggerAbove),x=parseFloat(this._triggerBelow);if(isNaN(y)&&isNaN(x))return u;_&&m.push(r("trigger_hint_now",i).replace("{value}",v(p)+l)),isNaN(y)||m.push(r("trigger_hint_above",i).replace("{target}",v(y)+l)),isNaN(x)||m.push(r("trigger_hint_below",i).replace("{target}",v(x)+l))}else if(this._triggerType==="counter"){let y=parseFloat(this._triggerTargetValue);if(isNaN(y))return u;this._triggerDeltaMode?this._taskId?m.push(r("trigger_hint_counter_delta_edit",i).replace("{target}",v(y)+l)):_?m.push(r("trigger_hint_counter_delta",i).replace("{value}",v(p)+l).replace("{due}",v(p+y)+l).replace("{target}",v(y)+l)):m.push(r("trigger_hint_counter_delta_edit",i).replace("{target}",v(y)+l)):(_&&m.push(r("trigger_hint_now",i).replace("{value}",v(p)+l)),m.push(r("trigger_hint_counter_abs",i).replace("{target}",v(y)+l)))}else if(this._triggerType==="runtime"){let y=parseFloat(this._triggerRuntimeHours);if(isNaN(y))return u;m.push(r("trigger_hint_runtime",i).replace("{hours}",v(y))),m.push(r("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let y=parseInt(this._triggerTargetChanges,10)||1,x=this._triggerToState.trim();m.push((x?r("trigger_hint_state_change_to",i).replace("{state}",x):r("trigger_hint_state_change",i)).replace("{count}",String(y))),m.push(r("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return m.length?o`<div class="trigger-live-hint">${m.join(" ")}</div>`:u}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?o`
></ms-textfield>`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return u;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return u;let t=this.hass.states[e];if(!t)return u;let i=this._lang,n=t.attributes?.unit_of_measurement,l=typeof n=="string"&&n?` ${n}`:"",d=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,p=typeof d=="number"?d:parseFloat(String(d)),_=d!=="unknown"&&d!=="unavailable"&&d!=null&&!isNaN(p),v=b=>Number.isInteger(b)?String(b):String(Math.round(b*10)/10),m=[];if(this._triggerType==="threshold"){let b=parseFloat(this._triggerAbove),y=parseFloat(this._triggerBelow);if(isNaN(b)&&isNaN(y))return u;_&&m.push(r("trigger_hint_now",i).replace("{value}",v(p)+l)),isNaN(b)||m.push(r("trigger_hint_above",i).replace("{target}",v(b)+l)),isNaN(y)||m.push(r("trigger_hint_below",i).replace("{target}",v(y)+l))}else if(this._triggerType==="counter"){let b=parseFloat(this._triggerTargetValue);if(isNaN(b))return u;this._triggerDeltaMode?this._taskId?m.push(r("trigger_hint_counter_delta_edit",i).replace("{target}",v(b)+l)):_?m.push(r("trigger_hint_counter_delta",i).replace("{value}",v(p)+l).replace("{due}",v(p+b)+l).replace("{target}",v(b)+l)):m.push(r("trigger_hint_counter_delta_edit",i).replace("{target}",v(b)+l)):(_&&m.push(r("trigger_hint_now",i).replace("{value}",v(p)+l)),m.push(r("trigger_hint_counter_abs",i).replace("{target}",v(b)+l)))}else if(this._triggerType==="runtime"){let b=parseFloat(this._triggerRuntimeHours);if(isNaN(b))return u;m.push(r("trigger_hint_runtime",i).replace("{hours}",v(b))),m.push(r("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let b=parseInt(this._triggerTargetChanges,10)||1,y=this._triggerToState.trim();m.push((y?r("trigger_hint_state_change_to",i).replace("{state}",y):r("trigger_hint_state_change",i)).replace("{count}",String(b))),m.push(r("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return m.length?o`<div class="trigger-live-hint">${m.join(" ")}</div>`:u}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?o`
<ms-textfield
label="${r("trigger_above",e)}"
type="number"
@@ -2196,7 +2282,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._type}
@change=${i=>this._type=i.target.value}
>
${Hs.map(i=>o`<option value=${i} ?selected=${i===this._type}>${r(i,e)}</option>`)}
${zs.map(i=>o`<option value=${i} ?selected=${i===this._type}>${r(i,e)}</option>`)}
</select>
</div>
${this._type==="reading"?o`
@@ -2230,7 +2316,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._priority}
@change=${i=>this._priority=i.target.value}
>
${qs.map(i=>o`<option value=${i} ?selected=${i===this._priority}>${r("priority_"+i,e)}</option>`)}
${Fs.map(i=>o`<option value=${i} ?selected=${i===this._priority}>${r("priority_"+i,e)}</option>`)}
</select>
</div>
<div class="field">
@@ -2249,7 +2335,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._scheduleType}
@change=${i=>this._scheduleType=i.target.value}
>
${Os.map(i=>o`<option value=${i} ?selected=${i===this._scheduleType}>${r(i,e)}</option>`)}
${Us.map(i=>o`<option value=${i} ?selected=${i===this._scheduleType}>${r(i,e)}</option>`)}
</select>
</div>
${this._scheduleType==="time_based"?o`
@@ -2318,16 +2404,17 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
></textarea>
<div class="field-help">${r("checklist_help",e)}</div>
`:u}
${this._renderPhasesEditor(e)}
<h3>${r("require_on_completion",e)}</h3>
<div class="required-completion">
${fi.map(i=>o`
${yi.map(i=>o`
<label class="req-option">
<input
type="checkbox"
.checked=${this._requiredCompletion.includes(i)}
@change=${n=>this._toggleRequired(i,n.target.checked)}
/>
<span>${r(We[i],e)}</span>
<span>${r(Ge[i],e)}</span>
</label>
`)}
</div>
@@ -2386,7 +2473,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-form
class="entity-picker-form"
.hass=${this.hass}
.schema=${[{name:"environmental_entity",selector:{entity:{domain:xi,device_class:wi}}}]}
.schema=${[{name:"environmental_entity",selector:{entity:{domain:ki,device_class:Ei}}}]}
.data=${{environmental_entity:this._environmentalEntity}}
.computeLabel=${()=>r("environmental_entity_optional",e)}
.computeHelper=${()=>r("environmental_entity_helper",e)}
@@ -2573,6 +2660,82 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
gap: 8px;
padding: 2px 0;
}
.phase-def {
border: 1px solid var(--divider-color);
border-radius: 6px;
padding: 8px;
margin: 6px 0;
}
.phase-def-head {
display: flex;
align-items: center;
gap: 8px;
}
.phase-def-head ms-textfield {
flex: 1;
min-width: 0;
}
.phase-part {
max-width: 160px;
padding: 6px;
border: 1px solid var(--divider-color);
border-radius: 4px;
background: var(--card-background-color);
color: var(--primary-text-color);
}
.phase-qty {
width: 64px;
padding: 6px;
border: 1px solid var(--divider-color);
border-radius: 4px;
background: var(--card-background-color);
color: var(--primary-text-color);
}
.phase-checklist {
min-height: 56px;
margin-top: 6px;
}
.phase-remove {
--mdc-icon-button-size: 36px;
color: var(--secondary-text-color);
}
.phase-seq-label {
font-size: 12px;
color: var(--secondary-text-color);
margin-top: 8px;
}
.phase-seq {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
padding: 4px 0;
}
.phase-chip {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 12px;
background: var(--secondary-background-color);
font-size: 13px;
}
.phase-chip-x {
border: none;
background: none;
color: var(--secondary-text-color);
cursor: pointer;
padding: 0 2px;
font-size: 12px;
}
.phase-seq-add {
padding: 4px 8px;
border: 1px dashed var(--divider-color);
border-radius: 12px;
background: transparent;
color: var(--secondary-text-color);
font-size: 13px;
}
.consumes-check {
display: flex;
align-items: center;
@@ -2750,7 +2913,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
font-size: 14px;
cursor: pointer;
}
`,c([b({attribute:!1})],g.prototype,"hass",2),c([b({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),c([b({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),c([b({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),c([b({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),c([h()],g.prototype,"parts",2),c([h()],g.prototype,"_foreignOwners",2),c([h()],g.prototype,"_open",2),c([h()],g.prototype,"_entityPickerFallback",2),c([h()],g.prototype,"_loading",2),c([h()],g.prototype,"_error",2),c([h()],g.prototype,"_entryId",2),c([h()],g.prototype,"_taskId",2),c([h()],g.prototype,"_objectChoices",2),c([h()],g.prototype,"_name",2),c([h()],g.prototype,"_type",2),c([h()],g.prototype,"_scheduleType",2),c([h()],g.prototype,"_intervalDays",2),c([h()],g.prototype,"_intervalUnit",2),c([h()],g.prototype,"_dueDate",2),c([h()],g.prototype,"_warningDays",2),c([h()],g.prototype,"_earliestCompletionDays",2),c([h()],g.prototype,"_intervalAnchor",2),c([h()],g.prototype,"_weekdays",2),c([h()],g.prototype,"_nth",2),c([h()],g.prototype,"_nthWeekday",2),c([h()],g.prototype,"_domDay",2),c([h()],g.prototype,"_domLastDay",2),c([h()],g.prototype,"_domBusiness",2),c([h()],g.prototype,"_calOffset",2),c([h()],g.prototype,"_seasonMonths",2),c([h()],g.prototype,"_endsMode",2),c([h()],g.prototype,"_endsCount",2),c([h()],g.prototype,"_endsUntil",2),c([h()],g.prototype,"_schedulePreview",2),c([h()],g.prototype,"_schedulePreviewEnded",2),c([h()],g.prototype,"_notes",2),c([h()],g.prototype,"_documentationUrl",2),c([h()],g.prototype,"_customIcon",2),c([h()],g.prototype,"_priority",2),c([h()],g.prototype,"_labels",2),c([h()],g.prototype,"_enabled",2),c([h()],g.prototype,"_triggerEntityId",2),c([h()],g.prototype,"_triggerEntityIds",2),c([h()],g.prototype,"_triggerEntityLogic",2),c([h()],g.prototype,"_triggerAttribute",2),c([h()],g.prototype,"_triggerType",2),c([h()],g.prototype,"_triggerAbove",2),c([h()],g.prototype,"_triggerBelow",2),c([h()],g.prototype,"_triggerEquals",2),c([h()],g.prototype,"_triggerNotEquals",2),c([h()],g.prototype,"_triggerForMinutes",2),c([h()],g.prototype,"_triggerCombinator",2),c([h()],g.prototype,"_triggerTargetValue",2),c([h()],g.prototype,"_triggerDeltaMode",2),c([h()],g.prototype,"_triggerBaselineValue",2),c([h()],g.prototype,"_liveBaselineValue",2),c([h()],g.prototype,"_autoCompleteOnRecovery",2),c([h()],g.prototype,"_triggerFromState",2),c([h()],g.prototype,"_triggerToState",2),c([h()],g.prototype,"_triggerTargetChanges",2),c([h()],g.prototype,"_triggerRuntimeHours",2),c([h()],g.prototype,"_triggerOnStates",2),c([h()],g.prototype,"_compoundLogic",2),c([h()],g.prototype,"_compoundConditions",2),c([h()],g.prototype,"_suggestedAttributes",2),c([h()],g.prototype,"_availableAttributes",2),c([h()],g.prototype,"_entityDomain",2),c([h()],g.prototype,"_lastPerformed",2),c([h()],g.prototype,"_nfcTagId",2),c([h()],g.prototype,"_readingUnit",2),c([h()],g.prototype,"_consumesParts",2),c([h()],g.prototype,"_partsLoadFailed",2),c([h()],g.prototype,"_availableTags",2),c([h()],g.prototype,"_responsibleUserId",2),c([h()],g.prototype,"_assigneePool",2),c([h()],g.prototype,"_rotationStrategy",2),c([h()],g.prototype,"_availableUsers",2),c([h()],g.prototype,"_checklistText",2),c([h()],g.prototype,"_requiredCompletion",2),c([h()],g.prototype,"_scheduleTime",2),c([h()],g.prototype,"_actionService",2),c([h()],g.prototype,"_actionTargetEntity",2),c([h()],g.prototype,"_actionData",2),c([h()],g.prototype,"_actionDataJsonFallback",2),c([h()],g.prototype,"_actionTesting",2),c([h()],g.prototype,"_actionTestResult",2),c([h()],g.prototype,"_actionTestError",2),c([h()],g.prototype,"_qcNotes",2),c([h()],g.prototype,"_qcCost",2),c([h()],g.prototype,"_qcDuration",2),c([h()],g.prototype,"_qcFeedback",2),c([h()],g.prototype,"_environmentalEntity",2),c([h()],g.prototype,"_environmentalAttribute",2),c([h()],g.prototype,"_adaptiveEnabled",2),c([h()],g.prototype,"_adaptiveAlpha",2),c([h()],g.prototype,"_adaptiveMin",2),c([h()],g.prototype,"_adaptiveMax",2),c([h()],g.prototype,"_adaptiveSeasonal",2),c([h()],g.prototype,"_adaptivePrediction",2),c([h()],g.prototype,"_conditionAttrOptions",2);$t=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",$t)});var U,Si=w(()=>{"use strict";P();O();R();ie();U=class extends S{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return j(this.hass)}openEdit(e){this._draft={...e},this._originalSnapshot={...e},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let e=this._draft;if(e)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),i=[];for(let l of t.part Line truncated
`,c([x({attribute:!1})],g.prototype,"hass",2),c([x({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),c([x({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),c([x({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),c([x({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),c([h()],g.prototype,"parts",2),c([h()],g.prototype,"_foreignOwners",2),c([h()],g.prototype,"_open",2),c([h()],g.prototype,"_entityPickerFallback",2),c([h()],g.prototype,"_loading",2),c([h()],g.prototype,"_error",2),c([h()],g.prototype,"_entryId",2),c([h()],g.prototype,"_taskId",2),c([h()],g.prototype,"_objectChoices",2),c([h()],g.prototype,"_name",2),c([h()],g.prototype,"_type",2),c([h()],g.prototype,"_scheduleType",2),c([h()],g.prototype,"_intervalDays",2),c([h()],g.prototype,"_intervalUnit",2),c([h()],g.prototype,"_dueDate",2),c([h()],g.prototype,"_warningDays",2),c([h()],g.prototype,"_earliestCompletionDays",2),c([h()],g.prototype,"_intervalAnchor",2),c([h()],g.prototype,"_weekdays",2),c([h()],g.prototype,"_nth",2),c([h()],g.prototype,"_nthWeekday",2),c([h()],g.prototype,"_domDay",2),c([h()],g.prototype,"_domLastDay",2),c([h()],g.prototype,"_domBusiness",2),c([h()],g.prototype,"_calOffset",2),c([h()],g.prototype,"_seasonMonths",2),c([h()],g.prototype,"_endsMode",2),c([h()],g.prototype,"_endsCount",2),c([h()],g.prototype,"_endsUntil",2),c([h()],g.prototype,"_schedulePreview",2),c([h()],g.prototype,"_schedulePreviewEnded",2),c([h()],g.prototype,"_notes",2),c([h()],g.prototype,"_documentationUrl",2),c([h()],g.prototype,"_customIcon",2),c([h()],g.prototype,"_priority",2),c([h()],g.prototype,"_labels",2),c([h()],g.prototype,"_enabled",2),c([h()],g.prototype,"_triggerEntityId",2),c([h()],g.prototype,"_triggerEntityIds",2),c([h()],g.prototype,"_triggerEntityLogic",2),c([h()],g.prototype,"_triggerAttribute",2),c([h()],g.prototype,"_triggerType",2),c([h()],g.prototype,"_triggerAbove",2),c([h()],g.prototype,"_triggerBelow",2),c([h()],g.prototype,"_triggerEquals",2),c([h()],g.prototype,"_triggerNotEquals",2),c([h()],g.prototype,"_triggerForMinutes",2),c([h()],g.prototype,"_triggerCombinator",2),c([h()],g.prototype,"_triggerTargetValue",2),c([h()],g.prototype,"_triggerDeltaMode",2),c([h()],g.prototype,"_triggerBaselineValue",2),c([h()],g.prototype,"_liveBaselineValue",2),c([h()],g.prototype,"_autoCompleteOnRecovery",2),c([h()],g.prototype,"_triggerFromState",2),c([h()],g.prototype,"_triggerToState",2),c([h()],g.prototype,"_triggerTargetChanges",2),c([h()],g.prototype,"_triggerRuntimeHours",2),c([h()],g.prototype,"_triggerOnStates",2),c([h()],g.prototype,"_compoundLogic",2),c([h()],g.prototype,"_compoundConditions",2),c([h()],g.prototype,"_suggestedAttributes",2),c([h()],g.prototype,"_availableAttributes",2),c([h()],g.prototype,"_entityDomain",2),c([h()],g.prototype,"_lastPerformed",2),c([h()],g.prototype,"_nfcTagId",2),c([h()],g.prototype,"_readingUnit",2),c([h()],g.prototype,"_consumesParts",2),c([h()],g.prototype,"_partsLoadFailed",2),c([h()],g.prototype,"_availableTags",2),c([h()],g.prototype,"_responsibleUserId",2),c([h()],g.prototype,"_assigneePool",2),c([h()],g.prototype,"_rotationStrategy",2),c([h()],g.prototype,"_availableUsers",2),c([h()],g.prototype,"_checklistText",2),c([h()],g.prototype,"_phaseDefs",2),c([h()],g.prototype,"_phaseSeq",2),c([h()],g.prototype,"_requiredCompletion",2),c([h()],g.prototype,"_scheduleTime",2),c([h()],g.prototype,"_actionService",2),c([h()],g.prototype,"_actionTargetEntity",2),c([h()],g.prototype,"_actionData",2),c([h()],g.prototype,"_actionDataJsonFallback",2),c([h()],g.prototype,"_actionTesting",2),c([h()],g.prototype,"_actionTestResult",2),c([h()],g.prototype,"_actionTestError",2),c([h()],g.prototype,"_qcNotes",2),c([h()],g.prototype,"_qcCost",2),c([h()],g.prototype,"_qcDuration",2),c([h()],g.prototype,"_qcFeedback",2),c([h()],g.prototype,"_environmentalEntity",2),c([h()],g.prototype,"_environmentalAttribute",2),c([h()],g.prototype,"_adaptiveEnabled",2),c([h()],g.prototype,"_adaptiveAlpha",2),c([h()],g.prototype,"_adaptiveMin",2),c([h()],g.prototype,"_adaptiveMax",2),c([h()],g.prototype,"_adaptiveSeasonal",2),c([h()],g.prototype,"_adaptivePrediction",2),c([h()],g.prototype,"_conditionAttrOptions",2);St=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",St)});var U,Ci=w(()=>{"use strict";L();M();j();se();U=class extends S{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return R(this.hass)}openEdit(e){this._draft={...e},this._originalSnapshot={...e},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let e=this._draft;if(e)try{let t=await this.hass.connection.sendMessagePromise( Line truncated
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
<h2>${r("history_edit_title",e)||"Edit history entry"}</h2>
@@ -2888,7 +3051,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.part-row-edit input[type="checkbox"] { width: auto; }
.part-label { flex: 1; color: var(--primary-text-color); }
.part-qty { width: 76px; }
`,c([b({attribute:!1})],U.prototype,"hass",2),c([h()],U.prototype,"_open",2),c([h()],U.prototype,"_saving",2),c([h()],U.prototype,"_error",2),c([h()],U.prototype,"_draft",2),c([h()],U.prototype,"_partOptions",2),c([h()],U.prototype,"_partQty",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",U)});function ge(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Ai(a){return!a.startsWith("data:image/svg+xml,")&&!a.startsWith("data:image/png;base64,")?"":ge(a)}function Ws(a){return a.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var M,Ti=w(()=>{"use strict";P();O();R();ut();M=class extends S{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,t){this._entryId=e,this._taskId=null,this._objectName=t,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,t,i,n){this._entryId=e,this._taskId=t,this._objectName=i,this._taskName=n,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let t={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(t.task_id=this._taskId);let i=[this.hass.connection.sendMessagePromise({...t,action:"view"})];this._taskId&&i.push(this.hass.connection.sendMessagePromise({...t,action:"complete"}));let n=await Promise.all(i);if(e!==this._generateSeq)return;this._viewResult=n[0],n.length>1&&(this._completeResult=n[1])}catch(t){if(e!==this._generateSeq)return;let i=t?.code,n=t?.message;this._error=i==="no_url"||typeof n=="string"&&n.includes("No Home Assistant URL")?r("qr_error_no_url",this.lang):r("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,t=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,i=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),n=window.open("","_blank","width=600,height=500");if(!n)return;let l=this.lang||"en",d=ge(t),p=ge(i),_=!!this._completeResult,v=ge(r("qr_action_view",l)),m=ge(r("qr_action_complete",l));n.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
`,c([x({attribute:!1})],U.prototype,"hass",2),c([h()],U.prototype,"_open",2),c([h()],U.prototype,"_saving",2),c([h()],U.prototype,"_error",2),c([h()],U.prototype,"_draft",2),c([h()],U.prototype,"_partOptions",2),c([h()],U.prototype,"_partQty",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",U)});function me(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Ii(a){return!a.startsWith("data:image/svg+xml,")&&!a.startsWith("data:image/png;base64,")?"":me(a)}function Js(a){return a.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var D,Pi=w(()=>{"use strict";L();M();j();gt();D=class extends S{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,t){this._entryId=e,this._taskId=null,this._objectName=t,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,t,i,n){this._entryId=e,this._taskId=t,this._objectName=i,this._taskName=n,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let t={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(t.task_id=this._taskId);let i=[this.hass.connection.sendMessagePromise({...t,action:"view"})];this._taskId&&i.push(this.hass.connection.sendMessagePromise({...t,action:"complete"}));let n=await Promise.all(i);if(e!==this._generateSeq)return;this._viewResult=n[0],n.length>1&&(this._completeResult=n[1])}catch(t){if(e!==this._generateSeq)return;let i=t?.code,n=t?.message;this._error=i==="no_url"||typeof n=="string"&&n.includes("No Home Assistant URL")?r("qr_error_no_url",this.lang):r("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,t=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,i=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),n=window.open("","_blank","width=600,height=500");if(!n)return;let l=this.lang||"en",d=me(t),p=me(i),_=!!this._completeResult,v=me(r("qr_action_view",l)),m=me(r("qr_action_complete",l));n.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="color-scheme" content="light">
<title>${d}</title>
<style>
@@ -2910,17 +3073,17 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
${p?`<div class="sub">${p}</div>`:""}
<div class="qr-row">
<div class="qr-col">
<img src="${Ai(this._viewResult.svg_data_uri)}" alt="QR Info" />
<img src="${Ii(this._viewResult.svg_data_uri)}" alt="QR Info" />
<div class="qr-label">${v}</div>
</div>
${_?`<div class="qr-col">
<img src="${Ai(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<img src="${Ii(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${m}</div>
</div>`:""}
</div>
<div class="url">${ge(this._viewResult.url)}</div>
<div class="url">${me(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
</body></html>`),n.document.close()}_downloadSvg(e,t){let i=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),n=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;gi(i,`qr-${Ws(n)}-${t}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en",t=this._taskName?`${r("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${r("qr_code",e)}: ${this._objectName}`,i=!!this._viewResult;return o`
</body></html>`),n.document.close()}_downloadSvg(e,t){let i=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),n=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;vi(i,`qr-${Js(n)}-${t}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en",t=this._taskName?`${r("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${r("qr_code",e)}: ${this._objectName}`,i=!!this._viewResult;return o`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t}</div>
<div class="content">
@@ -2981,7 +3144,7 @@ ${p?`<div class="sub">${p}</div>`:""}
</ha-button>
</div>
</ha-dialog>
`}};M.styles=A`
`}};D.styles=A`
.dialog-title {
font-size: 18px;
font-weight: 500;
@@ -3099,26 +3262,26 @@ ${p?`<div class="sub">${p}</div>`:""}
color: var(--text-primary-color, #fff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
`,c([b({attribute:!1})],M.prototype,"hass",2),c([b()],M.prototype,"lang",2),c([h()],M.prototype,"_open",2),c([h()],M.prototype,"_loading",2),c([h()],M.prototype,"_error",2),c([h()],M.prototype,"_viewResult",2),c([h()],M.prototype,"_completeResult",2),c([h()],M.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",M)});var Ci=w(()=>{"use strict"});function Ii(a){let s=a.getFullYear(),e=String(a.getMonth()+1).padStart(2,"0"),t=String(a.getDate()).padStart(2,"0");return`${s}-${e}-${t}`}var Li=w(()=>{"use strict";Ci()});function Pi(a,s){let e=a.interval_analysis,t=e?.weibull_beta,i=e?.weibull_eta;if(t==null||i==null||i<=0)return u;let n=a.interval_days??0,l=a.suggested_interval??n;return o`
`,c([x({attribute:!1})],D.prototype,"hass",2),c([x()],D.prototype,"lang",2),c([h()],D.prototype,"_open",2),c([h()],D.prototype,"_loading",2),c([h()],D.prototype,"_error",2),c([h()],D.prototype,"_viewResult",2),c([h()],D.prototype,"_completeResult",2),c([h()],D.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",D)});var Li=w(()=>{"use strict"});function Ri(a){let s=a.getFullYear(),e=String(a.getMonth()+1).padStart(2,"0"),t=String(a.getDate()).padStart(2,"0");return`${s}-${e}-${t}`}var ji=w(()=>{"use strict";Li()});function Ni(a,s){let e=a.interval_analysis,t=e?.weibull_beta,i=e?.weibull_eta;if(t==null||i==null||i<=0)return u;let n=a.interval_days??0,l=a.suggested_interval??n;return o`
<div class="weibull-section">
<div class="weibull-title">
<ha-svg-icon aria-hidden="true" path="M3,14L3.5,14.07L8.07,9.5C7.89,8.85 8.06,8.11 8.59,7.59C9.37,6.8 10.63,6.8 11.41,7.59C11.94,8.11 12.11,8.85 11.93,9.5L14.5,12.07L15,12C15.18,12 15.35,12 15.5,12.07L19.07,8.5C19,8.35 19,8.18 19,8A2,2 0 0,1 21,6A2,2 0 0,1 23,8A2,2 0 0,1 21,10C20.82,10 20.65,10 20.5,9.93L16.93,13.5C17,13.65 17,13.82 17,14A2,2 0 0,1 15,16A2,2 0 0,1 13,14L13.07,13.5L10.5,10.93C10.18,11 9.82,11 9.5,10.93L4.93,15.5L5,16A2,2 0 0,1 3,18A2,2 0 0,1 1,16A2,2 0 0,1 3,14Z"></ha-svg-icon>
${r("weibull_reliability_curve",s)}
${Ks(t,s)}
${Zs(t,s)}
</div>
${Gs(t,i,n,l,s)}
${Ys(e,s)}
${e?.confidence_interval_low!=null?Qs(e,a,s):u}
${Xs(t,i,n,l,s)}
${er(e,s)}
${e?.confidence_interval_low!=null?tr(e,a,s):u}
</div>
`}function Ks(a,s){let e,t,i;return a<.8?(e="early_failures",t="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",i="beta_early_failures"):a<=1.2?(e="random_failures",t="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",i="beta_random_failures"):a<=3.5?(e="wear_out",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",i="beta_wear_out"):(e="highly_predictable",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",i="beta_highly_predictable"),o`
`}function Zs(a,s){let e,t,i;return a<.8?(e="early_failures",t="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",i="beta_early_failures"):a<=1.2?(e="random_failures",t="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",i="beta_random_failures"):a<=3.5?(e="wear_out",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",i="beta_wear_out"):(e="highly_predictable",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",i="beta_highly_predictable"),o`
<span class="beta-badge ${e}">
<ha-svg-icon path="${t}"></ha-svg-icon>
${r(i,s)} (\u03B2=${a.toFixed(2)})
</span>
`}function Gs(a,s,e,t,i){let x=Math.max(e,t,s,1)*1.3,f=50,$=[];for(let F=0;F<=f;F++){let B=F/f*x,Qi=1-Math.exp(-Math.pow(B/s,a)),Ji=32+B/x*260,Zi=136-Qi*128;$.push([Ji,Zi])}let z=$.map(([F,B])=>`${F.toFixed(1)},${B.toFixed(1)}`).join(" "),L="M32,136 "+$.map(([F,B])=>`L${F.toFixed(1)},${B.toFixed(1)}`).join(" ")+` L${$[f][0].toFixed(1)},136 Z`,E=32+e/x*260,N=1-Math.exp(-Math.pow(e/s,a)),Le=136-N*128,Gi=((1-N)*100).toFixed(0),At=32+t/x*260,Yi=[0,.25,.5,.75,1];return o`
`}function Xs(a,s,e,t,i){let y=Math.max(e,t,s,1)*1.3,f=50,$=[];for(let F=0;F<=f;F++){let B=F/f*y,Xi=1-Math.exp(-Math.pow(B/s,a)),es=32+B/y*260,ts=136-Xi*128;$.push([es,ts])}let H=$.map(([F,B])=>`${F.toFixed(1)},${B.toFixed(1)}`).join(" "),P="M32,136 "+$.map(([F,B])=>`L${F.toFixed(1)},${B.toFixed(1)}`).join(" ")+` L${$[f][0].toFixed(1)},136 Z`,E=32+e/y*260,N=1-Math.exp(-Math.pow(e/s,a)),Re=136-N*128,Ji=((1-N)*100).toFixed(0),It=32+t/y*260,Zi=[0,.25,.5,.75,1];return o`
<div class="weibull-chart">
<svg viewBox="0 0 ${300} ${160}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${r("chart_weibull",i)}">
${Yi.map(F=>{let B=136-F*128;return pe`
${Zi.map(F=>{let B=136-F*128;return he`
<line x1="${32}" y1="${B.toFixed(1)}" x2="${292}" y2="${B.toFixed(1)}"
stroke="var(--divider-color)" stroke-width="0.5" stroke-dasharray="${F===.5?"4,3":u}" />
<text x="${28}" y="${(B+3).toFixed(1)}" fill="var(--secondary-text-color)"
@@ -3126,24 +3289,24 @@ ${p?`<div class="sub">${p}</div>`:""}
`})}
<text x="${32}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">0</text>
<text x="${324/2}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(x/2)}</text>
<text x="${292}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(x)}</text>
<text x="${324/2}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(y/2)}</text>
<text x="${292}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(y)}</text>
<path d="${L}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
<polyline points="${z}" fill="none"
<path d="${P}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
<polyline points="${H}" fill="none"
stroke="var(--primary-color, #03a9f4)" stroke-width="2" />
${e>0?pe`
${e>0?he`
<line x1="${E.toFixed(1)}" y1="${8}" x2="${E.toFixed(1)}" y2="${136 .toFixed(1)}"
stroke="var(--primary-color, #03a9f4)" stroke-width="1.5" stroke-dasharray="4,3" />
<circle cx="${E.toFixed(1)}" cy="${Le.toFixed(1)}" r="3"
<circle cx="${E.toFixed(1)}" cy="${Re.toFixed(1)}" r="3"
fill="var(--primary-color, #03a9f4)" />
<text x="${(E+4).toFixed(1)}" y="${(Le-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)"
font-size="9" font-weight="600">R=${Gi}%</text>
<text x="${(E+4).toFixed(1)}" y="${(Re-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)"
font-size="9" font-weight="600">R=${Ji}%</text>
`:u}
${t>0&&t!==e?pe`
<line x1="${At.toFixed(1)}" y1="${8}" x2="${At.toFixed(1)}" y2="${136 .toFixed(1)}"
${t>0&&t!==e?he`
<line x1="${It.toFixed(1)}" y1="${8}" x2="${It.toFixed(1)}" y2="${136 .toFixed(1)}"
stroke="var(--success-color, #4caf50)" stroke-width="1.5" stroke-dasharray="4,3" />
`:u}
@@ -3158,7 +3321,7 @@ ${p?`<div class="sub">${p}</div>`:""}
${e>0?o`<span class="legend-item"><span class="legend-swatch" style="background:var(--primary-color, #03a9f4); opacity:0.5"></span> ${r("current_interval_marker",i)}</span>`:u}
${t>0&&t!==e?o`<span class="legend-item"><span class="legend-swatch" style="background:var(--success-color, #4caf50)"></span> ${r("recommended_marker",i)}</span>`:u}
</div>
`}function Ys(a,s){return o`
`}function er(a,s){return o`
<div class="weibull-info-row">
<div class="weibull-info-item">
<span>${r("characteristic_life",s)}</span>
@@ -3171,22 +3334,22 @@ ${p?`<div class="sub">${p}</div>`:""}
</div>
`:u}
</div>
`}function Qs(a,s,e){let t=a.confidence_interval_low,i=a.confidence_interval_high,n=s.suggested_interval??s.interval_days??0,l=s.interval_days??0,d=Math.max(0,t-5),_=i+5-d,v=(t-d)/_*100,m=(i-t)/_*100,y=(n-d)/_*100,x=l>0?(l-d)/_*100:-1;return o`
`}function tr(a,s,e){let t=a.confidence_interval_low,i=a.confidence_interval_high,n=s.suggested_interval??s.interval_days??0,l=s.interval_days??0,d=Math.max(0,t-5),_=i+5-d,v=(t-d)/_*100,m=(i-t)/_*100,b=(n-d)/_*100,y=l>0?(l-d)/_*100:-1;return o`
<div class="confidence-range">
<div class="confidence-range-title">
${r("confidence_interval",e)}: ${n} ${r("days",e)} (${t}\u2013${i})
</div>
<div class="confidence-bar">
<div class="confidence-fill" style="left:${v.toFixed(1)}%;width:${m.toFixed(1)}%"></div>
${x>=0?o`<div class="confidence-marker current" style="left:${x.toFixed(1)}%"></div>`:u}
<div class="confidence-marker recommended" style="left:${y.toFixed(1)}%"></div>
${y>=0?o`<div class="confidence-marker current" style="left:${y.toFixed(1)}%"></div>`:u}
<div class="confidence-marker recommended" style="left:${b.toFixed(1)}%"></div>
</div>
<div class="confidence-labels">
<span class="confidence-text low">${r("confidence_conservative",e)} (${t}${r("days",e).charAt(0)})</span>
<span class="confidence-text high">${r("confidence_aggressive",e)} (${i}${r("days",e).charAt(0)})</span>
</div>
</div>
`}var ji=w(()=>{"use strict";P();R()});function Ri(a,s,e){let t=a.degradation_trend!=null&&a.degradation_trend!=="insufficient_data",i=a.days_until_threshold!=null,n=a.environmental_factor!=null&&a.environmental_factor!==1;if(!t&&!i&&!n)return u;let l=a.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":a.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return o`
`}var qi=w(()=>{"use strict";L();j()});function Hi(a,s,e){let t=a.degradation_trend!=null&&a.degradation_trend!=="insufficient_data",i=a.days_until_threshold!=null,n=a.environmental_factor!=null&&a.environmental_factor!==1;if(!t&&!i&&!n)return u;let l=a.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":a.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return o`
<div class="prediction-section">
${a.sensor_prediction_urgency?o`
<div class="prediction-urgency-banner">
@@ -3221,12 +3384,12 @@ ${p?`<div class="sub">${p}</div>`:""}
<ha-svg-icon path="M15,13V5A3,3 0 0,0 12,2A3,3 0 0,0 9,5V13A5,5 0 0,0 7,17A5,5 0 0,0 12,22A5,5 0 0,0 17,17A5,5 0 0,0 15,13M12,4A1,1 0 0,1 13,5V8H11V5A1,1 0 0,1 12,4Z"></ha-svg-icon>
<span class="prediction-label">${r("environmental_adjustment",s)}</span>
<span class="prediction-value">${a.environmental_factor.toFixed(2)}x</span>
${a.environmental_entity?o`<span class="prediction-entity entity-link" @click=${d=>ui(d,a.environmental_entity)}>${a.environmental_entity}</span>`:u}
${a.environmental_entity?o`<span class="prediction-entity entity-link" @click=${d=>mi(d,a.environmental_entity)}>${a.environmental_entity}</span>`:u}
</div>
`:u}
</div>
</div>
`}var Ni=w(()=>{"use strict";P();R()});function Hi(a,s,e,t){let i=Math.max(a||1,s);return o`
`}var Oi=w(()=>{"use strict";L();j()});function Mi(a,s,e,t){let i=Math.max(a||1,s);return o`
<div class="interval-comparison">
<div class="interval-bar">
<div class="interval-label">
@@ -3244,7 +3407,7 @@ ${p?`<div class="sub">${p}</div>`:""}
style="width: ${Math.min(s/i*100,100)}%"></div>
</div>
</div>
`}var qi=w(()=>{"use strict";P();R()});function Mi(a,s,e){if(!e.seasonal||!a.seasonal_factor||a.seasonal_factor===1)return u;let t=Oi.map(d=>r(d,s)),i=new Date().getMonth(),n=a.seasonal_factors||a.interval_analysis?.seasonal_factors||null,l=n&&n.length===12?n:t.map((d,p)=>{let _=a.seasonal_factor||1,v=Math.sin((p-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,_+v))});return o`
`}var Di=w(()=>{"use strict";L();j()});function Fi(a,s,e){if(!e.seasonal||!a.seasonal_factor||a.seasonal_factor===1)return u;let t=zi.map(d=>r(d,s)),i=new Date().getMonth(),n=a.seasonal_factors||a.interval_analysis?.seasonal_factors||null,l=n&&n.length===12?n:t.map((d,p)=>{let _=a.seasonal_factor||1,v=Math.sin((p-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,_+v))});return o`
<div class="seasonal-card-compact">
<h4>${r("seasonal_awareness",s)}</h4>
<div class="seasonal-mini-chart">
@@ -3261,7 +3424,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<span class="legend-item"><span class="dot high"></span> ${r("longer",s)||"L\xE4nger"}</span>
</div>
</div>
`}function Di(a,s){return Js(a,s)}function Js(a,s){let e=a.seasonal_factors??a.interval_analysis?.seasonal_factors;if(!e||e.length!==12)return u;let t=a.interval_analysis?.seasonal_reason,i=new Date().getMonth(),n=300,l=100,d=8,_=l-d-4,v=Math.max(...e,1.5),m=n/12,y=m*.65,x=d+_-1/v*_;return o`
`}function Ui(a,s){return ir(a,s)}function ir(a,s){let e=a.seasonal_factors??a.interval_analysis?.seasonal_factors;if(!e||e.length!==12)return u;let t=a.interval_analysis?.seasonal_reason,i=new Date().getMonth(),n=300,l=100,d=8,_=l-d-4,v=Math.max(...e,1.5),m=n/12,b=m*.65,y=d+_-1/v*_;return o`
<div class="seasonal-chart">
<div class="seasonal-chart-title">
<ha-svg-icon aria-hidden="true" path="M17.75 4.09L15.22 6.03L16.13 9.09L13.5 7.28L10.87 9.09L11.78 6.03L9.25 4.09L12.44 4L13.5 1L14.56 4L17.75 4.09M21.25 11L19.61 12.25L20.2 14.23L18.5 13.06L16.8 14.23L17.39 12.25L15.75 11L17.81 10.95L18.5 9L19.19 10.95L21.25 11M18.97 15.95C19.8 15.87 20.69 17.05 20.16 17.8C19.84 18.25 19.5 18.67 19.08 19.07C15.17 23 8.84 23 4.94 19.07C1.03 15.17 1.03 8.83 4.94 4.93C5.34 4.53 5.76 4.17 6.21 3.85C6.96 3.32 8.14 4.21 8.06 5.04C7.79 7.9 8.75 10.87 10.95 13.06C13.14 15.26 16.1 16.22 18.97 15.95Z"></ha-svg-icon>
@@ -3269,22 +3432,22 @@ ${p?`<div class="sub">${p}</div>`:""}
${t?o`<span class="source-tag">${t==="learned"?r("seasonal_learned",s):r("seasonal_manual",s)}</span>`:u}
</div>
<svg viewBox="0 0 ${n} ${l}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${r("chart_seasonal",s)}">
<line x1="0" y1="${x.toFixed(1)}" x2="${n}" y2="${x.toFixed(1)}"
<line x1="0" y1="${y.toFixed(1)}" x2="${n}" y2="${y.toFixed(1)}"
stroke="var(--divider-color)" stroke-width="1" stroke-dasharray="4,3" />
${e.map((f,$)=>{let z=f/v*_,L=$*m+(m-y)/2,E=d+_-z,N=$===i,Le=f<1?"var(--success-color, #4caf50)":f>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return pe`
<rect x="${L.toFixed(1)}" y="${E.toFixed(1)}"
width="${y.toFixed(1)}" height="${z.toFixed(1)}"
fill="${Le}" opacity="${N?1:.5}" rx="2" />
${e.map((f,$)=>{let H=f/v*_,P=$*m+(m-b)/2,E=d+_-H,N=$===i,Re=f<1?"var(--success-color, #4caf50)":f>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return he`
<rect x="${P.toFixed(1)}" y="${E.toFixed(1)}"
width="${b.toFixed(1)}" height="${H.toFixed(1)}"
fill="${Re}" opacity="${N?1:.5}" rx="2" />
`})}
</svg>
<div class="seasonal-labels">
${Oi.map((f,$)=>o`<span class="seasonal-label ${$===i?"active-month":""}">${r(f,s)}</span>`)}
${zi.map((f,$)=>o`<span class="seasonal-label ${$===i?"active-month":""}">${r(f,s)}</span>`)}
</div>
</div>
`}var Oi,zi=w(()=>{"use strict";P();R();Oi=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"]});var I,Fi=w(()=>{"use strict";P();O();R();ie();Li();Ie();ji();Ni();qi();zi();I=class extends S{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1}get _lang(){return j(this.hass)}async openFor(e,t){this._entryId=e,this._taskId=t,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=Ii(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});e?.features&&(this._features={...this._features,...e.features}),this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=e.object?.name||"";let t=(e.tasks||[]).find(i=>i.id===this._taskId);this._task=t??null}catch(e){this._error=T(e,this._lang)}}async _runWs(e){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(e),this._busy=!1,!0}catch(t){return this._error=T(t,this._lang),this._busy=!1,!1}}_notifyChanged(e){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:e},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(K(),Z)).then(async({openCompleteDialog:e})=>{let t=this._task,i=!!t.part_ref,n=[];if(!i)try{let d=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0});n=Ve(t,this._entryId,d.objects||[],this._lang)}catch{}e({entry_id:this._entryId,task_id:this._taskId,task_name:t.name,checklist:t.checklist||[],adaptive_enabled:!!t.adaptive_config?.enabled,required_completion_fields:t.required_completion_fields||[],task_type:t.type||"",reading_unit:t.reading_unit||"",parts:n,consumes_parts:i?[]:t.consumes_parts||[]})&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||Promise.resolve().then(()=>(K(),Z)).then(({openEditTaskDialog:e})=>{e(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(K(),Z)).then(({openQrDialog:e})=>{e({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let e=r("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(e))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let e=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",e),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=r("suggestion_applied",this._lang)||"Applied",this._notifyChanged("apply_suggestion"),await this._loadTask(),setTimeout(()=>{this._toast=""},2500))}async _reanalyzeInterval( Line truncated
`}var zi,Bi=w(()=>{"use strict";L();j();zi=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"]});var I,Vi=w(()=>{"use strict";L();M();j();se();ji();Pe();vt();qi();Oi();Di();Bi();I=class extends S{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1}get _lang(){return R(this.hass)}async openFor(e,t){this._entryId=e,this._taskId=t,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=Ri(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});e?.features&&(this._features={...this._features,...e.features}),this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=e.object?.name||"";let t=(e.tasks||[]).find(i=>i.id===this._taskId);this._task=t??null}catch(e){this._error=T(e,this._lang)}}async _runWs(e){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(e),this._busy=!1,!0}catch(t){return this._error=T(t,this._lang),this._busy=!1,!1}}_notifyChanged(e){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:e},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(K(),Z)).then(async({openCompleteDialog:e})=>{let t=this._task,i=!!t.part_ref,n=Le(t),l=n?n.consumesParts:t.consumes_parts||[],d=[];if(!i)try{let _=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0});d=Ke({consumes_parts:l},this._entryId,_.objects||[],this._lang)}catch{}e({entry_id:this._entryId,task_id:this._taskId,task_name:t.name,checklist:n?n.checklist:t.checklist||[],adaptive_enabled:!!t.adaptive_config?.enabled,required_completion_fields:n?n.requiredFields:t.required_completion_fields||[],task_type:t.type||"",reading_unit:t.reading_unit||"",parts:d,consumes_parts:i?[]:l,phase_label:ie(t)})&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||Promise.resolve().then(()=>(K(),Z)).then(({openEditTaskDialog:e})=>{e(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(K(),Z)).then(({openQrDialog:e})=>{e({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let e=r("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(e))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let e=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",e),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=r("suggestion_applied",this._lang)||"Applied",this._notifyChanged("app Line truncated
<div class="recommendation-card">
<h4>${r("suggested_interval",t)}</h4>
${Hi(e.interval_days,e.suggested_interval,e.interval_confidence||"medium",t)}
${Mi(e.interval_days,e.suggested_interval,e.interval_confidence||"medium",t)}
<div class="recommendation-actions">
<button class="btn primary"
@click=${this._applySuggestion} ?disabled=${this._busy}>
@@ -3304,11 +3467,11 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="adaptive-stack">
${this._toast?o`<div class="toast">${this._toast}</div>`:u}
${i?this._renderRecommendation(e):u}
${n?Ri(e,t,this._features):u}
${l?Pi(e,t):u}
${n?Hi(e,t,this._features):u}
${l?Ni(e,t):u}
${d?o`
${Mi(e,t,this._features)}
${e.seasonal_factors?.length===12||e.interval_analysis?.seasonal_factors?.length===12?Di(e,t):u}
${Fi(e,t,this._features)}
${e.seasonal_factors?.length===12||e.interval_analysis?.seasonal_factors?.length===12?Ui(e,t):u}
`:u}
</div>
`}_renderDetails(e){let t=this._lang,i=e.history||[],n=i.filter(p=>p.type==="completed"),l=n.reduce((p,_)=>p+(typeof _.cost=="number"?_.cost:0),0),d=(()=>{let p=n.map(_=>typeof _.duration=="number"?_.duration:null).filter(_=>_!=null);return p.length?Math.round(p.reduce((_,v)=>_+v,0)/p.length):null})();return o`
@@ -3337,7 +3500,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="history-entry">
<div class="history-line">
<span class="history-type type-${p.type}">${r(p.type,t)}</span>
<span class="history-date">${pi(p.timestamp,t)}</span>
<span class="history-date">${_i(p.timestamp,t)}</span>
${_?o`<button class="history-edit"
title="${r("history_edit_button",t)||"Edit"}"
@click=${()=>this._onEditHistoryEntry(p)}>
@@ -3361,7 +3524,7 @@ ${p?`<div class="sub">${p}</div>`:""}
${t?o`
<div class="header">
<div class="title">
<span class="status-dot" style="background: ${oe[t.status]||"#ccc"}"></span>
<span class="status-dot" style="background: ${le[t.status]||"#ccc"}"></span>
<span class="task-name">${t.name}</span>
</div>
<div class="object">
@@ -3370,7 +3533,8 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="quick-info">
${t.next_due?o`<span><strong>${r("next_due",e)||"Next due"}:</strong> ${Q(t.next_due,e)}</span>`:u}
${t.last_performed?o`<span><strong>${r("last_performed",e)||"Last"}:</strong> ${Q(t.last_performed,e)}</span>`:u}
${t.schedule?.kind&&!["manual","one_time"].includes(t.schedule.kind)||t.interval_days!=null?o`<span><strong>${r("interval",e)||"Interval"}:</strong> ${hi(t,e)}</span>`:u}
${t.schedule?.kind&&!["manual","one_time"].includes(t.schedule.kind)||t.interval_days!=null?o`<span><strong>${r("interval",e)||"Interval"}:</strong> ${gi(t,e)}</span>`:u}
${ie(t)?o`<span><strong>${r("phase_current",e)}:</strong> ${ie(t)}</span>`:u}
</div>
</div>
@@ -3462,7 +3626,7 @@ ${p?`<div class="sub">${p}</div>`:""}
`}
`:o`<div class="loading">${r("loading",e)||"Loading\u2026"}</div>`}
</div>
`}};I.styles=[Fe,A`
`}};I.styles=[Be,A`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0; z-index: 100;
@@ -3642,7 +3806,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.weibull-section, .seasonal-card-compact { max-width: 100%; }
.weibull-chart svg { max-width: 100%; height: auto; }
.details-toggle { gap: 12px; flex-wrap: wrap; }
`],c([b({attribute:!1})],I.prototype,"hass",2),c([h()],I.prototype,"_open",2),c([h()],I.prototype,"_entryId",2),c([h()],I.prototype,"_taskId",2),c([h()],I.prototype,"_task",2),c([h()],I.prototype,"_objectName",2),c([h()],I.prototype,"_busy",2),c([h()],I.prototype,"_error",2),c([h()],I.prototype,"_showSkip",2),c([h()],I.prototype,"_showReset",2),c([h()],I.prototype,"_showDetails",2),c([h()],I.prototype,"_showAdaptive",2),c([h()],I.prototype,"_skipReason",2),c([h()],I.prototype,"_resetDate",2),c([h()],I.prototype,"_features",2),c([h()],I.prototype,"_toast",2);customElements.get("maintenance-task-quick-actions-dialog")||customElements.define("maintenance-task-quick-actions-dialog",I)});var G,Ui=w(()=>{"use strict";P();_t();O();R();ie();G=class extends S{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._data=null;this._busy=!1;this._error=""}get _lang(){return j(this.hass)}async openFor(e){this._entryId=e,this._error="",this._open=!0,await this._load()}close(){this._open=!1,this._data=null,this._error=""}async _load(){if(this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._data=e}catch(e){this._error=T(e,this._lang)}}_onEditObject(){!this._entryId||!this._data||Promise.resolve().then(()=>(K(),Z)).then(({openEditObjectDialog:e})=>{e(this._entryId,this._data.object),this.close()})}_onAddTask(){this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openCreateTaskDialog:e})=>{e(this._entryId),this.close()})}async _onDelete(){if(!this._entryId||!this._data)return;let e=r("delete_object_confirm",this._lang)||`Delete "${this._data.object.name}" and all its tasks?`;if(window.confirm(e)){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/delete",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-deleted",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=T(t,this._lang)}finally{this._busy=!1}}}async _onArchiveObject(){if(!this._entryId||!this._data)return;let e=!!this._data.object.archived;if(!e){let t=r("confirm_archive_object",this._lang)||"Archive this object and its tasks?";if(!window.confirm(t))return}this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:e?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-changed",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=T(t,this._lang)}finally{this._busy=!1}}_onTaskClick(e){this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openTaskQuickActions:t})=>{t(this._entryId,e)})}render(){if(!this._open)return u;let e=this._lang,t=this._data,i=t?.object,n=t?.tasks||[],l=this.hass?.user?.is_admin??!0;return o`
`],c([x({attribute:!1})],I.prototype,"hass",2),c([h()],I.prototype,"_open",2),c([h()],I.prototype,"_entryId",2),c([h()],I.prototype,"_taskId",2),c([h()],I.prototype,"_task",2),c([h()],I.prototype,"_objectName",2),c([h()],I.prototype,"_busy",2),c([h()],I.prototype,"_error",2),c([h()],I.prototype,"_showSkip",2),c([h()],I.prototype,"_showReset",2),c([h()],I.prototype,"_showDetails",2),c([h()],I.prototype,"_showAdaptive",2),c([h()],I.prototype,"_skipReason",2),c([h()],I.prototype,"_resetDate",2),c([h()],I.prototype,"_features",2),c([h()],I.prototype,"_toast",2);customElements.get("maintenance-task-quick-actions-dialog")||customElements.define("maintenance-task-quick-actions-dialog",I)});var G,Wi=w(()=>{"use strict";L();mt();M();j();se();G=class extends S{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._data=null;this._busy=!1;this._error=""}get _lang(){return R(this.hass)}async openFor(e){this._entryId=e,this._error="",this._open=!0,await this._load()}close(){this._open=!1,this._data=null,this._error=""}async _load(){if(this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._data=e}catch(e){this._error=T(e,this._lang)}}_onEditObject(){!this._entryId||!this._data||Promise.resolve().then(()=>(K(),Z)).then(({openEditObjectDialog:e})=>{e(this._entryId,this._data.object),this.close()})}_onAddTask(){this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openCreateTaskDialog:e})=>{e(this._entryId),this.close()})}async _onDelete(){if(!this._entryId||!this._data)return;let e=r("delete_object_confirm",this._lang)||`Delete "${this._data.object.name}" and all its tasks?`;if(window.confirm(e)){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/delete",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-deleted",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=T(t,this._lang)}finally{this._busy=!1}}}async _onArchiveObject(){if(!this._entryId||!this._data)return;let e=!!this._data.object.archived;if(!e){let t=r("confirm_archive_object",this._lang)||"Archive this object and its tasks?";if(!window.confirm(t))return}this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:e?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-changed",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=T(t,this._lang)}finally{this._busy=!1}}_onTaskClick(e){this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openTaskQuickActions:t})=>{t(this._entryId,e)})}render(){if(!this._open)return u;let e=this._lang,t=this._data,i=t?.object,n=t?.tasks||[],l=this.hass?.user?.is_admin??!0;return o`
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
${t&&i?o`
@@ -3662,7 +3826,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="task-list">
${n.map(d=>o`
<div class="task-row" @click=${()=>this._onTaskClick(d.id)}>
<span class="status-dot" style="background: ${oe[d.status]||"#ccc"}"></span>
<span class="status-dot" style="background: ${le[d.status]||"#ccc"}"></span>
<span class="task-name">${d.name}</span>
<span class="task-status">${r(d.status||"ok",e)}</span>
</div>
@@ -3705,7 +3869,7 @@ ${p?`<div class="sub">${p}</div>`:""}
${i.map(([n,l])=>o`
<div class="meta-item">
<span class="meta-label">${n}</span>
<span class="meta-value">${Ue(l)?o`<a href="${l}" target="_blank" rel="noopener noreferrer">${l}</a>`:l}</span>
<span class="meta-value">${Ve(l)?o`<a href="${l}" target="_blank" rel="noopener noreferrer">${l}</a>`:l}</span>
</div>
`)}
</div>
@@ -3768,7 +3932,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.btn ha-icon { --mdc-icon-size: 16px; }
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); }
.error { padding: 8px; border-radius: 6px; background: rgba(211,47,47,0.1); color: var(--error-color); font-size: 13px; }
`,c([b({attribute:!1})],G.prototype,"hass",2),c([h()],G.prototype,"_open",2),c([h()],G.prototype,"_entryId",2),c([h()],G.prototype,"_data",2),c([h()],G.prototype,"_busy",2),c([h()],G.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",G)});var Z={};es(Z,{openCompleteDialog:()=>or,openCreateObjectDialog:()=>kt,openCreateTaskDialog:()=>Et,openEditObjectDialog:()=>rr,openEditTaskDialog:()=>ar,openHistoryEditDialog:()=>nr,openObjectQuickActions:()=>cr,openQrDialog:()=>lr,openTaskQuickActions:()=>St});function Ge(){return document.querySelector("home-assistant")?.hass}function sr(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function X(a){let s=sr(),e=s.querySelector(a)??document.body.querySelector(a);return e?e.parentNode!==s&&s.appendChild(e):(e=document.createElement(a),s.appendChild(e)),e}function ee(a){let s=Ge();if(!s)return!1;a.hass=s;let e=j(s);return pt(e)||Ce(e).then(()=>{a.requestUpdate?.()}),!0}function Ki(a){return Ke||(Ke=a.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(s=>({features:s.features??Bi.features,defaultWarningDays:s.general?.default_warning_days??7})).catch(()=>Bi),Ke)}function kt(){let a=X(Vi);return ee(a)?(a.openCreate(),!0):!1}function rr(a,s){let e=X(Vi);return ee(e)?(e.openEdit(a,s),!0):!1}function Et(a="",s){let e=X(Wi);if(!ee(e))return!1;let t=Ge();return t?((async()=>{let i=await Ki(t),n=e;n.checklistsEnabled=i.features.checklists,n.scheduleTimeEnabled=i.features.schedule_time,n.completionActionsEnabled=i.features.completion_actions,n.defaultWarningDays=i.defaultWarningDays,n.openCreate(a,s)})(),!0):!1}function ar(a,s){let e=X(Wi);if(!ee(e))return!1;let t=Ge();return t?((async()=>{try{let[i,n]=await Promise.all([t.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:a}),Ki(t)]),l=(i.tasks||[]).find(p=>p.id===s);if(!l){console.warn(`openEditTaskDialog: task ${s} not found in entry ${a}`);return}let d=e;d.checklistsEnabled=n.features.checklists,d.scheduleTimeEnabled=n.features.schedule_time,d.completionActionsEnabled=n.features.completion_actions,d.defaultWarningDays=n.defaultWarningDays,await d.openEdit(a,l)}catch(i){console.warn("openEditTaskDialog: failed to load task/features",i)}})(),!0):!1}function nr(a){let s=X(Zs);return ee(s)?(s.openEdit(a),!0):!1}function or(a){let s=X(Xs);return ee(s)?(s.entryId=a.entry_id,s.taskId=a.task_id,s.taskName=a.task_name,s.checklist=a.checklist??[],s.adaptiveEnabled=!!a.adaptive_enabled,s.requiredFields=a.required_completion_fields??[],s.taskType=a.task_type??"",s.readingUnit=a.reading_unit??"",s.parts=a.parts??[],s.consumesParts=a.consumes_parts??[],s.lang=Ge()?.language||"en",s.open(),!0):!1}function lr(a){let s=X(er);return ee(s)?(s.openForTask(a.entry_id,a.task_id,a.object_name,a.task_name),!0):!1}function St(a,s){let e=X(tr);return ee(e)?(e.openFor(a,s),!0):!1}function cr(a){let s=X(ir);return ee(s)?(s.openFor(a),!0):!1}var Vi,Wi,Zs,Xs,er,tr,ir,Bi,Ke,K=w(()=>{"use strict";yi();Ei();ft();Si();Ti();Fi();Ui();R();Vi="maintenance-object-dialog",Wi="maintenance-task-dialog",Zs="maintenance-history-edit-dialog",Xs="maintenance-complete-dialog",er="maintenance-qr-dialog",tr="maintenance-task-quick-actions-dialog",ir="maintenance-object-quick-actions-dialog";Bi={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7},Ke=null});P();var _s=["assignee_pool","required_completion_fields","checklist","labels","history"],gs=["checklist_progress"],ms=["tasks","parts"],fs=["manual_docs","battery_fleet_excluded"];function lt(a,s,e=[]){for(let t of s)a[t]===void 0&&(a[t]=[]);for(let t of e)a[t]===void 0&&(a[t]={})}function vs(a){let s=a;lt(s,ms),s.object&&typeof s.object=="object"&&lt(s.object,fs);for(let e of s.tasks)lt(e,_s,gs);return a}function Ee(a){for(let s of a)vs(s);return a}function bs(a,s){if(s.objects)return s.objects;let e=s.delta||[],t=s.removed||[];if(!e.length&&!t.length)return null;let i=new Map(a.map(n=>[n.entry_id,n]));for(let n of e)i.set(n.entry_id,n);for(let n of t)i.delete(n);return[...i.values()]}function Gt(a,s){return s.objects&&Ee(s.objects),s.delta&&Ee(s.delta),bs(a,s)}O();R();ut();async function Cs(a,s,e=300){return(await a.connection.sendMessagePromise({type:"auth/sign_path",path:s,expires:e})).path}async function Is(a,s,e=300){return Cs(a,`/api/maintenance_supporter/document/${s}`,e)}async function mi(a,s,e=""){let t=window.open("about:blank","_blank");try{let i=await Is(a,s);t&&(t.location.href=new URL(i+e,window.location.origin).href)}catch(i){throw t&&t.close(),i}}_t();function Be(a){let s=window;s.customCards=s.customCards||[],s.customCards.some(e=>e.type===a.type)||s.customCards.push(a)}gt();Ie();P();O();R();var Ps=["overdue","triggered","due_soon","ok"],W=class extends S{constructor(){super(...arguments);this._config={type:"custom:mainten Line truncated
`,c([x({attribute:!1})],G.prototype,"hass",2),c([h()],G.prototype,"_open",2),c([h()],G.prototype,"_entryId",2),c([h()],G.prototype,"_data",2),c([h()],G.prototype,"_busy",2),c([h()],G.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",G)});var Z={};ss(Z,{openCompleteDialog:()=>hr,openCreateObjectDialog:()=>At,openCreateTaskDialog:()=>Tt,openEditObjectDialog:()=>cr,openEditTaskDialog:()=>dr,openHistoryEditDialog:()=>pr,openObjectQuickActions:()=>_r,openQrDialog:()=>ur,openTaskQuickActions:()=>Ct});function Qe(){return document.querySelector("home-assistant")?.hass}function lr(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function X(a){let s=lr(),e=s.querySelector(a)??document.body.querySelector(a);return e?e.parentNode!==s&&s.appendChild(e):(e=document.createElement(a),s.appendChild(e)),e}function ee(a){let s=Qe();if(!s)return!1;a.hass=s;let e=R(s);return ut(e)||Ie(e).then(()=>{a.requestUpdate?.()}),!0}function Qi(a){return Ye||(Ye=a.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(s=>({features:s.features??Ki.features,defaultWarningDays:s.general?.default_warning_days??7})).catch(()=>Ki),Ye)}function At(){let a=X(Gi);return ee(a)?(a.openCreate(),!0):!1}function cr(a,s){let e=X(Gi);return ee(e)?(e.openEdit(a,s),!0):!1}function Tt(a="",s){let e=X(Yi);if(!ee(e))return!1;let t=Qe();return t?((async()=>{let i=await Qi(t),n=e;n.checklistsEnabled=i.features.checklists,n.scheduleTimeEnabled=i.features.schedule_time,n.completionActionsEnabled=i.features.completion_actions,n.defaultWarningDays=i.defaultWarningDays,n.openCreate(a,s)})(),!0):!1}function dr(a,s){let e=X(Yi);if(!ee(e))return!1;let t=Qe();return t?((async()=>{try{let[i,n]=await Promise.all([t.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:a}),Qi(t)]),l=(i.tasks||[]).find(p=>p.id===s);if(!l){console.warn(`openEditTaskDialog: task ${s} not found in entry ${a}`);return}let d=e;d.checklistsEnabled=n.features.checklists,d.scheduleTimeEnabled=n.features.schedule_time,d.completionActionsEnabled=n.features.completion_actions,d.defaultWarningDays=n.defaultWarningDays,await d.openEdit(a,l)}catch(i){console.warn("openEditTaskDialog: failed to load task/features",i)}})(),!0):!1}function pr(a){let s=X(sr);return ee(s)?(s.openEdit(a),!0):!1}function hr(a){let s=X(rr);return ee(s)?(s.entryId=a.entry_id,s.taskId=a.task_id,s.taskName=a.task_name,s.checklist=a.checklist??[],s.adaptiveEnabled=!!a.adaptive_enabled,s.requiredFields=a.required_completion_fields??[],s.taskType=a.task_type??"",s.readingUnit=a.reading_unit??"",s.parts=a.parts??[],s.consumesParts=a.consumes_parts??[],s.phaseLabel=a.phase_label??"",s.lang=Qe()?.language||"en",s.open(),!0):!1}function ur(a){let s=X(ar);return ee(s)?(s.openForTask(a.entry_id,a.task_id,a.object_name,a.task_name),!0):!1}function Ct(a,s){let e=X(nr);return ee(e)?(e.openFor(a,s),!0):!1}function _r(a){let s=X(or);return ee(s)?(s.openFor(a),!0):!1}var Gi,Yi,sr,rr,ar,nr,or,Ki,Ye,K=w(()=>{"use strict";$i();Ti();yt();Ci();Pi();Vi();Wi();j();Gi="maintenance-object-dialog",Yi="maintenance-task-dialog",sr="maintenance-history-edit-dialog",rr="maintenance-complete-dialog",ar="maintenance-qr-dialog",nr="maintenance-task-quick-actions-dialog",or="maintenance-object-quick-actions-dialog";Ki={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7},Ye=null});L();var fs=["assignee_pool","required_completion_fields","checklist","labels","history"],vs=["checklist_progress"],bs=["tasks","parts"],ys=["manual_docs","battery_fleet_excluded"];function dt(a,s,e=[]){for(let t of s)a[t]===void 0&&(a[t]=[]);for(let t of e)a[t]===void 0&&(a[t]={})}function xs(a){let s=a;dt(s,bs),s.object&&typeof s.object=="object"&&dt(s.object,ys);for(let e of s.tasks)dt(e,fs,vs);return a}function Se(a){for(let s of a)xs(s);return a}function ws(a,s){if(s.objects)return s.objects;let e=s.delta||[],t=s.removed||[];if(!e.length&&!t.length)return null;let i=new Map(a.map(n=>[n.entry_id,n]));for(let n of e)i.set(n.entry_id,n);for(let n of t)i.delete(n);return[...i.values()]}function Jt(a,s){return s.objects&&Se(s.objects),s.delta&&Se(s.delta),ws(a,s)}M();j();gt();async function Ls(a,s,e=300){return(await a.connection.sendMessagePromise({type:"auth/sign_path",path:s,expires:e})).path}async function Rs(a,s,e=300){return Ls(a,`/api/maintenance_supporter/document/${s}`,e)}async function bi(a,s,e=""){let t=window.open("about:blank","_blank");try{let i=await Rs(a,s);t&&(t.location.href=new URL(i+e,window.location.origin).href)}catch(i){throw t&&t.close(),i}}mt();function We(a){let s=window;s.customCards=s.customCards||[],s.customCards.some(e=>e.type===a.type)||s.customCards.push(a)}ft();Pe();vt();L();M();j();var Hs=["overdue","triggered","due_soon","ok"],W=class extends S{constructor(){super(...arguments Line truncated
<div class="editor">
<ha-textfield
label="${r("card_title",e)}"
@@ -3780,7 +3944,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="field">
<div class="field-label">${r("card_filter_status",e)}</div>
<div class="chip-row">
${Ps.map(f=>o`
${Hs.map(f=>o`
<label class="chip ${t.has(f)?"active":""}">
<input type="checkbox"
.checked=${t.has(f)}
@@ -3827,11 +3991,11 @@ ${p?`<div class="sub">${p}</div>`:""}
</div>
<div class="field-help">${r("card_filter_areas_help",e)}</div>
</div>`:u}
${y.length?o`
${b.length?o`
<div class="field">
<div class="field-label">${r("labels",e)}</div>
<div class="object-list">
${y.map(f=>o`
${b.map(f=>o`
<label class="object-row">
<input type="checkbox"
.checked=${v.has(f)}
@@ -3865,7 +4029,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.hass=${this.hass}
.value=${this._config.entity_ids||[]}
.includeDomains=${["sensor","binary_sensor"]}
.includeEntities=${x}
.includeEntities=${y}
@value-changed=${this._onEntitiesChanged}
></ha-entities-picker>
<div class="field-help">${r("card_filter_entities_help",e)}</div>
@@ -4015,23 +4179,23 @@ ${p?`<div class="sub">${p}</div>`:""}
font-size: 14px;
max-width: 320px;
}
`,c([b({attribute:!1})],W.prototype,"hass",2),c([h()],W.prototype,"_config",2),c([h()],W.prototype,"_objects",2),c([h()],W.prototype,"_loadingObjects",2),c([h()],W.prototype,"_loadError",2),c([h()],W.prototype,"_views",2);customElements.get("maintenance-supporter-card-editor")||customElements.define("maintenance-supporter-card-editor",W);ft();P();O();P();O();R();var vt={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"};function vi(a){try{return localStorage.getItem(a)}catch{return null}}function bi(a,s){try{localStorage.setItem(a,s)}catch{}}ie();var q=class q extends S{constructor(){super(...arguments);this.flat=!1;this._ov=null;this._loading=!1;this._marking=!1;this._error="";this._history=null;this._rosterSort=q._storedSort();this._typeFilter=null;this._recorded=[];this._historyRequested=!1;this._localeReady=!1;this._markAll=async()=>{await this._mark(void 0)};this._repair=async()=>{if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/setup",language:this._lang}),await this._load()}catch(e){this._error=T(e,this._lang)}finally{this._marking=!1}}};this._loadHistory=async e=>{if(!(!e.target.open||this._historyRequested)){this._historyRequested=!0;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview_history"});this._history=t.series}catch{this._history=null}}}}get _lang(){return j(this.hass)}connectedCallback(){super.connectedCallback(),this.hass&&this._load()}updated(e){e.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,Ce(this._lang).then(()=>this.requestUpdate()),this._ov===null&&!this._loading&&this._load())}async _load(){this._loading=!0,this._error="";try{this._ov=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview"})}catch(e){this._error=T(e,this._lang)}finally{this._loading=!1}}async _mark(e){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/mark_replaced",...e?{entity_ids:e}:{}}),await this._load()}catch(t){this._error=T(t,this._lang)}finally{this._marking=!1}}}async _setExcluded(e,t){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_excluded",entity_id:e,excluded:t}),await this._load()}catch(i){this._error=T(i,this._lang)}finally{this._marking=!1}}}async _addBattery(e){let t=e.detail?.value;if(!(!t||this._marking)){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_included",entity_id:t,included:!0}),await this._load()}catch(i){this._error=T(i,this._lang)}finally{this._marking=!1}}}async _setTrackSelf(e){let t=e.target.checked;if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_track_self_charging",enabled:t}),await this._load()}catch(i){this._error=T(i,this._lang)}finally{this._marking=!1}}}_sparkline(e){let t=this._history?.[e.entity_id];if(!t||t.points.length<2)return u;let i=110,n=24,l=2,d=t.points[0][0],p=t.points[t.points.length-1][0],_=Date.now()/1e3,v=e.status!=="low"&&e.predicted_source==="trend"&&e.days_until!=null?_+e.days_until*86400:null,m=Math.max(p,v??p),y=L=>m===d?l:l+(L-d)/(m-d)*(i-2*l),x=L=>l+(1-Math.min(100,Math.max(0,L))/100)*(n-2*l),f=t.points.map(([L,E])=>`${y(L).toFixed(1)},${x(E).toFixed(1)}`).join(" "),$=t.points[t.points.length-1][1],z=x(t.threshold).toFixed(1);return o`<svg
`,c([x({attribute:!1})],W.prototype,"hass",2),c([h()],W.prototype,"_config",2),c([h()],W.prototype,"_objects",2),c([h()],W.prototype,"_loadingObjects",2),c([h()],W.prototype,"_loadError",2),c([h()],W.prototype,"_views",2);customElements.get("maintenance-supporter-card-editor")||customElements.define("maintenance-supporter-card-editor",W);yt();L();M();L();M();j();var xt={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"};function xi(a){try{return localStorage.getItem(a)}catch{return null}}function wi(a,s){try{localStorage.setItem(a,s)}catch{}}se();var O=class O extends S{constructor(){super(...arguments);this.flat=!1;this._ov=null;this._loading=!1;this._marking=!1;this._error="";this._history=null;this._rosterSort=O._storedSort();this._typeFilter=null;this._recorded=[];this._historyRequested=!1;this._localeReady=!1;this._markAll=async()=>{await this._mark(void 0)};this._repair=async()=>{if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/setup",language:this._lang}),await this._load()}catch(e){this._error=T(e,this._lang)}finally{this._marking=!1}}};this._loadHistory=async e=>{if(!(!e.target.open||this._historyRequested)){this._historyRequested=!0;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview_history"});this._history=t.series}catch{this._history=null}}}}get _lang(){return R(this.hass)}connectedCallback(){super.connectedCallback(),this.hass&&this._load()}updated(e){e.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,Ie(this._lang).then(()=>this.requestUpdate()),this._ov===null&&!this._loading&&this._load())}async _load(){this._loading=!0,this._error="";try{this._ov=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/overview"})}catch(e){this._error=T(e,this._lang)}finally{this._loading=!1}}async _mark(e){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/mark_replaced",...e?{entity_ids:e}:{}}),await this._load()}catch(t){this._error=T(t,this._lang)}finally{this._marking=!1}}}async _setExcluded(e,t){if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_excluded",entity_id:e,excluded:t}),await this._load()}catch(i){this._error=T(i,this._lang)}finally{this._marking=!1}}}async _addBattery(e){let t=e.detail?.value;if(!(!t||this._marking)){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_included",entity_id:t,included:!0}),await this._load()}catch(i){this._error=T(i,this._lang)}finally{this._marking=!1}}}async _setTrackSelf(e){let t=e.target.checked;if(!this._marking){this._marking=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/set_track_self_charging",enabled:t}),await this._load()}catch(i){this._error=T(i,this._lang)}finally{this._marking=!1}}}_sparkline(e){let t=this._history?.[e.entity_id];if(!t||t.points.length<2)return u;let i=110,n=24,l=2,d=t.points[0][0],p=t.points[t.points.length-1][0],_=Date.now()/1e3,v=e.status!=="low"&&e.predicted_source==="trend"&&e.days_until!=null?_+e.days_until*86400:null,m=Math.max(p,v??p),b=P=>m===d?l:l+(P-d)/(m-d)*(i-2*l),y=P=>l+(1-Math.min(100,Math.max(0,P))/100)*(n-2*l),f=t.points.map(([P,E])=>`${b(P).toFixed(1)},${y(E).toFixed(1)}`).join(" "),$=t.points[t.points.length-1][1],H=y(t.threshold).toFixed(1);return o`<svg
class="bf-spark"
viewBox="0 0 ${i} ${n}"
role="img"
aria-label=${r("battery_fleet_sparkline_hint",this._lang)}
>
<title>${r("battery_fleet_sparkline_hint",this._lang)}</title>
<line class="bf-spark-th" x1="0" y1=${z} x2=${i} y2=${z}></line>
<line class="bf-spark-th" x1="0" y1=${H} x2=${i} y2=${H}></line>
<polyline class="bf-spark-line" points=${f}></polyline>
${v!==null?o`<line
class="bf-spark-proj"
x1=${y(p).toFixed(1)}
y1=${x($).toFixed(1)}
x2=${y(v).toFixed(1)}
y2=${z}
x1=${b(p).toFixed(1)}
y1=${y($).toFixed(1)}
x2=${b(v).toFixed(1)}
y2=${H}
></line>`:u}
</svg>`}static _storedSort(){return vi(vt.batteryRosterSort)==="name"?"name":"urgency"}_setSort(e){this._rosterSort=e,bi(vt.batteryRosterSort,e)}_sortedRoster(e){let t=this._typeFilter===null?e:e.filter(n=>n.battery_type===this._typeFilter);if(this._rosterSort==="name")return t;let i=n=>n.status==="low"?-1e3+(n.level??101)/101:n.days_until??1/0;return[...t].sort((n,l)=>i(n)-i(l)||n.device_name.localeCompare(l.device_name))}_predictedDate(e){return this._fmtDate(Date.now()+e*864e5)}_fmtDate(e){return new Intl.DateTimeFormat(this._lang,{day:"numeric",month:"numeric",year:"numeric"}).format(new Date(e))}_shoppingLine(e){return Object.entries(e).map(([t,i])=>o`<button
</svg>`}static _storedSort(){return xi(xt.batteryRosterSort)==="name"?"name":"urgency"}_setSort(e){this._rosterSort=e,wi(xt.batteryRosterSort,e)}_sortedRoster(e){let t=this._typeFilter===null?e:e.filter(n=>n.battery_type===this._typeFilter);if(this._rosterSort==="name")return t;let i=n=>n.status==="low"?-1e3+(n.level??101)/101:n.days_until??1/0;return[...t].sort((n,l)=>i(n)-i(l)||n.device_name.localeCompare(l.device_name))}_predictedDate(e){return this._fmtDate(Date.now()+e*864e5)}_fmtDate(e){return new Intl.DateTimeFormat(this._lang,{day:"numeric",month:"numeric",year:"numeric"}).format(new Date(e))}_shoppingLine(e){return Object.entries(e).map(([t,i])=>o`<button
class="bf-type-chip ${this._typeFilter===t?"bf-type-chip-active":""}"
title=${r("battery_fleet_filter_type",this._lang)}
@click=${()=>this._toggleTypeFilter(t)}
@@ -4202,7 +4366,7 @@ ${p?`<div class="sub">${p}</div>`:""}
`:u}
<div class="bf-total">${r("battery_fleet_total",e).replace("{n}",String(t.total))}</div>
</div>
`}};q.styles=A`
`}};O.styles=A`
.bf-card {
background: var(--card-background-color, #fff);
border: 1px solid var(--divider-color);
@@ -4590,20 +4754,20 @@ ${p?`<div class="sub">${p}</div>`:""}
accent-color: var(--primary-color);
margin: 0;
}
`,c([b({attribute:!1})],q.prototype,"hass",2),c([b({type:Boolean})],q.prototype,"flat",2),c([h()],q.prototype,"_ov",2),c([h()],q.prototype,"_loading",2),c([h()],q.prototype,"_marking",2),c([h()],q.prototype,"_error",2),c([h()],q.prototype,"_history",2),c([h()],q.prototype,"_rosterSort",2),c([h()],q.prototype,"_typeFilter",2),c([h()],q.prototype,"_recorded",2);var bt=q;customElements.get("maintenance-battery-fleet-section")||customElements.define("maintenance-battery-fleet-section",bt);var _e=class extends S{constructor(){super(...arguments);this._config={type:"custom:maintenance-battery-fleet-card"}}static getStubConfig(){return{type:"custom:maintenance-battery-fleet-card"}}setConfig(e){this._config=e}getCardSize(){return 6}render(){return this.hass?o`
`,c([x({attribute:!1})],O.prototype,"hass",2),c([x({type:Boolean})],O.prototype,"flat",2),c([h()],O.prototype,"_ov",2),c([h()],O.prototype,"_loading",2),c([h()],O.prototype,"_marking",2),c([h()],O.prototype,"_error",2),c([h()],O.prototype,"_history",2),c([h()],O.prototype,"_rosterSort",2),c([h()],O.prototype,"_typeFilter",2),c([h()],O.prototype,"_recorded",2);var wt=O;customElements.get("maintenance-battery-fleet-section")||customElements.define("maintenance-battery-fleet-section",wt);var ge=class extends S{constructor(){super(...arguments);this._config={type:"custom:maintenance-battery-fleet-card"}}static getStubConfig(){return{type:"custom:maintenance-battery-fleet-card"}}setConfig(e){this._config=e}getCardSize(){return 6}render(){return this.hass?o`
<ha-card .header=${this._config.title||void 0}>
<div class="content">
<maintenance-battery-fleet-section flat .hass=${this.hass}></maintenance-battery-fleet-section>
</div>
</ha-card>
`:u}};_e.styles=A`
`:u}};ge.styles=A`
ha-card {
overflow: hidden;
}
.content {
padding: 12px 16px 14px;
}
`,c([b({attribute:!1})],_e.prototype,"hass",2),c([h()],_e.prototype,"_config",2);customElements.get("maintenance-battery-fleet-card")||customElements.define("maintenance-battery-fleet-card",_e);Be({type:"maintenance-battery-fleet-card",name:"Battery Fleet",description:"All tracked batteries: what is low now, what runs out soon, and what to buy.",preview:!1});K();var D=class extends S{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-card"};this._objects=[];this._stats=null;this._unsub=null;this._viewFilters=null;this._userNames={};this._userService=null;this._userNamesLoaded=!1;this._taskDocs={};this._docsLoadedFor=new Set;this._dataLoaded=!1;this._lastConnection=null;this._onCompleted=async()=>{await this._loadData()}}get _lang(){return j(this.hass)}static getConfigElement(){return document.createElement("maintenance-supporter-card-editor")}static getStubConfig(){return{type:"custom:maintenance-supporter-card",show_header:!0,show_actions:!0,filter_status:["overdue","triggered","due_soon"],max_items:10}}setConfig(e){let t=e.view_id!==this._config.view_id;this._config=e,t&&this._dataLoaded&&this.hass&&this._loadViewFilters()}getCardSize(){return 3}connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._lastConnection=null}updated(e){if(super.updated(e),ci(this,e),this.hass&&!this._userNamesLoaded&&this._config.show_assignee!==!1&&this._objects.some(t=>t.tasks.some(i=>i.responsible_user_id))&&this._loadUserNames(),this.hass&&this._config.show_documents!==!1)for(let t of this._objects)this._docsLoadedFor.has(t.entry_id)||t.tasks.some(i=>(i.document_count??0)>0)&&this._loadDocuments(t.entry_id);if(e.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}}}async _loadData(){try{let[e,t]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"})]);this._objects=Ee(e.objects),this._stats=t}catch{}await this._loadViewFilters()}_assigneeName(e){if(this._config.show_assignee===!1)return"";let t=e.responsible_user_id;return t&&this._userNames[t]||""}async _loadUserNames(){this._userNamesLoaded=!0,this._userService?this._userService.updateHass(this.hass):this._userService=new ue(this.hass);try{let e=await this._userService.getUsers();this._userNames=Object.fromEntries(e.map(t=>[t.id,t.name]))}catch{}}async _loadDocuments(e){this._docsLoadedFor.add(e);try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:e}),i={};for(let n of t.documents||[])for(let l of n.task_ids||[])(i[l]||=[]).push({id:n.id,title:n.title,kind:n.kind,url:n.url});this._taskDocs={...this._taskDocs,[e]:i}}catch{}}_docsFor(e,t){if(this._config.show_documents===!1)return[];let n=[...this._taskDocs[e]?.[t.id]||[]];return t.documentation_url&&n.push({id:`url:${t.id}`,title:r("documentation_label",this._lang),kind:"weblink",url:t.documentation_url}),n}async _openDoc(e){if(e.kind==="weblink"&&e.url){Ue(e.url)&&window.open(e.url,"_blank","noopener");return}try{await mi(this.hass,e.id)}catch{}}async _loadViewFilters(){if(!this._config.view_id){this._viewFilters=null;return}try{let t=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"})).views||[]).find(i=>i.id===this._config.view_id);this._viewFilters=t?t.filters:null}catch{this._viewFilters=null}}async _subscribe(){try{let e=await this.hass.connection.subscribeMessage(t=>{let i=Gt(this._objects,t);i!==null&&(this._objects=i)},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){e();return}this._unsub=e}catch{}}get _flatTasks(){let e=[],{filter_status:t,filter_objects:i,entity_ids:n,filter_due_min_days:l,filter_due_max_days:d,filter_labels:p,filter_priority:_,filter_areas:v,max_items:m}=this._config,y=n?.length?new Set(n):null,x=l!==void 0||d!==void 0,f=this._viewFilters,$=f?.user_id==="current_user"?this.hass.user?.id??null:f?.user_id??null;for(let L of this._objects)if(!(i?.length&&!i.includes(L.object.name))){if(v?.length){let E=L.object.area_id;if(!E||!v.includes(E))continue}for(let E of L.tasks)if(!E.is_done&&!(E.archived||L.object.archived)&&!(t?.length&&!t.includes(E.status))&&!(p?.length&&!(E.labels||[]).some(N=>p.includes(N)))&&!(_?.length&&!_.includes(E.priority||"normal"))&&!(y&&!(E.sensor_entity_id&&y.has(E.sensor_entity_id)||E.binary_sensor_entity_id&&y.has(E.binary_sensor_entity_id)))){if(x){let N=E.days_until_due;if(N==null||l!==void 0&&N<l||d!==void 0&&N>d)continue}f&&(f.status&&E.status!==f.status||f.label&&!(E.labels||[]). Line truncated
`,c([x({attribute:!1})],ge.prototype,"hass",2),c([h()],ge.prototype,"_config",2);customElements.get("maintenance-battery-fleet-card")||customElements.define("maintenance-battery-fleet-card",ge);We({type:"maintenance-battery-fleet-card",name:"Battery Fleet",description:"All tracked batteries: what is low now, what runs out soon, and what to buy.",preview:!1});K();var z=class extends S{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-card"};this._objects=[];this._stats=null;this._unsub=null;this._viewFilters=null;this._userNames={};this._userService=null;this._userNamesLoaded=!1;this._taskDocs={};this._docsLoadedFor=new Set;this._dataLoaded=!1;this._lastConnection=null;this._onCompleted=async()=>{await this._loadData()}}get _lang(){return R(this.hass)}static getConfigElement(){return document.createElement("maintenance-supporter-card-editor")}static getStubConfig(){return{type:"custom:maintenance-supporter-card",show_header:!0,show_actions:!0,filter_status:["overdue","triggered","due_soon"],max_items:10}}setConfig(e){let t=e.view_id!==this._config.view_id;this._config=e,t&&this._dataLoaded&&this.hass&&this._loadViewFilters()}getCardSize(){return 3}connectedCallback(){super.connectedCallback()}disconnectedCallback(){super.disconnectedCallback(),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._lastConnection=null}updated(e){if(super.updated(e),hi(this,e),this.hass&&!this._userNamesLoaded&&this._config.show_assignee!==!1&&this._objects.some(t=>t.tasks.some(i=>i.responsible_user_id))&&this._loadUserNames(),this.hass&&this._config.show_documents!==!1)for(let t of this._objects)this._docsLoadedFor.has(t.entry_id)||t.tasks.some(i=>(i.document_count??0)>0)&&this._loadDocuments(t.entry_id);if(e.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}}}async _loadData(){try{let[e,t]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"})]);this._objects=Se(e.objects),this._stats=t}catch{}await this._loadViewFilters()}_assigneeName(e){if(this._config.show_assignee===!1)return"";let t=e.responsible_user_id;return t&&this._userNames[t]||""}async _loadUserNames(){this._userNamesLoaded=!0,this._userService?this._userService.updateHass(this.hass):this._userService=new _e(this.hass);try{let e=await this._userService.getUsers();this._userNames=Object.fromEntries(e.map(t=>[t.id,t.name]))}catch{}}async _loadDocuments(e){this._docsLoadedFor.add(e);try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/documents/list",entry_id:e}),i={};for(let n of t.documents||[])for(let l of n.task_ids||[])(i[l]||=[]).push({id:n.id,title:n.title,kind:n.kind,url:n.url});this._taskDocs={...this._taskDocs,[e]:i}}catch{}}_docsFor(e,t){if(this._config.show_documents===!1)return[];let n=[...this._taskDocs[e]?.[t.id]||[]];return t.documentation_url&&n.push({id:`url:${t.id}`,title:r("documentation_label",this._lang),kind:"weblink",url:t.documentation_url}),n}async _openDoc(e){if(e.kind==="weblink"&&e.url){Ve(e.url)&&window.open(e.url,"_blank","noopener");return}try{await bi(this.hass,e.id)}catch{}}async _loadViewFilters(){if(!this._config.view_id){this._viewFilters=null;return}try{let t=((await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"})).views||[]).find(i=>i.id===this._config.view_id);this._viewFilters=t?t.filters:null}catch{this._viewFilters=null}}async _subscribe(){try{let e=await this.hass.connection.subscribeMessage(t=>{let i=Jt(this._objects,t);i!==null&&(this._objects=i)},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){e();return}this._unsub=e}catch{}}get _flatTasks(){let e=[],{filter_status:t,filter_objects:i,entity_ids:n,filter_due_min_days:l,filter_due_max_days:d,filter_labels:p,filter_priority:_,filter_areas:v,max_items:m}=this._config,b=n?.length?new Set(n):null,y=l!==void 0||d!==void 0,f=this._viewFilters,$=f?.user_id==="current_user"?this.hass.user?.id??null:f?.user_id??null;for(let P of this._objects)if(!(i?.length&&!i.includes(P.object.name))){if(v?.length){let E=P.object.area_id;if(!E||!v.includes(E))continue}for(let E of P.tasks)if(!E.is_done&&!(E.archived||P.object.archived)&&!(t?.length&&!t.includes(E.status))&&!(p?.length&&!(E.labels||[]).some(N=>p.includes(N)))&&!(_?.length&&!_.includes(E.priority||"normal"))&&!(b&&!(E.sensor_entity_id&&b.has(E.sensor_entity_id)||E.binary_sensor_entity_id&&b.has(E.binary_sensor_entity_id)))){if(y){let N=E.days_until_due;if(N==null||l!==void 0&&N<l||d!==void 0&&N>d)continue}f&&(f.status&&E.status!==f.status||f.label&&!(E.labels||[]). Line truncated
<ha-card>
<div class="card-header">
<h1>${t}</h1>
@@ -4619,14 +4783,14 @@ ${p?`<div class="sub">${p}</div>`:""}
<mwc-icon-button
class="hdr-add"
title="${r("new_object",e)}"
@click=${()=>kt()}
@click=${()=>At()}
>
<ha-icon icon="mdi:plus-box"></ha-icon>
</mwc-icon-button>
<mwc-icon-button
class="hdr-add"
title="${r("add_task",e)}"
@click=${()=>Et("",this._objects)}
@click=${()=>Tt("",this._objects)}
>
<ha-icon icon="mdi:playlist-plus"></ha-icon>
</mwc-icon-button>
@@ -4646,7 +4810,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="task-item clickable"
@click=${()=>this._openTaskDetail(_,m.id)}
title="${r("open_task",e)||"Open task"}">
<div class="status-dot" style="background: ${oe[m.status]||"#ccc"}"></div>
<div class="status-dot" style="background: ${le[m.status]||"#ccc"}"></div>
<div class="task-info">
<div class="task-name">
${m.name}
@@ -4659,32 +4823,32 @@ ${p?`<div class="sub">${p}</div>`:""}
${l?this._assigneeName(m)?o`<div class="task-meta compact-assignee" title="${this._assigneeName(m)}">
<ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(m)}
</div>`:u:o`<div class="task-meta">
${v} · ${r(m.type,e)}${this._assigneeName(m)?o` · <span class="assignee"
${v} · ${r(m.type,e)}${ie(m)?o` · ${ie(m)}`:u}${this._assigneeName(m)?o` · <span class="assignee"
><ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(m)}</span
>`:u}
</div>`}
</div>
${this._docsFor(_,m).length?o`<div class="doc-chips">
${this._docsFor(_,m).map(y=>o`
${this._docsFor(_,m).map(b=>o`
<button
type="button"
class="doc-chip"
title="${y.title}"
@click=${x=>{x.stopPropagation(),this._openDoc(y)}}
title="${b.title}"
@click=${y=>{y.stopPropagation(),this._openDoc(b)}}
>
<ha-icon icon=${y.kind==="weblink"?"mdi:link-variant":"mdi:file-document-outline"}></ha-icon>
<span>${y.title}</span>
<ha-icon icon=${b.kind==="weblink"?"mdi:link-variant":"mdi:file-document-outline"}></ha-icon>
<span>${b.title}</span>
</button>
`)}
</div>`:u}
<div class="task-due">
${m.days_until_due!==null&&m.days_until_due!==void 0?m.days_until_due<0?o`<span class="overdue-text">${ht(m.days_until_due,e)}</span>`:ht(m.days_until_due,e):m.trigger_active?"\u26A1":"\u2014"}
${m.days_until_due!==null&&m.days_until_due!==void 0?m.days_until_due<0?o`<span class="overdue-text">${_t(m.days_until_due,e)}</span>`:_t(m.days_until_due,e):m.trigger_active?"\u26A1":"\u2014"}
</div>
${n?o`
<mwc-icon-button
class="complete-btn"
title="${r("complete",e)}"
@click=${y=>{y.stopPropagation();let x=this.shadowRoot.querySelector("maintenance-complete-dialog");x.entryId=_,x.taskId=m.id,x.taskName=m.name,x.checklist=m.checklist||[],x.adaptiveEnabled=!!m.adaptive_config?.enabled,x.taskType=m.type||"",x.readingUnit=m.reading_unit||"",x.requiredFields=m.required_completion_fields||[],x.lang=e;let f=!!m.part_ref;x.parts=f?[]:Ve(m,_,this._objects,e),x.consumesParts=f?[]:m.consumes_parts||[],x.open()}}
@click=${b=>{b.stopPropagation();let y=this.shadowRoot.querySelector("maintenance-complete-dialog"),f=Le(m);y.entryId=_,y.taskId=m.id,y.taskName=m.name,y.checklist=f?f.checklist:m.checklist||[],y.adaptiveEnabled=!!m.adaptive_config?.enabled,y.taskType=m.type||"",y.readingUnit=m.reading_unit||"",y.requiredFields=f?f.requiredFields:m.required_completion_fields||[],y.phaseLabel=ie(m),y.lang=e;let $=!!m.part_ref,H=f?f.consumesParts:m.consumes_parts||[];y.parts=$?[]:Ke({consumes_parts:H},_,this._objects,e),y.consumesParts=$?[]:H,y.open()}}
>
<ha-icon icon="mdi:check"></ha-icon>
</mwc-icon-button>
@@ -4698,7 +4862,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.hass=${this.hass}
@task-completed=${this._onCompleted}
></maintenance-complete-dialog>
`}};D.styles=[Fe,A`
`}};z.styles=[Be,A`
ha-card { overflow: hidden; }
.card-header {
@@ -4830,7 +4994,7 @@ ${p?`<div class="sub">${p}</div>`:""}
--mdc-icon-size: 18px;
color: var(--primary-color);
}
`],c([b({attribute:!1})],D.prototype,"hass",2),c([h()],D.prototype,"_config",2),c([h()],D.prototype,"_objects",2),c([h()],D.prototype,"_stats",2),c([h()],D.prototype,"_unsub",2),c([h()],D.prototype,"_viewFilters",2),c([h()],D.prototype,"_userNames",2),c([h()],D.prototype,"_taskDocs",2);customElements.get("maintenance-supporter-card")||customElements.define("maintenance-supporter-card",D);Be({type:"maintenance-supporter-card",name:"Maintenance Supporter",description:"Overview of your maintenance tasks with quick actions.",preview:!0});export{D as MaintenanceSupporterCard};
`],c([x({attribute:!1})],z.prototype,"hass",2),c([h()],z.prototype,"_config",2),c([h()],z.prototype,"_objects",2),c([h()],z.prototype,"_stats",2),c([h()],z.prototype,"_unsub",2),c([h()],z.prototype,"_viewFilters",2),c([h()],z.prototype,"_userNames",2),c([h()],z.prototype,"_taskDocs",2);customElements.get("maintenance-supporter-card")||customElements.define("maintenance-supporter-card",z);We({type:"maintenance-supporter-card",name:"Maintenance Supporter",description:"Overview of your maintenance tasks with quick actions.",preview:!0});export{z as MaintenanceSupporterCard};
/*! Bundled license information:
@lit/reactive-element/css-tag.js:
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.64.0 */
var S="2.64.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
/*! maintenance_supporter frontend 2.65.0 */
var S="2.65.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
@@ -0,0 +1,250 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-IAYX7ZWS.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import{a as o,b as _,c as i,f as l,g as h,k as g,l as n,p as t,r as u,t as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.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})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${e=>e.stopPropagation()}>
<div class="title">${t("adopt_problem_title",s)}</div>
<div class="hint">${t("adopt_problem_hint",s)}</div>
${this._error?i`<div class="error">${this._error}</div>`:l}
${this._loading?i`<div class="loading">…</div>`:this._sensors.length===0?i`<div class="empty">${t("adopt_problem_none",s)}</div>`:i`
<label class="select-all">
<input
type="checkbox"
.checked=${a}
@change=${this._toggleAll}
/>
<span>${t("selected",s)}: ${this._selected.size} / ${this._sensors.length}</span>
</label>
<div class="list">
${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),d=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
<label class="row">
<input
type="checkbox"
.checked=${m}
@change=${()=>this._toggle(e.entity_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${e.name}</span>
<span class="chip ${d?"chip-active":"chip-ok"}">
${d?t("adopt_problem_active",s):t("adopt_problem_ok",s)}
</span>
</div>
${c?i`<div class="row-sub">${c}</div>`:l}
<div class="row-target">
→ ${e.suggested_object_name}${e.suggested_entry_id?l:i` <span class="new-tag">${t("adopt_problem_new_object",s)}</span>`}
</div>
${e.suggested_part_name?i`<div class="row-part">
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
${t("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
</div>`:l}
</div>
</label>
`})}
</div>
`}
${!this._loading&&this._sensors.length>0?i`
<label class="responsible">
<span>${t("for_at_least_minutes",s)}</span>
<input
class="for-input"
type="number"
min="0"
max="1440"
.value=${this._forMinutes}
@input=${e=>this._forMinutes=e.target.value}
/>
</label>
<div class="for-hint">${t("adopt_for_minutes_hint",s)}</div>
`:l}
${!this._loading&&this._sensors.length>0&&this._users.length>0?i`
<label class="responsible">
<span>${t("adopt_problem_responsible",s)}</span>
<select
.value=${this._responsible}
@change=${e=>{this._responsible=e.target.value}}
>
<option value="" ?selected=${!this._responsible}>${t("no_user_assigned",s)}</option>
${this._users.map(e=>i`<option value=${e.id} ?selected=${e.id===this._responsible}>${e.name}</option>`)}
</select>
</label>
`:l}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel",s)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${t("adopt_problem_adopt",s)}
</ha-button>
</div>
</div>
</div>
`}};r.styles=_`
.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);
}
.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;
}
.select-all {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--secondary-text-color);
cursor: pointer;
}
.select-all input {
cursor: pointer;
}
.list {
display: flex;
flex-direction: column;
gap: 6px;
overflow-y: auto;
max-height: 50vh;
}
.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-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;
}
.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;
}
`,o([g({attribute:!1})],r.prototype,"hass",2),o([n()],r.prototype,"_open",2),o([n()],r.prototype,"_loading",2),o([n()],r.prototype,"_adopting",2),o([n()],r.prototype,"_error",2),o([n()],r.prototype,"_sensors",2),o([n()],r.prototype,"_selected",2),o([n()],r.prototype,"_users",2),o([n()],r.prototype,"_responsible",2),o([n()],r.prototype,"_forMinutes",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
@@ -0,0 +1,306 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as _,e as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-UBKG7VLY.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import{B as m,a as s,b,c as a,f as d,g as v,k as n,l,p as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(){this._open||(this._open=!0,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((e,t)=>[String(t),!!this.checklistPrefill[e]]).filter(([,e])=>e)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(e=>[_(e),{...e}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,o=t.files?.[0];if(t.value="",!!o){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",o,o.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(o))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=r("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=g(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let o=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(o)||o<=0?null:Math.round(this.restockUnitCost*o*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let o of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(o));c?.cost!=null&&(e+=c.cost*(o.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let o=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return a`<button
type="button"
class="cost-suggestion"
@click=${()=>this._cost=t.toFixed(2)}
>${r("cost_from_parts",e).replace("{amount}",o)}</button>`}_close(){this._open=!1}render(){if(!this._open)return a``;let e=this.lang||this.hass?.language||"en";return a`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div>
${this.phaseLabel?a`<div class="phase-line">${r("phase_current",e)}: ${this.phaseLabel}</div>`:d}
<div class="content">
${this._error?a`<div class="error">${this._error}</div>`:d}
${this.checklist.length>0?a`
<div class="checklist-section">
<label class="checklist-label">${r("checklist",e)}</label>
${this.checklist.map((t,o)=>a`
<label class="checklist-item" @click=${()=>this._toggleCheck(o)}>
<input type="checkbox" .checked=${!!this._checklistState[String(o)]} />
<span>${t}</span>
</label>
`)}
</div>
`:d}
${this.taskType==="reading"?a`
<label class="field">
<span class="field-label">${r("reading_value_label",e)}${this.readingUnit?` (${this.readingUnit})`:""}</span>
<input type="number" step="any" class="field-input"
.value=${this._readingValue}
@input=${t=>this._readingValue=t.target.value} />
</label>`:d}
${this.parts.length?a`<div class="used-parts">
<span class="field-label">${r("complete_parts_used",e)}</span>
${this.parts.map(t=>{let o=_({part_id:t.id,entry_id:t.entry_id}),c=this._usedParts[o],p=c!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return a`<div class="used-part-row">
<label class="used-part-check">
<input type="checkbox" .checked=${p}
@change=${f=>{let h={...this._usedParts};f.target.checked?h[o]=h[o]||u:delete h[o],this._usedParts=h}} />
<span
>${t.name}${t.owner_name?a`<span class="used-part-owner"> (${t.owner_name})</span>`:d}${t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:""}</span
>
</label>
${p?a`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(c.quantity)}
@input=${f=>{let h=parseFloat(f.target.value);this._usedParts={...this._usedParts,[o]:{...u,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:d}
</div>`})}
</div>`:this.consumesInfo.length?a`<div class="consumes-hint">
${this.consumesInfo.map(t=>a`<div>${t}</div>`)}
</div>`:d}
${this.restockDefault!==null?a`
<label class="field">
<span class="field-label">${r("restock_quantity_label",e)}</span>
<input type="number" step="0.01" min="0.01" class="field-input"
.value=${this._restockQty}
@input=${t=>this._restockQty=t.target.value} />
</label>`:d}
<!-- Native <input>s rather than <ha-textfield>: when this dialog
is opened from a Lovelace card via dialog-mount, ha-textfield
isn't yet registered (HA loads it lazily when its own panels
need it) so the elements render with zero height and the user
only sees the title + Cancel/Complete buttons — the original
bug report. Native inputs always render. -->
<label class="field">
<span class="field-label">${r("notes_optional",e)}${this._req("notes")}</span>
<input type="text" class="field-input"
.value=${this._notes}
@input=${t=>this._notes=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("cost_optional",e)}${this._req("cost")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._cost}
@input=${t=>this._cost=t.target.value} />
${this._renderCostSuggestion(e)}
</label>
<label class="field">
<span class="field-label">${r("duration_minutes",e)}${this._req("duration")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._duration}
@input=${t=>this._duration=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("completed_at_optional",e)}</span>
<input type="datetime-local" class="field-input"
max=${new Date(Date.now()-new Date().getTimezoneOffset()*6e4).toISOString().slice(0,16)}
.value=${this._completedAt}
@change=${t=>this._completedAt=t.target.value} />
</label>
<div class="field">
<span class="field-label">${r("completion_photo_optional",e)}${this._req("photo")}</span>
${this._photoPreview?a`
<div class="photo-preview">
<img src=${this._photoPreview} alt="" />
<button type="button" class="photo-remove" @click=${this._removePhoto}
title="${r("remove",e)}">✕</button>
</div>`:a`
<label class="photo-pick">
<ha-icon icon="mdi:camera"></ha-icon>
<span>${this._photoUploading?r("uploading",e):r("add_photo",e)}</span>
<input type="file" accept="image/*" capture="environment"
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>`}
</div>
${this.adaptiveEnabled?a`
<div class="feedback-section">
<label class="feedback-label">${r("was_maintenance_needed",e)}</label>
<div class="feedback-buttons">
<button
class="feedback-btn ${this._feedback==="needed"?"selected":""}"
@click=${()=>this._setFeedback("needed")}
>${r("feedback_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_needed"?"selected":""}"
@click=${()=>this._setFeedback("not_needed")}
>${r("feedback_not_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_sure"?"selected":""}"
@click=${()=>this._setFeedback("not_sure")}
>${r("feedback_not_sure",e)}</button>
</div>
</div>
`:d}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${r("cancel",e)}
</ha-button>
<ha-button
@click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(k[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};i.styles=[m,b`
.req-mark {
color: var(--error-color, #f44336);
margin-left: 2px;
font-weight: 600;
}
/* #104: one-click cost suggestion from parts — quiet link-style chip. */
.cost-suggestion {
align-self: flex-start;
margin-top: 4px;
padding: 0;
border: none;
background: none;
color: var(--primary-color);
font-size: 12.5px;
cursor: pointer;
text-decoration: underline dotted;
text-underline-offset: 2px;
}
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.consumes-hint {
font-size: 13px;
color: var(--secondary-text-color);
border-left: 3px solid var(--primary-color);
padding: 4px 8px;
margin: 4px 0 8px;
}
/* #99: editable per-completion parts selection */
.used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; }
.used-part-row { display: flex; align-items: center; gap: 8px; }
.used-part-check {
display: flex; align-items: center; gap: 6px; flex: 1;
font-size: 13px; cursor: pointer;
}
.used-part-check input { cursor: pointer; }
/* #111: whose stock this row draws on. Muted but never omitted — an
unlabelled foreign pool is indistinguishable from an own part. */
.used-part-owner { color: var(--secondary-text-color); }
.used-part-qty {
width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px;
border: 1px solid var(--divider-color);
background: var(--card-background-color);
color: var(--primary-text-color);
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
/* .field/.field-label/.field-input come from nativeFieldStyles */
.photo-pick {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px dashed var(--divider-color);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
width: fit-content;
}
.photo-pick:hover { border-color: var(--primary-color); }
.photo-pick input[type="file"] { display: none; }
.photo-preview {
position: relative;
width: fit-content;
}
.photo-preview img {
max-width: 160px;
max-height: 160px;
border-radius: 8px;
display: block;
}
.photo-remove {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: var(--error-color, #db4437);
color: #fff;
cursor: pointer;
font-size: 12px;
line-height: 1;
}
.checklist-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid var(--divider-color);
margin-bottom: 4px;
}
.checklist-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
font-size: 14px;
}
.checklist-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.feedback-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-top: 1px solid var(--divider-color);
}
.feedback-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.feedback-buttons {
display: flex;
gap: 8px;
}
.feedback-btn {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.feedback-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
}
.feedback-btn.selected {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
`],s([n({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"entryId",2),s([n()],i.prototype,"taskId",2),s([n()],i.prototype,"taskName",2),s([n()],i.prototype,"lang",2),s([n({type:Array})],i.prototype,"checklist",2),s([n({type:Boolean})],i.prototype,"adaptiveEnabled",2),s([n()],i.prototype,"taskType",2),s([n()],i.prototype,"readingUnit",2),s([n({attribute:!1})],i.prototype,"restockDefault",2),s([n({attribute:!1})],i.prototype,"restockUnitCost",2),s([n()],i.prototype,"currencySymbol",2),s([n({attribute:!1})],i.prototype,"parts",2),s([n({attribute:!1})],i.prototype,"consumesParts",2),s([n({type:Array})],i.prototype,"consumesInfo",2),s([n({type:Array})],i.prototype,"requiredFields",2),s([n()],i.prototype,"phaseLabel",2),s([l()],i.prototype,"_open",2),s([l()],i.prototype,"_notes",2),s([l()],i.prototype,"_cost",2),s([l()],i.prototype,"_duration",2),s([l()],i.prototype,"_loading",2),s([l()],i.prototype,"_error",2),s([l()],i.prototype,"_checklistState",2),s([l()],i.prototype,"_feedback",2),s([l()],i.prototype,"_photoDocId",2),s([l()],i.prototype,"_photoPreview",2),s([l()],i.prototype,"_photoUploading",2),s([l()],i.prototype,"_readingValue",2),s([l()],i.prototype,"_restockQty",2),s([l()],i.prototype,"_completedAt",2),s([l()],i.prototype,"_usedParts",2),s([n({attribute:!1})],i.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",i);export{i as a};
@@ -0,0 +1,54 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as t,b as a,c as i,f as l,g as p,k as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var e=class extends p{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(n){let o=n.target.value;this.value=o,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:o}}))}render(){return i`
<label class="field">
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:l}</span>`:l}
<input
.value=${this.value??""}
.type=${this.type}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
step=${this.step??l}
min=${this.min??l}
max=${this.max??l}
pattern=${this.pattern??l}
@input=${this._onInput}
@change=${this._onInput}
/>
${this.helper?i`<span class="helper">${this.helper}</span>`:l}
</label>
`}};e.styles=a`
: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; }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,t([r()],e.prototype,"label",2),t([r()],e.prototype,"value",2),t([r()],e.prototype,"placeholder",2),t([r()],e.prototype,"type",2),t([r({type:Boolean})],e.prototype,"required",2),t([r({type:Boolean})],e.prototype,"disabled",2),t([r()],e.prototype,"step",2),t([r()],e.prototype,"min",2),t([r()],e.prototype,"max",2),t([r()],e.prototype,"pattern",2),t([r()],e.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e);
@@ -0,0 +1,213 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GSH25Q5Z.js";import{a,b as v,c as n,f as p,g as b,k as h,l as c,p as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";function d(l){return l.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function f(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":d(l)}function q(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var o=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,r,s){this._entryId=e,this._taskId=i,this._objectName=r,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=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 r=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&r.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(r);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let r=i?.code,s=i?.message;this._error=r==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,r=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let g=this.lang||"en",u=d(i),m=d(r),_=!!this._completeResult,w=d(t("qr_action_view",g)),$=d(t("qr_action_complete",g));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="color-scheme" content="light">
<title>${u}</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:${_?"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>${u}</h2>
${m?`<div class="sub">${m}</div>`:""}
<div class="qr-row">
<div class="qr-col">
<img src="${f(this._viewResult.svg_data_uri)}" alt="QR Info" />
<div class="qr-label">${w}</div>
</div>
${_?`<div class="qr-col">
<img src="${f(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${$}</div>
</div>`:""}
</div>
<div class="url">${d(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
</body></html>`),s.document.close()}_downloadSvg(e,i){let r=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),s=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;x(r,`qr-${q(s)}-${i}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return n``;let e=this.lang||this.hass?.language||"en",i=this._taskName?`${t("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${t("qr_code",e)}: ${this._objectName}`,r=!!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">${t("qr_generating",e)}</div>`:this._error?n`<div class="error">${this._error}</div>`:r?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">${t("qr_action_view",e)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._viewResult,"info")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download",e)}
</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">${t("qr_action_complete",e)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._completeResult,"complete")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download",e)}
</button>
</div>
`:p}
</div>
<div class="url-display">${this._viewResult.url}</div>
`:p}
<div class="action-row">
<label>${t("qr_url_mode",e)}</label>
<div class="action-toggle">
<button class="toggle-btn ${this._urlMode==="companion"?"active":""}"
@click=${()=>this._setUrlMode("companion")}>${t("qr_mode_companion",e)}</button>
<button class="toggle-btn ${this._urlMode==="local"?"active":""}"
@click=${()=>this._setUrlMode("local")}>${t("qr_mode_local",e)}</button>
<button class="toggle-btn ${this._urlMode==="server"?"active":""}"
@click=${()=>this._setUrlMode("server")}>${t("qr_mode_server",e)}</button>
</div>
</div>
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel",e)}
</ha-button>
<ha-button
@click=${this._print}
.disabled=${!r}
>
${t("qr_print",e)}
</ha-button>
</div>
</ha-dialog>
`}};o.styles=v`
.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;
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 {
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})],o.prototype,"hass",2),a([h()],o.prototype,"lang",2),a([c()],o.prototype,"_open",2),a([c()],o.prototype,"_loading",2),a([c()],o.prototype,"_error",2),a([c()],o.prototype,"_viewResult",2),a([c()],o.prototype,"_completeResult",2),a([c()],o.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",o);export{o as a};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{b as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GSH25Q5Z.js";async function l(e,n,t=300){return(await e.connection.sendMessagePromise({type:"auth/sign_path",path:n,expires:t})).path}async function r(e,n,t=300){return l(e,`/api/maintenance_supporter/document/${n}`,t)}async function y(e,n,t=""){let a=window.open("about:blank","_blank");try{let o=await r(e,n);a&&(a.location.href=new URL(o+t,window.location.origin).href)}catch(o){throw a&&a.close(),o}}async function p(e,n,t){i(await r(e,n,30),t)}function d(e){let n=URL.createObjectURL(new Blob([e],{type:"text/html"}));window.open(n,"_blank"),setTimeout(()=>URL.revokeObjectURL(n),6e4)}var c=[{key:"name",labelKey:"name",required:!0},{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=c.map(e=>e.key),s=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function g(e){if(!Array.isArray(e))return[...s];let n=new Set,t=[];for(let a of e)typeof a=="string"&&u.includes(a)&&!n.has(a)&&(n.add(a),t.push(a));return t.length?(t.includes("name")||t.unshift("name"),t):[...s]}export{l as a,r as b,y as c,p as d,d as e,c as f,s as g,g as h};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.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,810 @@
/*! maintenance_supporter frontend 2.65.0 */
import{c as nt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-UBKG7VLY.js";import{a as A}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import{A as tt,C as et,a as m,b as C,c as o,d as H,f as p,g as S,k as j,l as y,m as q,p as a,r as E,s as Z,t as Y,u as O,v as Q,x as J,z as X}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var k=class extends S{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return E(this.hass)}openEdit(t){this._draft={...t},this._originalSnapshot={...t},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let t=this._draft;if(t)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),s=[];for(let l of e.parts||[]){let d=l.entry_id===t.entry_id,u=l.consumers.some(c=>c.entry_id===t.entry_id&&c.task_id===t.task_id);!d&&!u||s.push({part_id:l.part_id,name:l.name,entry_id:l.entry_id,foreign:!d,object_name:l.object_name})}for(let l of t.used_parts||[]){let d=l.entry_id||t.entry_id;s.some(u=>u.part_id===l.part_id&&u.entry_id===d)||s.push({part_id:l.part_id,name:l.name||l.part_id,entry_id:d,foreign:d!==t.entry_id,object_name:null})}let r={};for(let l of t.used_parts||[])r[`${l.entry_id||t.entry_id}:${l.part_id}`]=l.quantity??1;this._partOptions=s,this._partQty=r,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,t])=>t>0).sort(([t],[e])=>t.localeCompare(e)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(t,e){this._draft&&(this._draft={...this._draft,[t]:e})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let t={type:"maintenance_supporter/task/history/update",entry_id:this._draft.entry_id,task_id:this._draft.task_id,original_timestamp:this._originalSnapshot.original_timestamp};if(this._draft.timestamp!==this._originalSnapshot.timestamp&&(t.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(t.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(t.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(t.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(t.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(t.used_parts=(this._partOptions||[]).filter(s=>(this._partQty[`${s.entry_id}:${s.part_id}`]||0)>0).map(s=>({part_id:s.part_id,quantity:this._partQty[`${s.entry_id}:${s.part_id}`],...s.foreign?{entry_id:s.entry_id}:{}}))),Object.keys(t).filter(s=>!["type","entry_id","task_id","original_timestamp"].includes(s)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(t),this.dispatchEvent(new CustomEvent("history-entry-saved",{detail:{entry_id:this._draft.entry_id,task_id:this._draft.task_id,new_timestamp:this._draft.timestamp},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=A(t,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return p;let t=this._lang,e=this._draft;return o`
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
<h2>${a("history_edit_title",t)||"Edit history entry"}</h2>
<div class="entry-type">
<ha-icon icon="mdi:tag-outline"></ha-icon>
<span>${a(e.type,t)||e.type}</span>
</div>
<label>
<span>${a("history_edit_timestamp",t)||"Timestamp"}</span>
<input type="datetime-local"
.value=${e.timestamp.length>=16?e.timestamp.slice(0,16):e.timestamp}
@change=${s=>{let r=s.target.value;this._set("timestamp",r.length===16?`${r}:00`:r)}} />
</label>
<label>
<span>${a("notes_label",t)}</span>
<textarea
rows="3"
@input=${s=>{let r=s.target.value;this._set("notes",r||null)}}
.value=${e.notes??""}></textarea>
</label>
<div class="row">
<label>
<span>${a("cost",t)||"Cost"}</span>
<input type="number" min="0" step="0.01"
.value=${e.cost!=null?String(e.cost):""}
@input=${s=>{let r=s.target.value;this._set("cost",r?Number(r):null)}} />
</label>
<label>
<span>${a("duration",t)||"Duration (min)"}</span>
<input type="number" min="0"
.value=${e.duration!=null?String(e.duration):""}
@input=${s=>{let r=s.target.value;this._set("duration",r?Number(r):null)}} />
</label>
</div>
${this._partOptions&&this._partOptions.length>0?o`
<div class="parts-block">
<span class="parts-title">${a("complete_parts_used",t)}</span>
${this._partOptions.map(s=>{let r=`${s.entry_id}:${s.part_id}`,l=this._partQty[r]||0;return o`
<label class="part-row-edit">
<input type="checkbox" .checked=${l>0}
@change=${d=>{let u=d.target.checked;this._partQty={...this._partQty,[r]:u?1:0}}} />
<span class="part-label">${s.name}${s.foreign&&s.object_name?` (${s.object_name})`:""}</span>
${l>0?o`
<input class="part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(l)}
@input=${d=>{let u=parseFloat(d.target.value);!isNaN(u)&&u>0&&(this._partQty={...this._partQty,[r]:u})}} />
`:p}
</label>
`})}
</div>
`:p}
${this._error?o`<div class="error">${this._error}</div>`:p}
<div class="actions">
<button class="cancel" @click=${this.close} ?disabled=${this._saving}>
${a("cancel",t)||"Cancel"}
</button>
<button class="save" @click=${this._save} ?disabled=${this._saving}>
${this._saving?a("saving",t)||"Saving\u2026":a("save",t)||"Save"}
</button>
</div>
</div>
`}};k.styles=C`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0;
background: rgba(0,0,0,0.5);
z-index: 100;
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: 480px;
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: 12px;
z-index: 101;
max-height: 90vh; overflow: auto;
}
h2 { margin: 0; font-size: 18px; }
.entry-type {
display: flex; align-items: center; gap: 6px;
color: var(--secondary-text-color); font-size: 13px;
}
label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
label span { color: var(--secondary-text-color); }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
input, textarea {
padding: 8px; font-size: 14px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color, #444);
border-radius: 6px;
width: 100%; box-sizing: border-box;
font-family: inherit;
}
.actions {
display: flex; gap: 8px; justify-content: flex-end;
margin-top: 8px;
}
button {
padding: 8px 16px; font-size: 14px;
border-radius: 6px; cursor: pointer;
border: none; font-weight: 500;
}
button.cancel {
background: transparent;
color: var(--primary-text-color);
border: 1px solid var(--divider-color);
}
button.save {
background: var(--primary-color);
color: var(--text-primary-color, white);
}
button[disabled] { opacity: 0.5; cursor: wait; }
.error {
color: var(--error-color, #d32f2f);
font-size: 13px; padding: 8px;
background: rgba(211,47,47,0.1);
border-radius: 6px;
}
/* #130: parts on the entry */
.parts-block {
display: flex; flex-direction: column; gap: 6px;
border: 1px solid var(--divider-color, #444);
border-radius: 6px; padding: 8px;
}
.parts-title { color: var(--secondary-text-color); font-size: 13px; }
.part-row-edit {
display: flex; flex-direction: row; align-items: center; gap: 8px;
font-size: 14px;
}
.part-row-edit input[type="checkbox"] { width: auto; }
.part-label { flex: 1; color: var(--primary-text-color); }
.part-qty { width: 76px; }
`,m([j({attribute:!1})],k.prototype,"hass",2),m([y()],k.prototype,"_open",2),m([y()],k.prototype,"_saving",2),m([y()],k.prototype,"_error",2),m([y()],k.prototype,"_draft",2),m([y()],k.prototype,"_partOptions",2),m([y()],k.prototype,"_partQty",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",k);var xt={days:1,weeks:7,months:30.4368,years:365.25};function K(n,i){return!n||n<=0?0:n*(xt[i||"days"]??1)}function Kt(n,i,t){let e=K(n,t);if(e<=0||i==null)return{pct:0,overflow:!1};let s=(e-i)/e*100;return{pct:Math.max(0,Math.min(100,s)),overflow:s>100}}var st=5;function R(n){let i=n.getFullYear(),t=String(n.getMonth()+1).padStart(2,"0"),e=String(n.getDate()).padStart(2,"0");return`${i}-${t}-${e}`}function $t(n,i){let t=[];for(let e=0;e<i;e++){let s=new Date(n);s.setDate(s.getDate()+e),s.setHours(0,0,0,0),t.push(R(s))}return t}function V(n,i){let[t,e,s]=n.split("-").map(Number),r=new Date(t,e-1,s);return r.setDate(r.getDate()+i),R(r)}function kt(n){if(!n||n.length===0)return null;let i=n.map(t=>t.cost).filter(t=>typeof t=="number");return i.length===0?null:i.reduce((t,e)=>t+e,0)/i.length}function wt(n){let{windowStart:i,windowEnd:t,task:e,entryId:s,objectName:r}=n,l=[],d=(_,g)=>({date:_,entry_id:s,task_id:e.id,task_name:e.name,object_name:r,status:g&&(e.status==="overdue"||e.status==="triggered")?"ok":e.status,days_until_due:g?null:e.days_until_due??null,projected:g,schedule_type:e.schedule_type,interval_days:e.interval_days??null,interval_unit:e.interval_unit??null,responsible_user_id:e.responsible_user_id??null,avg_cost:kt(e.history),adaptive_enabled:!!e.adaptive_config?.enabled,prediction_confidence:e.threshold_prediction_confidence??null}),u=Math.max(1,Math.round(K(e.interval_days,e.interval_unit)));if(e.status==="overdue"||e.status==="triggered"){if(l.push(d(i,!1)),e.schedule_type==="time_based"&&e.interval_days&&e.interval_days>0){let _=V(i,u),g=1;for(;_<=t&&g<st;)l.push(d(_,!0)),g++,_=V(_,u)}return l}let c=e.next_due;if(typeof c!="string"||!c)return l;let h=c.slice(0,10);if(h>=i&&h<=t)l.push(d(h,!1));else if(h>t)return l;if(e.schedule_type==="time_based"&&e.interval_days&&e.interval_days>0){let _=V(h,u),g=l.length;for(;_<=t&&g<st;)_>=i&&(l.push(d(_,!0)),g++),_=V(_,u)}return l}var it={overdue:0,triggered:1,due_soon:2,ok:3};function Zt(n,i,t,e=null){let s=$t(i,t),r=s[0],l=s[s.length-1],d=[];for(let c of n){let h=c.object?.name||"",_=c.entry_id,g=c.tasks||[];for(let f of g){if(e&&f.responsible_user_id!==e||f.enabled===!1)continue;let x=wt({windowStart:r,windowEnd:l,task:f,entryId:_,objectName:h});d.push(...x)}}let u=new Map;for(let c of s)u.set(c,[]);for(let c of d){let h=u.get(c.date);h&&h.push(c)}for(let[,c]of u)c.sort((h,_)=>{let g=it[h.status]??99,f=it[_.status]??99;if(g!==f)return g-f;if(h.projected!==_.projected)return h.projected?1:-1;let x=h.object_name.localeCompare(_.object_name);return x!==0?x:h.task_name.localeCompare(_.task_name)});return s.map(c=>({date:c,events:u.get(c)??[]}))}var At={completed:"ok",reset:"ok",skipped:"due_soon",missed:"overdue",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"};function Lt(n,i){let t=[];for(let e=i-1;e>=0;e--){let s=new Date(n);s.setDate(s.getDate()-e),s.setHours(0,0,0,0),t.push(R(s))}return t}function Yt(n,i,t,e=null){let s=Lt(i,t),r=s[0],l=s[s.length-1],d=new Map;for(let c of s)d.set(c,[]);for(let c of n){let h=c.object?.name||"",_=c.entry_id,g=c.tasks||[];for(let f of g){if(e&&f.responsible_user_id!==e)continue;let x=f.history||[];for(let v of x){if(typeof v?.timestamp!="string")continue;let M=v.timestamp.slice(0,10);if(M<r||M>l)continue;let I=d.get(M);if(!I)continue;let D=v.type??"completed";I.push({date:M,entry_id:_,task_id:f.id,task_name:f.name,object_name:h,status:At[D]??"ok",days_until_due:null,projected:!1,schedule_type:f.schedule_type,interval_days:f.interval_days??null,responsible_user_id:f.responsible_user_id??null,avg_cost:typeof v.cost=="number"?v.cost:null,adaptive_enabled:!!f.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:v.timestamp,history_type:D,history_cost:typeof v.cost=="number"?v.cost:null,history_notes:typeof v.notes=="string"?v.notes:null,history_duration:typeof v.duration=="number"?v.duration:null})}}}let u={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,c]of d)c.sort((h,_)=>{let g=u[h.history_type??""]??99,f=u[_.history_type??""]??99;if(g!==f)return g-f;let x=h.object_name.localeCompare(_.object_name);return x!==0?x:h.task_name.localeCompare(_.task_name)});return s.map(c=>({date:c,events:d.get(c)??[]}))}function Mt(n,i){if(i<=0)return 0;let t=typeof n=="number"&&Number.isFinite(n)?Math.trunc(n):0;return t<0?0:t%i}function Dt(n){return!!(n?.phases&&n.phase_sequence&&n.phase_sequence.length>0)}function U(n){if(!n||!Dt(n))return null;let i=n.phase_sequence,t=Mt(n.phase_cursor,i.length),e=i[t],s=n.phases?.[e];return s?{id:e,name:s.name,index:t,count:i.length,notes:s.notes,checklist:s.checklist!==void 0?s.che Line truncated
<div class="weibull-section">
<div class="weibull-title">
<ha-svg-icon aria-hidden="true" path="M3,14L3.5,14.07L8.07,9.5C7.89,8.85 8.06,8.11 8.59,7.59C9.37,6.8 10.63,6.8 11.41,7.59C11.94,8.11 12.11,8.85 11.93,9.5L14.5,12.07L15,12C15.18,12 15.35,12 15.5,12.07L19.07,8.5C19,8.35 19,8.18 19,8A2,2 0 0,1 21,6A2,2 0 0,1 23,8A2,2 0 0,1 21,10C20.82,10 20.65,10 20.5,9.93L16.93,13.5C17,13.65 17,13.82 17,14A2,2 0 0,1 15,16A2,2 0 0,1 13,14L13.07,13.5L10.5,10.93C10.18,11 9.82,11 9.5,10.93L4.93,15.5L5,16A2,2 0 0,1 3,18A2,2 0 0,1 1,16A2,2 0 0,1 3,14Z"></ha-svg-icon>
${a("weibull_reliability_curve",i)}
${Pt(e,i)}
</div>
${Tt(e,s,r,l,i)}
${Et(t,i)}
${t?.confidence_interval_low!=null?It(t,n,i):p}
</div>
`}function Pt(n,i){let t,e,s;return n<.8?(t="early_failures",e="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",s="beta_early_failures"):n<=1.2?(t="random_failures",e="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",s="beta_random_failures"):n<=3.5?(t="wear_out",e="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",s="beta_wear_out"):(t="highly_predictable",e="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",s="beta_highly_predictable"),o`
<span class="beta-badge ${t}">
<ha-svg-icon path="${e}"></ha-svg-icon>
${a(s,i)} (\u03B2=${n.toFixed(2)})
</span>
`}function Tt(n,i,t,e,s){let f=Math.max(t,e,i,1)*1.3,x=50,v=[];for(let $=0;$<=x;$++){let w=$/x*f,gt=1-Math.exp(-Math.pow(w/i,n)),vt=32+w/f*260,bt=136-gt*128;v.push([vt,bt])}let M=v.map(([$,w])=>`${$.toFixed(1)},${w.toFixed(1)}`).join(" "),I="M32,136 "+v.map(([$,w])=>`L${$.toFixed(1)},${w.toFixed(1)}`).join(" ")+` L${v[x][0].toFixed(1)},136 Z`,D=32+t/f*260,F=1-Math.exp(-Math.pow(t/i,n)),z=136-F*128,ft=((1-F)*100).toFixed(0),G=32+e/f*260,yt=[0,.25,.5,.75,1];return o`
<div class="weibull-chart">
<svg viewBox="0 0 ${300} ${160}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${a("chart_weibull",s)}">
${yt.map($=>{let w=136-$*128;return H`
<line x1="${32}" y1="${w.toFixed(1)}" x2="${292}" y2="${w.toFixed(1)}"
stroke="var(--divider-color)" stroke-width="0.5" stroke-dasharray="${$===.5?"4,3":p}" />
<text x="${28}" y="${(w+3).toFixed(1)}" fill="var(--secondary-text-color)"
font-size="8" text-anchor="end">${($*100).toFixed(0)}%</text>
`})}
<text x="${32}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">0</text>
<text x="${324/2}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(f/2)}</text>
<text x="${292}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(f)}</text>
<path d="${I}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
<polyline points="${M}" fill="none"
stroke="var(--primary-color, #03a9f4)" stroke-width="2" />
${t>0?H`
<line x1="${D.toFixed(1)}" y1="${8}" x2="${D.toFixed(1)}" y2="${136 .toFixed(1)}"
stroke="var(--primary-color, #03a9f4)" stroke-width="1.5" stroke-dasharray="4,3" />
<circle cx="${D.toFixed(1)}" cy="${z.toFixed(1)}" r="3"
fill="var(--primary-color, #03a9f4)" />
<text x="${(D+4).toFixed(1)}" y="${(z-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)"
font-size="9" font-weight="600">R=${ft}%</text>
`:p}
${e>0&&e!==t?H`
<line x1="${G.toFixed(1)}" y1="${8}" x2="${G.toFixed(1)}" y2="${136 .toFixed(1)}"
stroke="var(--success-color, #4caf50)" stroke-width="1.5" stroke-dasharray="4,3" />
`:p}
<line x1="${32}" y1="${8}" x2="${32}" y2="${136}"
stroke="var(--secondary-text-color)" stroke-width="1" />
<line x1="${32}" y1="${136}" x2="${292}" y2="${136}"
stroke="var(--secondary-text-color)" stroke-width="1" />
</svg>
</div>
<div class="chart-legend">
<span class="legend-item"><span class="legend-swatch" style="background:var(--primary-color, #03a9f4)"></span> ${a("weibull_failure_probability",s)}</span>
${t>0?o`<span class="legend-item"><span class="legend-swatch" style="background:var(--primary-color, #03a9f4); opacity:0.5"></span> ${a("current_interval_marker",s)}</span>`:p}
${e>0&&e!==t?o`<span class="legend-item"><span class="legend-swatch" style="background:var(--success-color, #4caf50)"></span> ${a("recommended_marker",s)}</span>`:p}
</div>
`}function Et(n,i){return o`
<div class="weibull-info-row">
<div class="weibull-info-item">
<span>${a("characteristic_life",i)}</span>
<span class="weibull-info-value">${Math.round(n.weibull_eta)} ${a("days",i)}</span>
</div>
${n.weibull_r_squared!=null?o`
<div class="weibull-info-item">
<span>${a("weibull_r_squared",i)}</span>
<span class="weibull-info-value">${n.weibull_r_squared.toFixed(3)}</span>
</div>
`:p}
</div>
`}function It(n,i,t){let e=n.confidence_interval_low,s=n.confidence_interval_high,r=i.suggested_interval??i.interval_days??0,l=i.interval_days??0,d=Math.max(0,e-5),c=s+5-d,h=(e-d)/c*100,_=(s-e)/c*100,g=(r-d)/c*100,f=l>0?(l-d)/c*100:-1;return o`
<div class="confidence-range">
<div class="confidence-range-title">
${a("confidence_interval",t)}: ${r} ${a("days",t)} (${e}\u2013${s})
</div>
<div class="confidence-bar">
<div class="confidence-fill" style="left:${h.toFixed(1)}%;width:${_.toFixed(1)}%"></div>
${f>=0?o`<div class="confidence-marker current" style="left:${f.toFixed(1)}%"></div>`:p}
<div class="confidence-marker recommended" style="left:${g.toFixed(1)}%"></div>
</div>
<div class="confidence-labels">
<span class="confidence-text low">${a("confidence_conservative",t)} (${e}${a("days",t).charAt(0)})</span>
<span class="confidence-text high">${a("confidence_aggressive",t)} (${s}${a("days",t).charAt(0)})</span>
</div>
</div>
`}function rt(n,i,t){let e=n.degradation_trend!=null&&n.degradation_trend!=="insufficient_data",s=n.days_until_threshold!=null,r=n.environmental_factor!=null&&n.environmental_factor!==1;if(!e&&!s&&!r)return p;let l=n.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":n.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return o`
<div class="prediction-section">
${n.sensor_prediction_urgency?o`
<div class="prediction-urgency-banner">
<ha-svg-icon path="M1,21H23L12,2L1,21M12,18A1,1 0 0,1 11,17A1,1 0 0,1 12,16A1,1 0 0,1 13,17A1,1 0 0,1 12,18M13,15H11V10H13V15Z"></ha-svg-icon>
${a("sensor_prediction_urgency",i).replace("{days}",String(Math.round(n.days_until_threshold||0)))}
</div>
`:p}
<div class="prediction-title">
<ha-svg-icon path="M2,2V4H7V2H2M22,2V4H13V2H22M7,7V9H2V7H7M22,7V9H13V7H22M7,12V14H2V12H7M22,12V14H13V12H22M7,17V19H2V17H7M22,17V19H13V17H22M9,2V19L12,22L15,19V2H9M11,4H13V17.17L12,18.17L11,17.17V4Z"></ha-svg-icon>
${a("sensor_prediction",i)}
</div>
<div class="prediction-grid">
${e?o`
<div class="prediction-item">
<ha-svg-icon path="${l}"></ha-svg-icon>
<span class="prediction-label">${a("degradation_trend",i)}</span>
<span class="prediction-value ${n.degradation_trend}">${a("trend_"+n.degradation_trend,i)}</span>
${n.degradation_rate!=null?o`<span class="prediction-rate">${n.degradation_rate>0?"+":""}${Math.abs(n.degradation_rate)>=10?Math.round(n.degradation_rate).toLocaleString():n.degradation_rate.toFixed(1)} ${n.trigger_entity_info?.unit_of_measurement||""}/${a("day_short",i)}</span>`:p}
</div>
`:p}
${s?o`
<div class="prediction-item">
<ha-svg-icon path="M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M12.5,8H11V14L15.75,16.85L16.5,15.62L12.5,13.25V8M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72Z"></ha-svg-icon>
<span class="prediction-label">${a("days_until_threshold",i)}</span>
<span class="prediction-value prediction-days${n.days_until_threshold===0?" exceeded":n.sensor_prediction_urgency?" urgent":""}">${n.days_until_threshold===0?a("threshold_exceeded",i):"~"+Math.round(n.days_until_threshold)+" "+a("days",i)}</span>
${n.threshold_prediction_date?o`<span class="prediction-date">${O(n.threshold_prediction_date,i)}</span>`:p}
${n.threshold_prediction_confidence?o`<span class="confidence-dot ${n.threshold_prediction_confidence}"></span>`:p}
</div>
`:p}
${r&&t.environmental?o`
<div class="prediction-item">
<ha-svg-icon path="M15,13V5A3,3 0 0,0 12,2A3,3 0 0,0 9,5V13A5,5 0 0,0 7,17A5,5 0 0,0 12,22A5,5 0 0,0 17,17A5,5 0 0,0 15,13M12,4A1,1 0 0,1 13,5V8H11V5A1,1 0 0,1 12,4Z"></ha-svg-icon>
<span class="prediction-label">${a("environmental_adjustment",i)}</span>
<span class="prediction-value">${n.environmental_factor.toFixed(2)}x</span>
${n.environmental_entity?o`<span class="prediction-entity entity-link" @click=${d=>tt(d,n.environmental_entity)}>${n.environmental_entity}</span>`:p}
</div>
`:p}
</div>
</div>
`}function ot(n,i,t,e){let s=Math.max(n||1,i);return o`
<div class="interval-comparison">
<div class="interval-bar">
<div class="interval-label">
${a("current",e)}: ${n??"\u2014"} ${n!=null?a("days",e):""}
</div>
<div class="interval-visual current"
style="width: ${n!=null?Math.min(n/s*100,100):0}%"></div>
</div>
<div class="interval-bar">
<div class="interval-label">
${a("recommended",e)}: ${i} ${a("days",e)}
<span class="confidence-badge ${t}">${a(`confidence_${t}`,e)}</span>
</div>
<div class="interval-visual suggested"
style="width: ${Math.min(i/s*100,100)}%"></div>
</div>
</div>
`}var lt=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"];function dt(n,i,t){if(!t.seasonal||!n.seasonal_factor||n.seasonal_factor===1)return p;let e=lt.map(d=>a(d,i)),s=new Date().getMonth(),r=n.seasonal_factors||n.interval_analysis?.seasonal_factors||null,l=r&&r.length===12?r:e.map((d,u)=>{let c=n.seasonal_factor||1,h=Math.sin((u-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,c+h))});return o`
<div class="seasonal-card-compact">
<h4>${a("seasonal_awareness",i)}</h4>
<div class="seasonal-mini-chart">
${l.map((d,u)=>{let c=d*40,h=d<.9?"low":d>1.1?"high":"normal";return o`
<div class="seasonal-bar ${h} ${u===s?"current":""}"
style="height: ${c}px"
title="${e[u]}: ${d.toFixed(2)}x">
</div>
`})}
</div>
<div class="seasonal-legend">
<span class="legend-item"><span class="dot low"></span> ${a("shorter",i)||"K\xFCrzer"}</span>
<span class="legend-item"><span class="dot normal"></span> ${a("normal",i)||"Normal"}</span>
<span class="legend-item"><span class="dot high"></span> ${a("longer",i)||"L\xE4nger"}</span>
</div>
</div>
`}function ct(n,i){return Ct(n,i)}function Ct(n,i){let t=n.seasonal_factors??n.interval_analysis?.seasonal_factors;if(!t||t.length!==12)return p;let e=n.interval_analysis?.seasonal_reason,s=new Date().getMonth(),r=300,l=100,d=8,c=l-d-4,h=Math.max(...t,1.5),_=r/12,g=_*.65,f=d+c-1/h*c;return o`
<div class="seasonal-chart">
<div class="seasonal-chart-title">
<ha-svg-icon aria-hidden="true" path="M17.75 4.09L15.22 6.03L16.13 9.09L13.5 7.28L10.87 9.09L11.78 6.03L9.25 4.09L12.44 4L13.5 1L14.56 4L17.75 4.09M21.25 11L19.61 12.25L20.2 14.23L18.5 13.06L16.8 14.23L17.39 12.25L15.75 11L17.81 10.95L18.5 9L19.19 10.95L21.25 11M18.97 15.95C19.8 15.87 20.69 17.05 20.16 17.8C19.84 18.25 19.5 18.67 19.08 19.07C15.17 23 8.84 23 4.94 19.07C1.03 15.17 1.03 8.83 4.94 4.93C5.34 4.53 5.76 4.17 6.21 3.85C6.96 3.32 8.14 4.21 8.06 5.04C7.79 7.9 8.75 10.87 10.95 13.06C13.14 15.26 16.1 16.22 18.97 15.95Z"></ha-svg-icon>
${a("seasonal_chart_title",i)}
${e?o`<span class="source-tag">${e==="learned"?a("seasonal_learned",i):a("seasonal_manual",i)}</span>`:p}
</div>
<svg viewBox="0 0 ${r} ${l}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${a("chart_seasonal",i)}">
<line x1="0" y1="${f.toFixed(1)}" x2="${r}" y2="${f.toFixed(1)}"
stroke="var(--divider-color)" stroke-width="1" stroke-dasharray="4,3" />
${t.map((x,v)=>{let M=x/h*c,I=v*_+(_-g)/2,D=d+c-M,F=v===s,z=x<1?"var(--success-color, #4caf50)":x>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return H`
<rect x="${I.toFixed(1)}" y="${D.toFixed(1)}"
width="${g.toFixed(1)}" height="${M.toFixed(1)}"
fill="${z}" opacity="${F?1:.5}" rx="2" />
`})}
</svg>
<div class="seasonal-labels">
${lt.map((x,v)=>o`<span class="seasonal-label ${v===s?"active-month":""}">${a(x,i)}</span>`)}
</div>
</div>
`}var b=class extends S{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1}get _lang(){return E(this.hass)}async openFor(t,e){this._entryId=t,this._taskId=e,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=R(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});t?.features&&(this._features={...this._features,...t.features}),this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=t.object?.name||"";let e=(t.tasks||[]).find(s=>s.id===this._taskId);this._task=e??null}catch(t){this._error=A(t,this._lang)}}async _runWs(t){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(t),this._busy=!1,!0}catch(e){return this._error=A(e,this._lang),this._busy=!1,!1}}_notifyChanged(t){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:t},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(async({openCompleteDialog:t})=>{let e=this._task,s=!!e.part_ref,r=U(e),l=r?r.consumesParts:e.consumes_parts||[],d=[];if(!s)try{let c=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0});d=nt({consumes_parts:l},this._entryId,c.objects||[],this._lang)}catch{}t({entry_id:this._entryId,task_id:this._taskId,task_name:e.name,checklist:r?r.checklist:e.checklist||[],adaptive_enabled:!!e.adaptive_config?.enabled,required_completion_fields:r?r.requiredFields:e.required_completion_fields||[],task_type:e.type||"",reading_unit:e.reading_unit||"",parts:d,consumes_parts:s?[]:l,phase_label:W(e)})&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(({openEditTaskDialog:t})=>{t(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(({openQrDialog:t})=>{t({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let t=a("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(t))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let t=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=a("suggestion_applied",this._lang)||"Applied",this._notifyChanged("apply_suggestion"),await this._loadTask(),setTimeout(()=>{this._toast=""},2500))}async _reanalyzeInterval(){if(!(!this._entryId||!this Line truncated
<div class="recommendation-card">
<h4>${a("suggested_interval",e)}</h4>
${ot(t.interval_days,t.suggested_interval,t.interval_confidence||"medium",e)}
<div class="recommendation-actions">
<button class="btn primary"
@click=${this._applySuggestion} ?disabled=${this._busy}>
<ha-icon icon="mdi:check"></ha-icon>
${a("apply_suggestion",e)}
</button>
<button class="btn"
@click=${this._reanalyzeInterval} ?disabled=${this._busy}>
<ha-icon icon="mdi:refresh"></ha-icon>
${a("reanalyze",e)}
</button>
</div>
</div>
`}_renderAdaptive(t){let e=this._lang,s=this._features.adaptive&&t.suggested_interval&&t.suggested_interval!==t.interval_days,r=t.degradation_trend!=null&&t.degradation_trend!=="insufficient_data"||t.days_until_threshold!=null||t.environmental_factor!=null&&t.environmental_factor!==1,l=this._features.adaptive&&t.interval_analysis?.weibull_beta!=null&&t.interval_analysis?.weibull_eta!=null,d=this._features.seasonal&&t.seasonal_factor&&t.seasonal_factor!==1;return!s&&!r&&!l&&!d?o`<div class="adaptive-empty">
${a("adaptive_no_data",e)||"Not enough completion history yet for adaptive analysis."}
</div>`:o`
<div class="adaptive-stack">
${this._toast?o`<div class="toast">${this._toast}</div>`:p}
${s?this._renderRecommendation(t):p}
${r?rt(t,e,this._features):p}
${l?at(t,e):p}
${d?o`
${dt(t,e,this._features)}
${t.seasonal_factors?.length===12||t.interval_analysis?.seasonal_factors?.length===12?ct(t,e):p}
`:p}
</div>
`}_renderDetails(t){let e=this._lang,s=t.history||[],r=s.filter(u=>u.type==="completed"),l=r.reduce((u,c)=>u+(typeof c.cost=="number"?c.cost:0),0),d=(()=>{let u=r.map(c=>typeof c.duration=="number"?c.duration:null).filter(c=>c!=null);return u.length?Math.round(u.reduce((c,h)=>c+h,0)/u.length):null})();return o`
<div class="details">
<div class="stats-grid">
<div class="stat">
<span class="stat-label">${a("times_performed",e)||"Performed"}</span>
<span class="stat-value">${r.length}</span>
</div>
<div class="stat">
<span class="stat-label">${a("total_cost",e)||"Total cost"}</span>
<span class="stat-value">${l.toFixed(2)}</span>
</div>
<div class="stat">
<span class="stat-label">${a("avg_duration",e)||"Avg duration"}</span>
<span class="stat-value">${d!=null?`${d}m`:"\u2014"}</span>
</div>
</div>
<div class="history-header">
<strong>${a("history",e)||"History"}</strong>
<span class="history-count">${s.length}</span>
</div>
${s.length===0?o`<div class="history-empty">${a("history_empty",e)||"No history yet."}</div>`:o`
<div class="history-list">
${[...s].reverse().slice(0,20).map(u=>{let c=["completed","reset","skipped"].includes(u.type);return o`
<div class="history-entry">
<div class="history-line">
<span class="history-type type-${u.type}">${a(u.type,e)}</span>
<span class="history-date">${Q(u.timestamp,e)}</span>
${c?o`<button class="history-edit"
title="${a("history_edit_button",e)||"Edit"}"
@click=${()=>this._onEditHistoryEntry(u)}>
<ha-icon icon="mdi:pencil"></ha-icon>
</button>`:p}
</div>
${u.notes?o`<div class="history-notes">${u.notes}</div>`:p}
${u.cost!=null||u.duration!=null?o`<div class="history-meta">
${u.cost!=null?o`<span>💰 ${u.cost.toFixed(2)}</span>`:p}
${u.duration!=null?o`<span>⏱️ ${u.duration}m</span>`:p}
</div>`:p}
</div>
`})}
${s.length>20?o`<div class="history-more">… +${s.length-20} ${a("older_entries",e)||"older"}</div>`:p}
</div>
`}
</div>
`}render(){if(!this._open)return p;let t=this._lang,e=this._task,s=this.hass?.user?.is_admin??!0;return o`
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
${e?o`
<div class="header">
<div class="title">
<span class="status-dot" style="background: ${q[e.status]||"#ccc"}"></span>
<span class="task-name">${e.name}</span>
</div>
<div class="object">
<button class="link-inline" @click=${()=>{this._entryId&&import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(({openObjectQuickActions:r})=>{r(this._entryId),this.close()})}}>${this._objectName}</button>
</div>
<div class="quick-info">
${e.next_due?o`<span><strong>${a("next_due",t)||"Next due"}:</strong> ${O(e.next_due,t)}</span>`:p}
${e.last_performed?o`<span><strong>${a("last_performed",t)||"Last"}:</strong> ${O(e.last_performed,t)}</span>`:p}
${e.schedule?.kind&&!["manual","one_time"].includes(e.schedule.kind)||e.interval_days!=null?o`<span><strong>${a("interval",t)||"Interval"}:</strong> ${X(e,t)}</span>`:p}
${W(e)?o`<span><strong>${a("phase_current",t)}:</strong> ${W(e)}</span>`:p}
</div>
</div>
${this._error?o`<div class="error">${this._error}</div>`:p}
${this._showSkip?o`
<div class="inline-form">
<label>${a("skip_reason",t)||"Skip reason (optional)"}</label>
<input type="text" .value=${this._skipReason}
@input=${r=>{this._skipReason=r.target.value}} />
<div class="inline-actions">
<button class="btn cancel" @click=${()=>{this._showSkip=!1}} ?disabled=${this._busy}>
${a("cancel",t)||"Cancel"}
</button>
<button class="btn primary" @click=${this._onSkipConfirm} ?disabled=${this._busy}>
${a("skip",t)||"Skip"}
</button>
</div>
</div>
`:this._showReset?o`
<div class="inline-form">
<label>${a("reset_to_date",t)||"Reset last_performed to"}</label>
<input type="date" .value=${this._resetDate}
@input=${r=>{this._resetDate=r.target.value}} />
<div class="inline-actions">
<button class="btn cancel" @click=${()=>{this._showReset=!1}} ?disabled=${this._busy}>
${a("cancel",t)||"Cancel"}
</button>
<button class="btn primary" @click=${this._onResetConfirm} ?disabled=${this._busy}>
${a("reset",t)||"Reset"}
</button>
</div>
</div>
`:o`
<div class="actions primary-row">
<button class="btn primary" @click=${this._onComplete} ?disabled=${this._busy}>
<ha-icon icon="mdi:check"></ha-icon>
${a("complete",t)||"Complete"}
</button>
<button class="btn" @click=${()=>{this._showSkip=!0}} ?disabled=${this._busy}>
<ha-icon icon="mdi:skip-next"></ha-icon>
${a("skip",t)||"Skip"}
</button>
<button class="btn" @click=${()=>{this._showReset=!0}} ?disabled=${this._busy}>
<ha-icon icon="mdi:restart"></ha-icon>
${a("reset",t)||"Reset"}
</button>
</div>
${s?o`
<div class="actions secondary-row">
<button class="btn ghost" @click=${this._onEdit} ?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
${a("edit",t)||"Edit"}
</button>
<button class="btn ghost" @click=${this._onQr} ?disabled=${this._busy}>
<ha-icon icon="mdi:qrcode"></ha-icon>
${a("qr_code",t)||"QR"}
</button>
<button class="btn ghost"
@click=${e.archived?this._onUnarchive:this._onArchive}
?disabled=${this._busy}>
<ha-icon icon="${e.archived?"mdi:archive-arrow-up-outline":"mdi:archive-outline"}"></ha-icon>
${e.archived?a("unarchive",t)||"Unarchive":a("archive",t)||"Archive"}
</button>
<button class="btn ghost danger" @click=${this._onDelete} ?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
${a("delete",t)||"Delete"}
</button>
</div>
`:p}
<div class="details-toggle">
<button class="link" @click=${()=>{this._showDetails=!this._showDetails}}>
<ha-icon icon="${this._showDetails?"mdi:chevron-up":"mdi:chevron-down"}"></ha-icon>
${this._showDetails?a("hide_details",t)||"Hide details":a("show_details",t)||"Show history + stats"}
</button>
${this._features.adaptive||this._features.seasonal||this._features.environmental?o`<button class="link" @click=${()=>{this._showAdaptive=!this._showAdaptive}}>
<ha-icon icon="${this._showAdaptive?"mdi:chart-line":"mdi:chart-line-variant"}"></ha-icon>
${this._showAdaptive?a("hide_stats",t)||"Hide stats":a("show_stats",t)||"Show stats + graphs"}
</button>`:p}
</div>
${this._showDetails?this._renderDetails(e):p}
${this._showAdaptive?this._renderAdaptive(e):p}
<div class="footer">
<button class="link" @click=${this._onOpenInPanel}>
<ha-icon icon="mdi:open-in-new"></ha-icon>
${a("open_in_panel",t)||"Open in Maintenance panel"}
</button>
</div>
`}
`:o`<div class="loading">${a("loading",t)||"Loading\u2026"}</div>`}
</div>
`}};b.styles=[et,C`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0; z-index: 100;
background: rgba(0,0,0,0.5);
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: 460px;
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: 14px;
z-index: 101;
}
.header { display: flex; flex-direction: column; gap: 6px; }
.title { display: flex; align-items: center; gap: 10px; }
.status-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.task-name { font-size: 18px; font-weight: 600; }
.object { font-size: 13px; color: var(--secondary-text-color); }
.link-inline {
background: transparent; border: none; padding: 0; cursor: pointer;
color: var(--primary-color); font-size: inherit; font-family: inherit;
}
.link-inline:hover { text-decoration: underline; }
.quick-info {
display: flex; flex-wrap: wrap; gap: 12px;
font-size: 12px; color: var(--secondary-text-color);
padding-top: 4px; border-top: 1px solid var(--divider-color);
}
.quick-info strong { color: var(--primary-text-color); font-weight: 500; }
.actions { display: flex; gap: 8px; }
.actions.primary-row { gap: 6px; }
.actions.primary-row .btn { flex: 1; }
/* Edit + QR are admin-tools — left-align as a group; Delete is destructive
so it gets pushed to the far right with margin-left:auto for visual
separation. Earlier this row was flex-end which left a strange empty
gap on the left (user feedback). */
.actions.secondary-row {
padding-top: 8px; border-top: 1px solid var(--divider-color);
justify-content: flex-start;
}
.actions.secondary-row .btn.danger {
margin-left: auto;
}
.btn {
padding: 8px 12px; font-size: 14px;
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: 6px;
transition: background 0.12s;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: wait; }
.btn.primary {
background: var(--primary-color);
color: var(--text-primary-color, white);
border-color: var(--primary-color);
}
.btn.cancel { background: transparent; }
.btn.ghost { padding: 6px 10px; font-size: 13px; }
.btn.danger { color: var(--error-color); }
.btn ha-icon { --mdc-icon-size: 18px; }
.inline-form { display: flex; flex-direction: column; gap: 8px; }
.inline-form label { font-size: 13px; color: var(--secondary-text-color); }
.inline-form input {
padding: 8px; font-size: 14px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color, #444);
border-radius: 6px;
}
.inline-actions { display: flex; gap: 8px; justify-content: flex-end; }
.footer { display: flex; justify-content: center; padding-top: 4px; }
.link {
background: transparent; border: none; cursor: pointer;
color: var(--primary-color); font-size: 13px;
display: inline-flex; align-items: center; gap: 4px;
}
.link:hover { text-decoration: underline; }
.link ha-icon { --mdc-icon-size: 14px; }
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); }
.error {
padding: 8px; border-radius: 6px;
background: rgba(211,47,47,0.1);
color: var(--error-color, #d32f2f); font-size: 13px;
}
/* Details (expandable Show details section) */
.details-toggle { display: flex; justify-content: center; margin-top: 4px; }
.details {
display: flex; flex-direction: column; gap: 12px;
border-top: 1px solid var(--divider-color);
padding-top: 12px;
}
.stats-grid {
display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px;
}
.stat {
display: flex; flex-direction: column; gap: 2px;
background: var(--secondary-background-color, rgba(255,255,255,0.04));
padding: 8px; border-radius: 6px;
align-items: center;
}
.stat-label { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.5px; }
.stat-value { font-size: 16px; font-weight: 600; }
.history-header {
display: flex; align-items: baseline; gap: 8px;
font-size: 14px;
}
.history-count {
font-size: 12px; color: var(--secondary-text-color);
background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px;
}
.history-empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; }
.history-list { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow: auto; }
.history-entry {
padding: 6px 8px; border-radius: 6px;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
font-size: 13px;
}
.history-line {
display: flex; align-items: center; gap: 8px;
justify-content: space-between;
}
.history-type {
font-weight: 600; font-size: 11px;
padding: 2px 6px; border-radius: 4px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.type-completed { background: rgba(46,125,50,0.2); color: #66bb6a; }
.type-skipped { background: rgba(158,158,158,0.2); color: var(--secondary-text-color); }
.type-reset { background: rgba(33,150,243,0.2); color: #64b5f6; }
.type-triggered { background: rgba(255,87,34,0.2); color: #ff8a65; }
.history-date { font-size: 11px; color: var(--secondary-text-color); flex: 1; text-align: right; }
.history-edit {
background: transparent; border: none; cursor: pointer;
padding: 4px; border-radius: 4px;
color: var(--secondary-text-color);
}
.history-edit:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); color: var(--primary-color); }
.history-edit ha-icon { --mdc-icon-size: 14px; }
.history-notes { margin-top: 4px; color: var(--primary-text-color); }
.history-meta { display: flex; gap: 12px; margin-top: 4px; color: var(--secondary-text-color); font-size: 11px; }
.history-more { padding: 8px; text-align: center; font-size: 12px; color: var(--secondary-text-color); font-style: italic; }
/* Adaptive section — wraps the panel renderers (which assume sharedStyles
are present) and adds dialog-specific layout. */
.adaptive-stack {
display: flex; flex-direction: column; gap: 12px;
border-top: 1px solid var(--divider-color);
padding-top: 12px;
}
.adaptive-empty {
padding: 16px; text-align: center;
color: var(--secondary-text-color);
font-style: italic; font-size: 13px;
border-top: 1px solid var(--divider-color);
}
.toast {
padding: 8px 12px; border-radius: 6px;
background: rgba(76, 175, 80, 0.15);
color: #4caf50; font-size: 13px; font-weight: 500;
}
/* The panel's recommendation-card uses ha-button. We use plain <button>
in this dialog's button styles. Re-style the action row to match. */
.recommendation-actions {
display: flex; gap: 8px; margin-top: 8px;
}
/* Constrain SVG charts so they fit the dialog width even on mobile. */
.weibull-section, .seasonal-card-compact { max-width: 100%; }
.weibull-chart svg { max-width: 100%; height: auto; }
.details-toggle { gap: 12px; flex-wrap: wrap; }
`],m([j({attribute:!1})],b.prototype,"hass",2),m([y()],b.prototype,"_open",2),m([y()],b.prototype,"_entryId",2),m([y()],b.prototype,"_taskId",2),m([y()],b.prototype,"_task",2),m([y()],b.prototype,"_objectName",2),m([y()],b.prototype,"_busy",2),m([y()],b.prototype,"_error",2),m([y()],b.prototype,"_showSkip",2),m([y()],b.prototype,"_showReset",2),m([y()],b.prototype,"_showDetails",2),m([y()],b.prototype,"_showAdaptive",2),m([y()],b.prototype,"_skipReason",2),m([y()],b.prototype,"_resetDate",2),m([y()],b.prototype,"_features",2),m([y()],b.prototype,"_toast",2);customElements.get("maintenance-task-quick-actions-dialog")||customElements.define("maintenance-task-quick-actions-dialog",b);function pt(n){return!!n&&/^https?:\/\//i.test(n)}var L=class extends S{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._data=null;this._busy=!1;this._error=""}get _lang(){return E(this.hass)}async openFor(t){this._entryId=t,this._error="",this._open=!0,await this._load()}close(){this._open=!1,this._data=null,this._error=""}async _load(){if(this._entryId)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._data=t}catch(t){this._error=A(t,this._lang)}}_onEditObject(){!this._entryId||!this._data||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(({openEditObjectDialog:t})=>{t(this._entryId,this._data.object),this.close()})}_onAddTask(){this._entryId&&import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(({openCreateTaskDialog:t})=>{t(this._entryId),this.close()})}async _onDelete(){if(!this._entryId||!this._data)return;let t=a("delete_object_confirm",this._lang)||`Delete "${this._data.object.name}" and all its tasks?`;if(window.confirm(t)){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/delete",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-deleted",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(e){this._error=A(e,this._lang)}finally{this._busy=!1}}}async _onArchiveObject(){if(!this._entryId||!this._data)return;let t=!!this._data.object.archived;if(!t){let e=a("confirm_archive_object",this._lang)||"Archive this object and its tasks?";if(!window.confirm(e))return}this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:t?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-changed",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(e){this._error=A(e,this._lang)}finally{this._busy=!1}}_onTaskClick(t){this._entryId&&import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-6AVGUS2J.js").then(({openTaskQuickActions:e})=>{e(this._entryId,t)})}render(){if(!this._open)return p;let t=this._lang,e=this._data,s=e?.object,r=e?.tasks||[],l=this.hass?.user?.is_admin??!0;return o`
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
${e&&s?o`
<div class="header">
<div class="title">${s.name}</div>
${this._renderMetaRow(s)}
</div>
${this._error?o`<div class="error">${this._error}</div>`:p}
<div class="tasks-section">
<div class="section-header">
<strong>${a("tasks",t)||"Tasks"}</strong>
<span class="count">${r.length}</span>
</div>
${r.length===0?o`<div class="empty">${a("no_tasks",t)||"No tasks yet."}</div>`:o`
<div class="task-list">
${r.map(d=>o`
<div class="task-row" @click=${()=>this._onTaskClick(d.id)}>
<span class="status-dot" style="background: ${q[d.status]||"#ccc"}"></span>
<span class="task-name">${d.name}</span>
<span class="task-status">${a(d.status||"ok",t)}</span>
</div>
`)}
</div>
`}
</div>
${s.notes?o`
<div class="notes-section">
<strong>${a("object_notes_label",t)}</strong>
<div class="notes-body">${s.notes}</div>
</div>
`:p}
${l?o`
<div class="actions">
<button class="btn primary" @click=${this._onAddTask} ?disabled=${this._busy}>
<ha-icon icon="mdi:plus"></ha-icon>
${a("add_task",t)||"Add task"}
</button>
<button class="btn" @click=${this._onEditObject} ?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
${a("edit",t)||"Edit"}
</button>
<button class="btn" @click=${this._onArchiveObject} ?disabled=${this._busy}>
<ha-icon icon="${s.archived?"mdi:archive-arrow-up-outline":"mdi:archive-outline"}"></ha-icon>
${s.archived?a("unarchive_object",t)||"Unarchive object":a("archive_object",t)||"Archive object"}
</button>
<button class="btn danger" @click=${this._onDelete} ?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
${a("delete",t)||"Delete"}
</button>
</div>
`:p}
`:o`<div class="loading">${a("loading",t)||"Loading\u2026"}</div>`}
</div>
`}_renderMetaRow(t){let e=this._lang,s=[];return t.area_id&&s.push([a("area",e),t.area_id]),t.manufacturer&&s.push([a("manufacturer",e),t.manufacturer]),t.model&&s.push([a("model",e),t.model]),t.serial_number&&s.push([a("serial_number_label",e),t.serial_number]),t.installation_date&&s.push([a("installed",e),t.installation_date]),t.warranty_expiry&&s.push([a("warranty",e),t.warranty_expiry]),t.documentation_url&&s.push([a("documentation_url_label",e),t.documentation_url]),s.length===0?p:o`
<div class="meta">
${s.map(([r,l])=>o`
<div class="meta-item">
<span class="meta-label">${r}</span>
<span class="meta-value">${pt(l)?o`<a href="${l}" target="_blank" rel="noopener noreferrer">${l}</a>`:l}</span>
</div>
`)}
</div>
`}};L.styles=C`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,0.5);
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: 480px;
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; z-index: 101;
display: flex; flex-direction: column; gap: 14px;
}
.header { display: flex; flex-direction: column; gap: 6px; }
.title { font-size: 20px; font-weight: 600; }
.meta { display: flex; flex-direction: column; gap: 4px; padding-top: 4px; border-top: 1px solid var(--divider-color); }
.meta-item { display: flex; gap: 8px; font-size: 12px; }
.meta-label { color: var(--secondary-text-color); min-width: 100px; }
.meta-value { color: var(--primary-text-color); flex: 1; word-break: break-word; }
.meta-value a { color: var(--primary-color); }
.tasks-section, .notes-section { display: flex; flex-direction: column; gap: 6px; }
.section-header { display: flex; align-items: baseline; gap: 8px; }
.count {
font-size: 11px; color: var(--secondary-text-color);
background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px;
}
.empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; padding: 8px 0; }
.task-list { display: flex; flex-direction: column; gap: 4px; max-height: 200px; overflow: auto; }
.task-row {
display: flex; align-items: center; gap: 10px;
padding: 8px; border-radius: 6px; cursor: pointer;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
transition: background 0.12s;
}
.task-row:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.status-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.task-name { flex: 1; font-size: 14px; }
.task-status { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; }
.notes-body { white-space: pre-wrap; font-size: 13px; padding: 8px; background: var(--secondary-background-color); border-radius: 6px; }
.actions { display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid var(--divider-color); }
.actions .btn { flex: 1; }
.btn {
padding: 8px; 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; justify-content: center; gap: 6px;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: wait; }
.btn.primary { background: var(--primary-color); color: var(--text-primary-color, white); border-color: var(--primary-color); }
.btn.danger { color: var(--error-color); }
.btn ha-icon { --mdc-icon-size: 16px; }
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); }
.error { padding: 8px; border-radius: 6px; background: rgba(211,47,47,0.1); color: var(--error-color); font-size: 13px; }
`,m([j({attribute:!1})],L.prototype,"hass",2),m([y()],L.prototype,"_open",2),m([y()],L.prototype,"_entryId",2),m([y()],L.prototype,"_data",2),m([y()],L.prototype,"_busy",2),m([y()],L.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",L);var ht="maintenance-object-dialog",_t="maintenance-task-dialog",Ht="maintenance-history-edit-dialog",St="maintenance-complete-dialog",jt="maintenance-qr-dialog",Ot="maintenance-task-quick-actions-dialog",Rt="maintenance-object-quick-actions-dialog";function B(){return document.querySelector("home-assistant")?.hass}function Ft(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function P(n){let i=Ft(),t=i.querySelector(n)??document.body.querySelector(n);return t?t.parentNode!==i&&i.appendChild(t):(t=document.createElement(n),i.appendChild(t)),t}function T(n){let i=B();if(!i)return!1;n.hass=i;let t=E(i);return Z(t)||Y(t).then(()=>{n.requestUpdate?.()}),!0}var ut={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7},N=null;function mt(n){return N||(N=n.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(i=>({features:i.features??ut.features,defaultWarningDays:i.general?.default_warning_days??7})).catch(()=>ut),N)}function ze(){let n=P(ht);return T(n)?(n.openCreate(),!0):!1}function qe(n,i){let t=P(ht);return T(t)?(t.openEdit(n,i),!0):!1}function Ve(n="",i){let t=P(_t);if(!T(t))return!1;let e=B();return e?((async()=>{let s=await mt(e),r=t;r.checklistsEnabled=s.features.checklists,r.scheduleTimeEnabled=s.features.schedule_time,r.completionActionsEnabled=s.features.completion_actions,r.defaultWarningDays=s.defaultWarningDays,r.openCreate(n,i)})(),!0):!1}function We(n,i){let t=P(_t);if(!T(t))return!1;let e=B();return e?((async()=>{try{let[s,r]=await Promise.all([e.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:n}),mt(e)]),l=(s.tasks||[]).find(u=>u.id===i);if(!l){console.warn(`openEditTaskDialog: task ${i} not found in entry ${n}`);return}let d=t;d.checklistsEnabled=r.features.checklists,d.scheduleTimeEnabled=r.features.schedule_time,d.completionActionsEnabled=r.features.completion_actions,d.defaultWarningDays=r.defaultWarningDays,await d.openEdit(n,l)}catch(s){console.warn("openEditTaskDialog: failed to load task/features",s)}})(),!0):!1}function Ne(n){let i=P(Ht);return T(i)?(i.openEdit(n),!0):!1}function Be(n){let i=P(St);return T(i)?(i.entryId=n.entry_id,i.taskId=n.task_id,i.taskName=n.task_name,i.checklist=n.checklist??[],i.adaptiveEnabled=!!n.adaptive_enabled,i.requiredFields=n.required_completion_fields??[],i.taskType=n.task_type??"",i.readingUnit=n.reading_unit??"",i.parts=n.parts??[],i.consumesParts=n.consumes_parts??[],i.phaseLabel=n.phase_label??"",i.lang=B()?.language||"en",i.open(),!0):!1}function Ke(n){let i=P(jt);return T(i)?(i.openForTask(n.entry_id,n.task_id,n.object_name,n.task_name),!0):!1}function Ue(n,i){let t=P(Ot);return T(t)?(t.openFor(n,i),!0):!1}function Ge(n){let i=P(Rt);return T(i)?(i.openFor(n),!0):!1}export{pt as a,Kt as b,Mt as c,Dt as d,U as e,W as f,R as g,Zt as h,Yt as i,at as j,rt as k,ot as l,dt as m,ct as n,ze as o,qe as p,Ve as q,We as r,Ne as s,Be as t,Ke as u,Ue as v,Ge as w};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
var r=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 e=Date.now();if(!s&&this.usersCache&&e-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=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,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}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.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{r as a};
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{p as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";function u(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let n=!!e.entry_id&&e.entry_id!==r,o=n?e.entry_id:r,a=s.find(p=>p.entry_id===o),t=(a?.parts||[]).find(p=>p.id===e.part_id)||null,d=n&&a?.object?.name||"",i=t?.name||_("shared_part_unknown",c);return{part:t,foreign:n,ownerName:d,label:d?`${i} (${d})`:i}}function P(e,r,s,c){let{part:n,label:o}=l(e,r,s,c),a=n&&n.stock!==null&&n.stock!==void 0?` (${n.stock}${n.unit?" "+n.unit:""})`:"",t=n?.storage_location?` \u2014 ${n.storage_location}`:"";return`${e.quantity}\xD7 ${o}${a}${t}`}function g(e,r,s,c){let o=(s.find(t=>t.entry_id===r)?.parts||[]).map(t=>({...t})),a=new Set(o.map(t=>u({part_id:t.id})));for(let t of e?.consumes_parts||[]){if(!t.entry_id||t.entry_id===r)continue;let d=u(t);if(a.has(d))continue;a.add(d);let{part:i,ownerName:p}=l(t,r,s,c);o.push({id:t.part_id,name:i?.name||_("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:t.entry_id,owner_name:p})}return o}var f=["notes","cost","duration","photo","user"],m={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{u as a,P as b,g as c,f as d,m as e};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{p as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var s={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"};function c(e,o){let r=s[e];if(!r)return e;let t=a(r,o);return t&&t!==r?t:e}function d(e){let r=e.match(/data\['([^']+)'\]/)?.[1],t;return(t=e.match(/length of value must be at most (\d+)/))?{field:r,rule:"too_long",param:t[1]}:(t=e.match(/length of value must be at least (\d+)/))?{field:r,rule:"too_short",param:t[1]}:(t=e.match(/value must be at most (\S+)/))?{field:r,rule:"value_too_high",param:t[1]}:(t=e.match(/value must be at least (\S+)/))?{field:r,rule:"value_too_low",param:t[1]}:/required key not provided/.test(e)?{field:r,rule:"required"}:(t=e.match(/expected (\w+)/))?{field:r,rule:"wrong_type",param:t[1]}:/value must be one of/.test(e)?{field:r,rule:"invalid_choice"}:/not a valid value/.test(e)?{field:r,rule:"invalid_value"}:{field:r,rule:"unknown"}}function g(e,o,r){if(r=r??a("action_error",o),typeof e=="string")return e;if(typeof e!="object"||e===null)return r;let t=e,_=t.message||t.error?.message||"";if(!_)return r;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||r}}export{g as a};
@@ -0,0 +1,143 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import{a as r,b as _,c as l,f as o,g as p,k as d,l as s,p as i,r as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var e=class extends p{constructor(){super(...arguments);this.objects=[];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._entryId=null}get _lang(){return h(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._error="",this._open=!0}openEdit(a,n){this._entryId=a,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,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}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",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._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(a){this._error=u(a,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(a=>a.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let a=this._lang,n=this._entryId?i("edit_object",a):i("new_object",a);return l`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${n}</div>
<div class="content">
${this._error?l`<div class="error">${this._error}</div>`:o}
<ms-textfield
label="${i("name",a)}"
required
.value=${this._name}
@input=${t=>this._name=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("manufacturer_optional",a)}"
.value=${this._manufacturer}
@input=${t=>this._manufacturer=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("model_optional",a)}"
.value=${this._model}
@input=${t=>this._model=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("serial_number_optional",a)}"
.value=${this._serialNumber}
@input=${t=>this._serialNumber=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("documentation_url_optional",a)}"
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",a)}"
.value=${this._areaId}
@value-changed=${t=>this._areaId=t.detail.value||""}
></ha-area-picker>
<ms-textfield
label="${i("installation_date_optional",a)}"
type="date"
.value=${this._installationDate}
@input=${t=>this._installationDate=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("warranty_expiry_optional",a)}"
type="date"
.value=${this._warrantyExpiry}
@input=${t=>this._warrantyExpiry=t.target.value}
></ms-textfield>
<ha-form
.hass=${this.hass}
.data=${{device:this._haDeviceId||void 0}}
.schema=${[{name:"device",selector:{device:{}}}]}
.computeLabel=${()=>i("link_device_optional",a)}
@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",a)}</span>
<select
class="parent-select"
.value=${this._parentEntryId}
@change=${t=>this._parentEntryId=t.target.value}
>
<option value="" ?selected=${!this._parentEntryId}>
${i("parent_none",a)}
</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",a)}</span>
<textarea
rows="3"
.value=${this._notes}
@input=${t=>this._notes=t.target.value}
></textarea>
</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>
`}};e.styles=_`
.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);
}
.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;
}
`,r([d({attribute:!1})],e.prototype,"hass",2),r([d({attribute:!1})],e.prototype,"objects",2),r([s()],e.prototype,"_open",2),r([s()],e.prototype,"_loading",2),r([s()],e.prototype,"_error",2),r([s()],e.prototype,"_name",2),r([s()],e.prototype,"_manufacturer",2),r([s()],e.prototype,"_model",2),r([s()],e.prototype,"_serialNumber",2),r([s()],e.prototype,"_areaId",2),r([s()],e.prototype,"_installationDate",2),r([s()],e.prototype,"_warrantyExpiry",2),r([s()],e.prototype,"_documentationUrl",2),r([s()],e.prototype,"_notes",2),r([s()],e.prototype,"_haDeviceId",2),r([s()],e.prototype,"_parentEntryId",2),r([s()],e.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",e);export{e as a};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7PCGJNSR.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-UBKG7VLY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";export{a as MaintenanceCompleteDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{o as a,p as b,q as c,r as d,s as e,t as f,u as g,v as h,w as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-H5N2BEP6.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZQ7M5ZII.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-KFTLMBBT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EUQJA76V.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7PCGJNSR.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-UBKG7VLY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FWFB6OZJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-GSH25Q5Z.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IAYX7ZWS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";export{f as openCompleteDialog,a as openCreateObjectDialog,c as openCreateTaskDialog,b as openEditObjectDialog,d as openEditTaskDialog,e as openHistoryEditDialog,i as openObjectQuickActions,g as openQrDialog,h as openTaskQuickActions};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZQ7M5ZII.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EUQJA76V.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";export{a as MaintenanceObjectDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FWFB6OZJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-GSH25Q5Z.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";export{a as MaintenanceQrDialog};
@@ -0,0 +1,146 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import{a as l,b as m,c as i,f as h,g as v,k as u,l as d,p,r as f,t as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";var a=class extends v{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._objects=[];this._localeReady=!1;this._toggle=t=>{let e=new Set(this._selected);e.has(t)?e.delete(t):e.add(t),this._selected=e};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/adopt",selections:[...this._selected].map(e=>{let r={device_id:e},c=this._targets.get(e);c&&(r.entry_id=c);let s=this._setups.find(n=>n.device_id===e);for(let n of s?.tasks??[]){let o=this._baselines.get(`${e} ${n.task_name}`),_=o?parseFloat(o):NaN;!isNaN(_)&&_>=0&&((r.baselines??={})[n.task_name]=_)}return r})});this.dispatchEvent(new CustomEvent("integration-setups-adopted",{bubbles:!0,composed:!0,detail:t})),this._open=!1}catch(t){this._error=g(t,this._lang)}finally{this._adopting=!1}}}}get _lang(){return f(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,x(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._setups=[],this._selected=new Set;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/discover"});this._setups=t.setups||[],this._selected=new Set(this._setups.map(e=>e.device_id)),this._baselines=new Map,this._targets=new Map;try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._objects=(e.objects||[]).map(r=>({entry_id:r.entry_id,name:r.object?.name||r.entry_id})).sort((r,c)=>r.name.localeCompare(c.name))}catch{this._objects=[]}}catch(t){this._error=g(t,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let t=this._lang;return i`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${e=>e.stopPropagation()}>
<div class="title">${p("setups_title",t)}</div>
<div class="hint">${p("setups_hint",t)}</div>
${this._error?i`<div class="error">${this._error}</div>`:h}
${this._loading?i`<div class="loading">…</div>`:this._setups.length===0?i`<div class="empty">${p("setups_none",t)}</div>`:i`
<div class="list">
${this._setups.map(e=>{let r=this._selected.has(e.device_id),c=[e.integration_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
<label class="row">
<input
type="checkbox"
.checked=${r}
@change=${()=>this._toggle(e.device_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${e.device_name}</span>
</div>
<div class="row-sub">${c}</div>
<div class="row-target" @click=${s=>s.preventDefault()}>
→
${r&&this._objects.length>0?i`
<select
class="target-select"
@change=${s=>{let n=new Map(this._targets),o=s.target.value;o?n.set(e.device_id,o):n.delete(e.device_id),this._targets=n}}
>
<option value="" ?selected=${!this._targets.get(e.device_id)}>
${e.suggested_entry_id?e.suggested_object_name:p("setups_target_new",t).replace("{name}",e.suggested_object_name)}
</option>
${this._objects.filter(s=>s.entry_id!==e.suggested_entry_id).map(s=>i`<option
value=${s.entry_id}
?selected=${this._targets.get(e.device_id)===s.entry_id}
>
${s.name}
</option>`)}
</select>
`:i`${e.suggested_object_name}${e.suggested_entry_id?h:i` <span class="new-tag">${p("adopt_problem_new_object",t)}</span>`}`}
</div>
<div class="row-tasks">
${e.tasks.map(s=>i`<span class="chip" title=${s.entity_ids.join(", ")}>
<ha-icon icon="mdi:link-variant"></ha-icon>${s.task_name_localized||s.task_name}
</span>`)}
</div>
${r?e.tasks.filter(s=>s.direction==="usage_delta").map(s=>{let n=`${e.device_id} ${s.task_name}`;return i`
<div class="baseline-field" @click=${o=>o.preventDefault()}>
<span class="baseline-label"
>${s.task_name_localized||s.task_name} —
${p("setups_baseline_hint",t)}</span
>
<input
type="number"
step="any"
min="0"
.value=${this._baselines.get(n)??""}
@click=${o=>o.preventDefault()}
@input=${o=>{let _=new Map(this._baselines);_.set(n,o.target.value),this._baselines=_}}
/>
</div>
`}):h}
</div>
</label>
`})}
</div>
`}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${p("cancel",t)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${p("setups_adopt",t)}
</ha-button>
</div>
</div>
</div>
`}};a.styles=m`
.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);
}
.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: 50vh; }
.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: 3px; 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; }
.new-tag { font-style: italic; }
.row-tasks { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.chip {
display: inline-flex; align-items: center; gap: 4px;
font-size: 11px; padding: 2px 8px; border-radius: 10px;
background: var(--secondary-background-color, rgba(0, 0, 0, 0.06));
color: var(--primary-text-color); white-space: nowrap;
}
.chip ha-icon { --mdc-icon-size: 12px; color: var(--primary-color); }
.target-select {
font-size: 12px; padding: 2px 4px; max-width: 100%;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.baseline-field {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
margin-top: 4px; 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; }
`,l([u({attribute:!1})],a.prototype,"hass",2),l([d()],a.prototype,"_open",2),l([d()],a.prototype,"_loading",2),l([d()],a.prototype,"_adopting",2),l([d()],a.prototype,"_error",2),l([d()],a.prototype,"_setups",2),l([d()],a.prototype,"_selected",2),l([d()],a.prototype,"_baselines",2),l([d()],a.prototype,"_targets",2),l([d()],a.prototype,"_objects",2);customElements.get("maintenance-suggested-setups-dialog")||customElements.define("maintenance-suggested-setups-dialog",a);export{a as MaintenanceSuggestedSetupsDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KFTLMBBT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EUQJA76V.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-UBKG7VLY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IAYX7ZWS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Z6JBBYEY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O6LFYSTY.js";export{a as MaintenanceTaskDialog};
@@ -0,0 +1,125 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as m,b as f}from"./chunk-HZFXKH3I.js";import{a as u,b as l,d as h,e as b,f as y,g as o,h as g,i as a,j as _,l as v,u as p}from"./chunk-CYP6LL7W.js";import{a as n}from"./chunk-UTYWUNJT.js";var k=80,s=class extends b{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 _(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),v(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"});this._status=t,this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1}catch(t){this._error=p(t,this._lang)}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=this._localMonthly.trim()===""?0:parseFloat(this._localMonthly),r=this._localYearly.trim()===""?0:parseFloat(this._localYearly),i={};!isNaN(t)&&t>=0&&(i.budget_monthly=t),!isNaN(r)&&r>=0&&(i.budget_yearly=r),await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:i}),await this._load()}catch(t){this._error=p(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,r=this._status;if(!r)return l`<ha-card><div class="loading">${a("loading",t)||"Loading\u2026"}</div></ha-card>`;let i=r.currency_symbol||g,x=r.alert_threshold_pct??k,$=[{label:a("budget_monthly",t)||"Monthly",spent:r.monthly_spent||0,budget:r.monthly_budget||0},{label:a("budget_yearly",t)||"Yearly",spent:r.yearly_spent||0,budget:r.yearly_budget||0}];return l`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">💰</span>
<span>${this._config.title||a("settings_budget",t)||"Budget"}</span>
</div>
<span class="currency">${i}</span>
</div>
${this._error?l`<div class="error">${this._error}</div>`:h}
${$.map(e=>{if(!(e.budget>0))return l`
<div class="track spent-only">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ok">${e.spent.toFixed(0)} ${i}</span>
</div>
</div>
`;let d=Math.min(100,Math.max(0,e.spent/e.budget*100)),c=d>=100?"danger":d>=x?"warning":"ok";return l`
<div class="track">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ${c}">
${e.spent.toFixed(0)} / ${e.budget.toFixed(0)} ${i}
</span>
</div>
<div class="bar"><div class="bar-fill ${c}" style="width:${d}%"></div></div>
</div>
`})}
${this._isAdmin?l`
<div class="inputs-row">
<div class="input-field">
<label>${a("budget_monthly_set",t)||"Set monthly"}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localMonthly}
?disabled=${this._busy}
@input=${e=>{this._localMonthly=e.target.value,this._dirty=!0}} />
<span class="input-suffix">${i}</span>
</div>
</div>
<div class="input-field">
<label>${a("budget_yearly_set",t)||"Set yearly"}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localYearly}
?disabled=${this._busy}
@input=${e=>{this._localYearly=e.target.value,this._dirty=!0}} />
<span class="input-suffix">${i}</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?a("save",t)||"Save":a("saved",t)||"Saved"}
</button>
<button class="btn link" @click=${this._onDeepLink}>
${a("budget_advanced",t)||"Currency, alerts\u2026"}
</button>
</div>
`:l`
<button class="btn link" @click=${this._onDeepLink}>
${a("budget_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};s.styles=[f,u`
.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); }
.inputs-row {
display: grid; grid-template-columns: 1fr 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; 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([y({attribute:!1})],s.prototype,"hass",2),n([o()],s.prototype,"_config",2),n([o()],s.prototype,"_status",2),n([o()],s.prototype,"_busy",2),n([o()],s.prototype,"_error",2),n([o()],s.prototype,"_localMonthly",2),n([o()],s.prototype,"_localYearly",2),n([o()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);m({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Inline monthly + yearly budget editor",preview:!1});export{s as MaintenanceBudgetSectionCard};
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,60 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as t}from"./chunk-CYP6LL7W.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.65.0 */
var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var t=(a,r,c,o)=>{for(var e=o>1?void 0:o?l(r,c):r,i=a.length-1,d;i>=0;i--)(d=a[i])&&(e=(o?d(r,c,e):d(e))||e);return o&&e&&s(r,c,e),e};var m={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)"},v={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"};export{t as a,m as b,v as c};
@@ -0,0 +1,137 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as v,b}from"./chunk-HZFXKH3I.js";import{a as u,b as s,d as c,e as h,f as g,g as o,i,j as _,l as m,u as p}from"./chunk-CYP6LL7W.js";import{a}from"./chunk-UTYWUNJT.js";var e=class extends h{constructor(){super(...arguments);this._config={type:""};this._groups={};this._loaded=!1;this._busy=!1;this._error="";this._newName="";this._editingId=null;this._editingName="";this._hasInitiallyLoaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return _(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._hasInitiallyLoaded&&(this._hasInitiallyLoaded=!0,this._load(),m(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"});this._groups=t.groups||{},this._loaded=!0}catch(t){this._error=p(t,this._lang)}}async _addGroup(){if(!this._isAdmin)return;let t=this._newName.trim();if(t){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/create",name:t}),this._newName="",await this._load()}catch(r){this._error=p(r,this._lang)}finally{this._busy=!1}}}_startEdit(t){this._editingId=t,this._editingName=this._groups[t]?.name||""}async _saveEdit(){if(!this._isAdmin||!this._editingId)return;let t=this._editingName.trim();if(t){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/update",group_id:this._editingId,name:t}),this._editingId=null,this._editingName="",await this._load()}catch(r){this._error=p(r,this._lang)}finally{this._busy=!1}}}async _deleteGroup(t,r){if(!this._isAdmin)return;let n=(i("group_delete_confirm",this._lang)||'Delete group "{name}"?').replace("{name}",r);if(window.confirm(n)){this._busy=!0;try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/delete",group_id:t}),await this._load()}catch(d){this._error=p(d,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_groups"),window.dispatchEvent(new CustomEvent("location-changed"))}_onKeyDown(t,r){t.key==="Enter"?(t.preventDefault(),r()):t.key==="Escape"&&(t.preventDefault(),this._editingId=null,this._editingName="")}render(){let t=this._lang;if(!this._loaded)return s`<ha-card><div class="loading">${i("loading",t)||"Loading\u2026"}</div></ha-card>`;let r=Object.keys(this._groups);return s`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏷️</span>
<span>${this._config.title||i("groups",t)||"Groups"}</span>
<span class="count">${r.length}</span>
</div>
</div>
${this._error?s`<div class="error">${this._error}</div>`:c}
${r.length===0?s`<div class="empty">${i("groups_empty",t)||"No groups yet."}</div>`:s`
<div class="group-list">
${r.map(n=>{let d=this._groups[n],y=d.task_refs?.length??0,f=this._editingId===n;return s`
<div class="group-row">
${f?s`
<input class="edit-input" type="text"
.value=${this._editingName}
?disabled=${this._busy}
@input=${l=>{this._editingName=l.target.value}}
@keydown=${l=>this._onKeyDown(l,this._saveEdit.bind(this))} />
<button class="btn small primary"
@click=${this._saveEdit}
?disabled=${this._busy||!this._editingName.trim()}>
${i("save",t)||"Save"}
</button>
<button class="btn small"
@click=${()=>{this._editingId=null}}>
${i("cancel",t)||"Cancel"}
</button>
`:s`
<span class="group-name">${d.name||"Unnamed"}</span>
<span class="task-count">${y}</span>
${this._isAdmin?s`
<button class="icon-btn"
title="${i("edit",t)||"Edit"}"
@click=${()=>this._startEdit(n)}
?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
</button>
<button class="icon-btn danger"
title="${i("delete",t)||"Delete"}"
@click=${()=>this._deleteGroup(n,d.name||"Unnamed")}
?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
</button>
`:c}
`}
</div>
`})}
</div>
`}
${this._isAdmin?s`
<div class="add-row">
<input type="text"
placeholder="${i("group_new_placeholder",t)||"Add group\u2026"}"
.value=${this._newName}
?disabled=${this._busy}
@input=${n=>{this._newName=n.target.value}}
@keydown=${n=>this._onKeyDown(n,this._addGroup.bind(this))} />
<button class="btn primary"
@click=${this._addGroup}
?disabled=${this._busy||!this._newName.trim()}>
<ha-icon icon="mdi:plus"></ha-icon>
${i("add",t)||"Add"}
</button>
</div>
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_manage_tasks",t)||"Manage task assignments\u2026"}
</button>
`:s`
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};e.styles=[b,u`
.count {
font-size: 12px; color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 8px; border-radius: 999px;
}
.empty {
padding: 16px; text-align: center;
color: var(--secondary-text-color); font-style: italic;
}
.group-list { display: flex; flex-direction: column; gap: 4px; }
.group-row {
display: flex; align-items: center; gap: 8px;
padding: 6px 8px; border-radius: 6px;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
}
.group-name { flex: 1; font-size: 14px; }
.task-count {
font-size: 11px; color: var(--secondary-text-color);
background: var(--card-background-color, rgba(0,0,0,0.2));
padding: 1px 8px; border-radius: 999px;
font-weight: 500;
}
.edit-input {
flex: 1; padding: 4px 8px; font-size: 14px;
background: var(--card-background-color, #1c1c1c);
color: var(--primary-text-color);
border: 1px solid var(--primary-color); border-radius: 4px;
font-family: inherit;
}
.icon-btn {
background: transparent; border: none; cursor: pointer;
color: var(--secondary-text-color); padding: 4px;
border-radius: 4px;
}
.icon-btn:hover {
background: var(--state-icon-color, rgba(255,255,255,0.06));
color: var(--primary-text-color);
}
.icon-btn.danger:hover { color: var(--error-color); }
.icon-btn ha-icon { --mdc-icon-size: 18px; }
.add-row {
display: flex; gap: 6px;
padding-top: 8px; border-top: 1px solid var(--divider-color);
}
.add-row input {
flex: 1; padding: 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;
}
/* Card-specific overrides on the shared .btn */
.btn.small { padding: 4px 8px; font-size: 12px; }
.btn ha-icon { --mdc-icon-size: 16px; }
`],a([g({attribute:!1})],e.prototype,"hass",2),a([o()],e.prototype,"_config",2),a([o()],e.prototype,"_groups",2),a([o()],e.prototype,"_loaded",2),a([o()],e.prototype,"_busy",2),a([o()],e.prototype,"_error",2),a([o()],e.prototype,"_newName",2),a([o()],e.prototype,"_editingId",2),a([o()],e.prototype,"_editingName",2);customElements.get("maintenance-groups-section-card")||customElements.define("maintenance-groups-section-card",e);v({type:"maintenance-groups-section-card",name:"Maintenance Supporter \u2014 Groups",description:"Inline group CRUD",preview:!1});export{e as MaintenanceGroupsSectionCard};
@@ -0,0 +1,122 @@
/*! maintenance_supporter frontend 2.65.0 */
import{a as m,b as g}from"./chunk-HZFXKH3I.js";import{a as h,b as n,d as c,e as _,f as v,g as r,i as e,j as f,l as b,u as l}from"./chunk-CYP6LL7W.js";import{a as i}from"./chunk-UTYWUNJT.js";var a=class extends _{constructor(){super(...arguments);this._config={type:""};this._state=null;this._busy=!1;this._error="";this._localStart="";this._localEnd="";this._localBuffer=7;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),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),b(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/state"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||"",this._localBuffer=t.buffer_days??7,this._dirty=!1}catch(t){this._error=l(t,this._lang)}}async _toggleEnabled(t){this._busy=!0,this._error="";try{let s=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",enabled:t});this._state=s}catch(s){this._error=l(s,this._lang)}finally{this._busy=!1}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",start:this._localStart||null,end:this._localEnd||null,buffer_days:this._localBuffer});this._state=t,this._dirty=!1}catch(t){this._error=l(t,this._lang)}finally{this._busy=!1}}}async _endNow(){if(this._isAdmin&&window.confirm(e("vacation_end_now_confirm",this._lang)||"End vacation immediately?")){this._busy=!0;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/end_now"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||""}catch(t){this._error=l(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_vacation"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,s=this._state;if(!s)return n`<ha-card><div class="loading">${e("loading",t)||"Loading\u2026"}</div></ha-card>`;let p=s.is_active===!0,d=s.enabled===!0,u=s.exempt_task_ids?.length??0,y=p?e("vacation_status_active",t)||"Active now":d?e("vacation_status_scheduled",t)||"Scheduled":e("vacation_status_inactive",t)||"Inactive",$=p?"active":d?"scheduled":"inactive";return n`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏖️</span>
<span>${this._config.title||e("vacation_mode",t)||"Vacation mode"}</span>
</div>
<span class="status-pill ${$}">${y}</span>
</div>
${this._error?n`<div class="error">${this._error}</div>`:c}
${this._isAdmin?n`
<div class="row toggle-row">
<label>${e("enable",t)||"Enable"}</label>
<ha-switch
.checked=${d}
.disabled=${this._busy}
@change=${o=>this._toggleEnabled(o.target.checked)}
></ha-switch>
</div>
<div class="dates-row">
<div class="date-field">
<label>${e("vacation_start",t)||"Start"}</label>
<input type="date" .value=${this._localStart}
?disabled=${this._busy}
@input=${o=>{this._localStart=o.target.value,this._dirty=!0}} />
</div>
<div class="date-field">
<label>${e("vacation_end",t)||"End"}</label>
<input type="date" .value=${this._localEnd}
?disabled=${this._busy}
@input=${o=>{this._localEnd=o.target.value,this._dirty=!0}} />
</div>
<div class="date-field buffer">
<label>${e("vacation_buffer",t)||"Buffer days"}</label>
<input type="number" min="0" max="14"
.value=${String(this._localBuffer)}
?disabled=${this._busy}
@input=${o=>{this._localBuffer=parseInt(o.target.value,10)||0,this._dirty=!0}} />
</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?e("save",t)||"Save":e("saved",t)||"Saved"}
</button>
${p?n`<button class="btn"
@click=${this._endNow}
?disabled=${this._busy}>
${e("vacation_end_now",t)||"End now"}
</button>`:c}
${u>0?n`<button class="btn link"
@click=${this._onDeepLink}>
${u} ${e("vacation_exempt_count",t)||"exempt"}…
</button>`:n`<button class="btn link"
@click=${this._onDeepLink}>
${e("vacation_advanced",t)||"Advanced\u2026"}
</button>`}
</div>
`:n`
<div class="readonly">
${d&&s.start&&s.end?n`<div>${s.start} → ${s.end}</div>`:c}
<button class="btn link" @click=${this._onDeepLink}>
${e("vacation_open_panel",t)||"Open in panel"}
</button>
</div>
`}
</div>
</ha-card>
`}};a.styles=[g,h`
.status-pill {
font-size: 11px; font-weight: 600;
padding: 3px 8px; border-radius: 999px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.status-pill.active {
background: rgba(76, 175, 80, 0.15);
color: #4caf50;
}
.status-pill.scheduled {
background: rgba(255, 152, 0, 0.15);
color: #ff9800;
}
.status-pill.inactive {
background: rgba(158, 158, 158, 0.15);
color: var(--secondary-text-color);
}
.row.toggle-row {
display: flex; align-items: center; justify-content: space-between;
}
.row.toggle-row label {
font-size: 14px; color: var(--primary-text-color);
}
.dates-row {
display: grid; grid-template-columns: 1fr 1fr 100px; gap: 10px;
}
.date-field.buffer label { white-space: nowrap; }
.date-field { display: flex; flex-direction: column; gap: 4px; }
.date-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.date-field input {
padding: 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;
}
.date-field input:disabled { opacity: 0.5; cursor: not-allowed; }
.actions {
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.readonly { display: flex; flex-direction: column; gap: 8px; }
`],i([v({attribute:!1})],a.prototype,"hass",2),i([r()],a.prototype,"_config",2),i([r()],a.prototype,"_state",2),i([r()],a.prototype,"_busy",2),i([r()],a.prototype,"_error",2),i([r()],a.prototype,"_localStart",2),i([r()],a.prototype,"_localEnd",2),i([r()],a.prototype,"_localBuffer",2),i([r()],a.prototype,"_dirty",2);customElements.get("maintenance-vacation-section-card")||customElements.define("maintenance-vacation-section-card",a);m({type:"maintenance-vacation-section-card",name:"Maintenance Supporter \u2014 Vacation",description:"Inline vacation mode toggle + dates",preview:!1});export{a as MaintenanceVacationSectionCard};
@@ -1,5 +1,5 @@
/*! maintenance_supporter frontend 2.64.0 */
import{c as y}from"./chunks/chunk-3FHCH2PB.js";var L="maintenance-supporter",G=`ll-strategy-dashboard-${L}`,x="hui-maintenance-supporter-strategy-editor",V="NOT_RUNNING",H=[{status:"overdue",title:"Overdue",icon:y.overdue,path:"overdue"},{status:"triggered",title:"Triggered",icon:y.triggered,path:"triggered"},{status:"due_soon",title:"Due Soon",icon:y.due_soon,path:"due-soon"},{status:"ok",title:"OK",icon:y.ok,path:"ok"}],E=[{title:"Overdue",icon:"mdi:alert-circle",path:"overdue",filter:{filter_due_max_days:-1},matches:n=>n<=-1},{title:"Today",icon:"mdi:calendar-today",path:"today",filter:{filter_due_min_days:0,filter_due_max_days:0},matches:n=>n===0},{title:"This Week",icon:"mdi:calendar-week",path:"this-week",filter:{filter_due_min_days:1,filter_due_max_days:7},matches:n=>n>=1&&n<=7},{title:"This Month",icon:"mdi:calendar-month",path:"this-month",filter:{filter_due_min_days:8,filter_due_max_days:30},matches:n=>n>=8&&n<=30},{title:"Later",icon:"mdi:calendar-clock",path:"later",filter:{filter_due_min_days:31},matches:n=>n>=31}];function l(n){return{type:"grid",cards:[n]}}var N={condition:"view_columns",min:2};function _(n,i,e){let s=i[n];if(s)return`{{ states('${s}') }}`;let t=e[n];return t==null?"\u2014":String(t)}function B(n={},i={}){return{type:"markdown",text_only:!0,content:[`\u{1F534} **${_("overdue",n,i)}** overdue`,`\u26A1 **${_("triggered",n,i)}** triggered`,`\u{1F7E1} **${_("due_soon",n,i)}** due soon`,`\u{1F7E2} **${_("ok",n,i)}** ok`].join(" \xB7 ")}}function $(){return{title:"Maintenance",type:"panel",cards:[{type:"empty-state",icon:"mdi:wrench-clock",icon_color:"primary",content_only:!0,title:"No maintenance objects yet",content:"Open the Maintenance panel to add your first object \u2014 pool pump, HVAC filter, vehicle, anything that needs scheduled care.",buttons:[{icon:"mdi:wrench",text:"Open Maintenance panel",appearance:"filled",variant:"brand",tap_action:{action:"navigate",navigation_path:"/maintenance-supporter"}},{icon:"mdi:plus",text:"Add object",appearance:"outlined",variant:"brand",tap_action:{action:"fire-dom-event",ll_custom:{type:"maintenance-supporter:add-object"}}}]}]}}function U(n={},i={}){let e=B(n,i);return{title:"Overview",icon:"mdi:wrench-clock",path:"overview",type:"sections",header:{layout:"responsive",card:e},sidebar:{sections:[{type:"grid",cards:[{type:"heading",heading:"Status",heading_style:"title"},e]}],visibility:[N],content_label:"Tasks",sidebar_label:"Status"},sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:["overdue","triggered","due_soon"]})]}}function W(n,i){let e=new Map;for(let a of n){let r=a.object.area_id||null;e.has(r)||e.set(r,[]),e.get(r).push(a)}let s=Array.from(e.keys()).filter(a=>a!==null);s.sort((a,r)=>{let d=i[a]?.name||a,c=i[r]?.name||r;return d.localeCompare(c)});let t=[];for(let a of s){let r=e.get(a);if(r.length===0)continue;let d=i[a];t.push({title:d?.name||a,icon:d?.icon||"mdi:floor-plan",path:`area-${a}`,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:r.map(c=>c.object.name)})]})}let o=e.get(null);return o&&o.length>0&&t.push({title:"Unassigned",icon:"mdi:help-circle-outline",path:"unassigned",type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:o.map(a=>a.object.name)})]}),t}function Y(n){let i=new Set;for(let s of n)for(let t of s.tasks||[])t.status&&i.add(t.status);let e=[];for(let s of H)i.has(s.status)&&e.push({title:s.title,icon:s.icon,path:s.path,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:[s.status]})]});return e}function F(n,i,e){let s=new Map;for(let r of n){let d=r.object.area_id,c=d&&i[d]?.floor_id||null;s.has(c)||s.set(c,[]),s.get(c).push(r)}let t=Array.from(s.keys()).filter(r=>r!==null);t.sort((r,d)=>{let c=e[r],g=e[d],u=c?.level??0,p=g?.level??0;return u!==p?u-p:(c?.name||r).localeCompare(g?.name||d)});let o=[];for(let r of t){let d=s.get(r);if(d.length===0)continue;let c=e[r];o.push({title:c?.name||r,icon:c?.icon||"mdi:home-floor-1",path:`floor-${r}`,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:d.map(g=>g.object.name)})]})}let a=s.get(null);return a&&a.length>0&&o.push({title:"Other",icon:"mdi:help-circle-outline",path:"other",type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:a.map(r=>r.object.name)})]}),o}function K(n){return[{title:"Week",icon:"mdi:calendar-week",path:"cal-7",window_days:7},{title:"Fortnight",icon:"mdi:calendar-week-begin",path:"cal-14",window_days:14},{title:"Month",icon:"mdi:calendar-month",path:"cal-30",window_days:30},{title:"Year",icon:"mdi:calendar-clock",path:"cal-365",window_days:365}].map(e=>({title:e.title,icon:e.icon,path:e.path,type:"panel",cards:[{type:"custom:maintenance-supporter-calendar-card",window_days:e.window_days,show_window_chips:!1,show_user_filter:!0}]}))}function q(n){let i=new Set;for(let sLine truncated
/*! maintenance_supporter frontend 2.65.0 */
import{c as y}from"./chunks/chunk-UTYWUNJT.js";var L="maintenance-supporter",G=`ll-strategy-dashboard-${L}`,x="hui-maintenance-supporter-strategy-editor",V="NOT_RUNNING",H=[{status:"overdue",title:"Overdue",icon:y.overdue,path:"overdue"},{status:"triggered",title:"Triggered",icon:y.triggered,path:"triggered"},{status:"due_soon",title:"Due Soon",icon:y.due_soon,path:"due-soon"},{status:"ok",title:"OK",icon:y.ok,path:"ok"}],E=[{title:"Overdue",icon:"mdi:alert-circle",path:"overdue",filter:{filter_due_max_days:-1},matches:n=>n<=-1},{title:"Today",icon:"mdi:calendar-today",path:"today",filter:{filter_due_min_days:0,filter_due_max_days:0},matches:n=>n===0},{title:"This Week",icon:"mdi:calendar-week",path:"this-week",filter:{filter_due_min_days:1,filter_due_max_days:7},matches:n=>n>=1&&n<=7},{title:"This Month",icon:"mdi:calendar-month",path:"this-month",filter:{filter_due_min_days:8,filter_due_max_days:30},matches:n=>n>=8&&n<=30},{title:"Later",icon:"mdi:calendar-clock",path:"later",filter:{filter_due_min_days:31},matches:n=>n>=31}];function l(n){return{type:"grid",cards:[n]}}var N={condition:"view_columns",min:2};function _(n,i,e){let s=i[n];if(s)return`{{ states('${s}') }}`;let t=e[n];return t==null?"\u2014":String(t)}function B(n={},i={}){return{type:"markdown",text_only:!0,content:[`\u{1F534} **${_("overdue",n,i)}** overdue`,`\u26A1 **${_("triggered",n,i)}** triggered`,`\u{1F7E1} **${_("due_soon",n,i)}** due soon`,`\u{1F7E2} **${_("ok",n,i)}** ok`].join(" \xB7 ")}}function $(){return{title:"Maintenance",type:"panel",cards:[{type:"empty-state",icon:"mdi:wrench-clock",icon_color:"primary",content_only:!0,title:"No maintenance objects yet",content:"Open the Maintenance panel to add your first object \u2014 pool pump, HVAC filter, vehicle, anything that needs scheduled care.",buttons:[{icon:"mdi:wrench",text:"Open Maintenance panel",appearance:"filled",variant:"brand",tap_action:{action:"navigate",navigation_path:"/maintenance-supporter"}},{icon:"mdi:plus",text:"Add object",appearance:"outlined",variant:"brand",tap_action:{action:"fire-dom-event",ll_custom:{type:"maintenance-supporter:add-object"}}}]}]}}function U(n={},i={}){let e=B(n,i);return{title:"Overview",icon:"mdi:wrench-clock",path:"overview",type:"sections",header:{layout:"responsive",card:e},sidebar:{sections:[{type:"grid",cards:[{type:"heading",heading:"Status",heading_style:"title"},e]}],visibility:[N],content_label:"Tasks",sidebar_label:"Status"},sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:["overdue","triggered","due_soon"]})]}}function W(n,i){let e=new Map;for(let a of n){let r=a.object.area_id||null;e.has(r)||e.set(r,[]),e.get(r).push(a)}let s=Array.from(e.keys()).filter(a=>a!==null);s.sort((a,r)=>{let d=i[a]?.name||a,c=i[r]?.name||r;return d.localeCompare(c)});let t=[];for(let a of s){let r=e.get(a);if(r.length===0)continue;let d=i[a];t.push({title:d?.name||a,icon:d?.icon||"mdi:floor-plan",path:`area-${a}`,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:r.map(c=>c.object.name)})]})}let o=e.get(null);return o&&o.length>0&&t.push({title:"Unassigned",icon:"mdi:help-circle-outline",path:"unassigned",type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:o.map(a=>a.object.name)})]}),t}function Y(n){let i=new Set;for(let s of n)for(let t of s.tasks||[])t.status&&i.add(t.status);let e=[];for(let s of H)i.has(s.status)&&e.push({title:s.title,icon:s.icon,path:s.path,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:[s.status]})]});return e}function F(n,i,e){let s=new Map;for(let r of n){let d=r.object.area_id,c=d&&i[d]?.floor_id||null;s.has(c)||s.set(c,[]),s.get(c).push(r)}let t=Array.from(s.keys()).filter(r=>r!==null);t.sort((r,d)=>{let c=e[r],g=e[d],u=c?.level??0,p=g?.level??0;return u!==p?u-p:(c?.name||r).localeCompare(g?.name||d)});let o=[];for(let r of t){let d=s.get(r);if(d.length===0)continue;let c=e[r];o.push({title:c?.name||r,icon:c?.icon||"mdi:home-floor-1",path:`floor-${r}`,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:d.map(g=>g.object.name)})]})}let a=s.get(null);return a&&a.length>0&&o.push({title:"Other",icon:"mdi:help-circle-outline",path:"other",type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:a.map(r=>r.object.name)})]}),o}function K(n){return[{title:"Week",icon:"mdi:calendar-week",path:"cal-7",window_days:7},{title:"Fortnight",icon:"mdi:calendar-week-begin",path:"cal-14",window_days:14},{title:"Month",icon:"mdi:calendar-month",path:"cal-30",window_days:30},{title:"Year",icon:"mdi:calendar-clock",path:"cal-365",window_days:365}].map(e=>({title:e.title,icon:e.icon,path:e.path,type:"panel",cards:[{type:"custom:maintenance-supporter-calendar-card",window_days:e.window_days,show_window_chips:!1,show_user_filter:!0}]}))}function q(n){let i=new Set;for(let sLine truncated
<style>
:host, .editor { display: block; padding: 16px 0; }
label { display: block; font-weight: 500; margin-bottom: 8px; }
@@ -22,4 +22,4 @@ import{c as y}from"./chunks/chunk-3FHCH2PB.js";var L="maintenance-supporter",G=`
The "Overview" view is always first. Empty groups are skipped.
</div>
</div>
`;let t=this.querySelector("#group-by");t?.addEventListener("change",()=>{let o={...this._config,group_by:t.value};this._config=o,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:o},bubbles:!0,composed:!0}))})}},I="maintenance-supporter-section",Q=`ll-strategy-section-${I}`,v=class extends HTMLElement{static async generate(i,e){let s={type:"custom:maintenance-supporter-card",show_header:!1};i?.filter_status?.length&&(s.filter_status=i.filter_status),i?.max_items&&(s.max_items=i.max_items),i?.filter_due_min_days!==void 0&&(s.filter_due_min_days=i.filter_due_min_days),i?.filter_due_max_days!==void 0&&(s.filter_due_max_days=i.filter_due_max_days);let t=i?.filter_objects;if(i?.area_id&&!t)try{t=((await e.connection.sendMessagePromise({type:"maintenance_supporter/objects"})).objects||[]).filter(r=>r.object.area_id===i.area_id).map(r=>r.object.name)}catch{}t&&t.length>0&&(s.filter_objects=t);let o=[];return i?.title&&o.push({type:"heading",heading:i.title,heading_style:"title"}),o.push(s),{type:"grid",cards:o}}},D="2.3.6",X="[maintenance-supporter]";function M(n,i){try{return customElements.get(n)?"skipped":(customElements.define(n,i),"ok")}catch(e){return`ERROR: ${String(e)}`}}var P={editor:M(x,w),section:M(Q,v)};console.log(`${X} strategy bundle v${D} loaded \u2014 registrations:`,P,"\u2014 run maintenanceSupporterDiagnose() in console for full state");window.maintenanceSupporterDiagnose=async()=>{let n=G,i=[];try{let e=document.querySelector("home-assistant");e?.hass&&(i=(await e.hass.connection.sendMessagePromise({type:"lovelace/resources"})).resources||[])}catch(e){i=[{url:`<error: ${String(e)}>`}]}return{version:D,el_registered:!!customElements.get(n),el_class_excerpt:customElements.get(n)?.toString().slice(0,100)||null,customStrategies_dashboard:window.customStrategies?.filter(e=>e.strategyType==="dashboard").map(e=>e.type)||[],scriptTagsInPage:Array.from(document.querySelectorAll("script[type=module]")).map(e=>e.src).filter(e=>/maint|strateg/i.test(e)),lovelaceResources_matching:i.filter(e=>/maint|strateg/i.test(e.url||"")).map(e=>e.url),registrations:P}};var k=class extends HTMLElement{static async generate(i,e){return await import("./chunks/vacation-section-card-ZL6KZC6R.js"),{type:"grid",cards:[{type:"custom:maintenance-vacation-section-card",title:i?.title}]}}},C=class extends HTMLElement{static async generate(i,e){return await import("./chunks/budget-section-card-G6NY7FKK.js"),{type:"grid",cards:[{type:"custom:maintenance-budget-section-card",title:i?.title}]}}},S=class extends HTMLElement{static async generate(i,e){return await import("./chunks/groups-section-card-KRPPNUTF.js"),{type:"grid",cards:[{type:"custom:maintenance-groups-section-card",title:i?.title}]}}},j="ll-strategy-section-maintenance-supporter-vacation",R="ll-strategy-section-maintenance-supporter-budget",A="ll-strategy-section-maintenance-supporter-groups";customElements.get(j)||customElements.define(j,k);customElements.get(R)||customElements.define(R,C);customElements.get(A)||customElements.define(A,S);var h=null;function J(){return h||(h=import("./chunks/dialog-mount-VABI5TW6.js").catch(n=>{throw h=null,n})),h}var O="_msSupporterLlCustomBound";function m(n){history.pushState(null,"",n),window.dispatchEvent(new CustomEvent("location-changed"))}function Z(){let n=window;n[O]||(n[O]=!0,document.addEventListener("ll-custom",i=>{let e=i.detail;if(!e||typeof e!="object")return;let s=e.ll_custom,t=s&&typeof s=="object"?s:e;if(!t||typeof t.type!="string"||!t.type.startsWith("maintenance-supporter:"))return;let o=t.type.slice(22);if(o==="deep-link"&&typeof t.path=="string"){m(t.path);return}(async()=>{let a;try{a=await J()}catch{m("/maintenance-supporter");return}if(o==="add-object"){if(a.openCreateObjectDialog())return;m("/maintenance-supporter?ms_action=add_object");return}if(o==="open-task"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"){if(a.openTaskQuickActions(t.entry_id,t.task_id)||a.openEditTaskDialog(t.entry_id,t.task_id))return;m(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}&task_id=${encodeURIComponent(t.task_id)}`);return}if(o==="open-object"&&typeof t.entry_id=="string"){if(a.openObjectQuickActions(t.entry_id))return;m(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}`);return}if(o==="edit-history"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"&&typeof t.original_timestamp=="string"){let d=document.querySelector("home-assistant")?.hass;if(!d)return;try{let u=(await d.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:t.entry_id})).tasks?.find(p=>p.id===t.task_id)?.history?.find(p=>p.timestamp===t.original_timestamp);if(!u)return;a.openHistoryEditDialog({entry_id:t.entry_id,task_id:t.task_id,original_timestamp:t.original_timestamp,type:u.type||"completed",timestamp:u.timestamp||t.original_timestamp,notes:u.notes??null,cost:u.cost??null,duration:u.duration??null,completed_by:u.completed_by??null,used_parts:u.used_parts??null} Line truncated
`;let t=this.querySelector("#group-by");t?.addEventListener("change",()=>{let o={...this._config,group_by:t.value};this._config=o,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:o},bubbles:!0,composed:!0}))})}},I="maintenance-supporter-section",Q=`ll-strategy-section-${I}`,v=class extends HTMLElement{static async generate(i,e){let s={type:"custom:maintenance-supporter-card",show_header:!1};i?.filter_status?.length&&(s.filter_status=i.filter_status),i?.max_items&&(s.max_items=i.max_items),i?.filter_due_min_days!==void 0&&(s.filter_due_min_days=i.filter_due_min_days),i?.filter_due_max_days!==void 0&&(s.filter_due_max_days=i.filter_due_max_days);let t=i?.filter_objects;if(i?.area_id&&!t)try{t=((await e.connection.sendMessagePromise({type:"maintenance_supporter/objects"})).objects||[]).filter(r=>r.object.area_id===i.area_id).map(r=>r.object.name)}catch{}t&&t.length>0&&(s.filter_objects=t);let o=[];return i?.title&&o.push({type:"heading",heading:i.title,heading_style:"title"}),o.push(s),{type:"grid",cards:o}}},D="2.3.6",X="[maintenance-supporter]";function M(n,i){try{return customElements.get(n)?"skipped":(customElements.define(n,i),"ok")}catch(e){return`ERROR: ${String(e)}`}}var P={editor:M(x,w),section:M(Q,v)};console.log(`${X} strategy bundle v${D} loaded \u2014 registrations:`,P,"\u2014 run maintenanceSupporterDiagnose() in console for full state");window.maintenanceSupporterDiagnose=async()=>{let n=G,i=[];try{let e=document.querySelector("home-assistant");e?.hass&&(i=(await e.hass.connection.sendMessagePromise({type:"lovelace/resources"})).resources||[])}catch(e){i=[{url:`<error: ${String(e)}>`}]}return{version:D,el_registered:!!customElements.get(n),el_class_excerpt:customElements.get(n)?.toString().slice(0,100)||null,customStrategies_dashboard:window.customStrategies?.filter(e=>e.strategyType==="dashboard").map(e=>e.type)||[],scriptTagsInPage:Array.from(document.querySelectorAll("script[type=module]")).map(e=>e.src).filter(e=>/maint|strateg/i.test(e)),lovelaceResources_matching:i.filter(e=>/maint|strateg/i.test(e.url||"")).map(e=>e.url),registrations:P}};var k=class extends HTMLElement{static async generate(i,e){return await import("./chunks/vacation-section-card-BCTJNS2F.js"),{type:"grid",cards:[{type:"custom:maintenance-vacation-section-card",title:i?.title}]}}},C=class extends HTMLElement{static async generate(i,e){return await import("./chunks/budget-section-card-QP3TTMOD.js"),{type:"grid",cards:[{type:"custom:maintenance-budget-section-card",title:i?.title}]}}},S=class extends HTMLElement{static async generate(i,e){return await import("./chunks/groups-section-card-PUPCVQTP.js"),{type:"grid",cards:[{type:"custom:maintenance-groups-section-card",title:i?.title}]}}},j="ll-strategy-section-maintenance-supporter-vacation",R="ll-strategy-section-maintenance-supporter-budget",A="ll-strategy-section-maintenance-supporter-groups";customElements.get(j)||customElements.define(j,k);customElements.get(R)||customElements.define(R,C);customElements.get(A)||customElements.define(A,S);var h=null;function J(){return h||(h=import("./chunks/dialog-mount-QSYSXY3T.js").catch(n=>{throw h=null,n})),h}var O="_msSupporterLlCustomBound";function m(n){history.pushState(null,"",n),window.dispatchEvent(new CustomEvent("location-changed"))}function Z(){let n=window;n[O]||(n[O]=!0,document.addEventListener("ll-custom",i=>{let e=i.detail;if(!e||typeof e!="object")return;let s=e.ll_custom,t=s&&typeof s=="object"?s:e;if(!t||typeof t.type!="string"||!t.type.startsWith("maintenance-supporter:"))return;let o=t.type.slice(22);if(o==="deep-link"&&typeof t.path=="string"){m(t.path);return}(async()=>{let a;try{a=await J()}catch{m("/maintenance-supporter");return}if(o==="add-object"){if(a.openCreateObjectDialog())return;m("/maintenance-supporter?ms_action=add_object");return}if(o==="open-task"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"){if(a.openTaskQuickActions(t.entry_id,t.task_id)||a.openEditTaskDialog(t.entry_id,t.task_id))return;m(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}&task_id=${encodeURIComponent(t.task_id)}`);return}if(o==="open-object"&&typeof t.entry_id=="string"){if(a.openObjectQuickActions(t.entry_id))return;m(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}`);return}if(o==="edit-history"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"&&typeof t.original_timestamp=="string"){let d=document.querySelector("home-assistant")?.hass;if(!d)return;try{let u=(await d.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:t.entry_id})).tasks?.find(p=>p.id===t.task_id)?.history?.find(p=>p.timestamp===t.original_timestamp);if(!u)return;a.openHistoryEditDialog({entry_id:t.entry_id,task_id:t.task_id,original_timestamp:t.original_timestamp,type:u.type||"completed",timestamp:u.timestamp||t.original_timestamp,notes:u.notes??null,cost:u.cost??null,duration:u.duration??null,completed_by:u.completed_by??null,used_parts:u.used_parts??null} Line truncated
@@ -22,5 +22,5 @@
"requirements": [
"pypdf>=4.3.0"
],
"version": "2.64.0"
"version": "2.65.0"
}
@@ -62,6 +62,14 @@ class MaintenanceTask:
# --- Trigger ---
trigger_config: dict[str, Any] | None = None
# --- Phases (#139): a cyclic sequence of different activities on ONE
# shared cadence. Definitions + sequence are STATIC config (dict-only,
# like consumes_parts — the model carries them read-only so complete()
# can rotate); the cursor is DYNAMIC (Store, like the rotation pointer).
phases: dict[str, Any] | None = None
phase_sequence: list[str] = field(default_factory=list)
phase_cursor: int = 0
# --- Metadata ---
notes: str | None = None
documentation_url: str | None = None
@@ -136,6 +144,17 @@ class MaintenanceTask:
due_override=parse_iso_date(self.due_override),
)
@property
def current_phase_name(self) -> str | None:
"""Name of the cycle phase currently due (#139), or None."""
if not (self.phases and self.phase_sequence):
return None
from ..helpers.phases import clamp_phase_cursor
phase_id = self.phase_sequence[clamp_phase_cursor(self.phase_cursor, len(self.phase_sequence))]
definition = self.phases.get(phase_id)
return definition.get("name") if isinstance(definition, dict) else None
def _planned_grid_due(self) -> date | None:
"""``next_due`` WITHOUT the postpone override — the drift-free grid.
@@ -375,6 +394,17 @@ class MaintenanceTask:
anchors.append(self.last_performed)
is_latest = ts_iso >= max(anchors, default="")
# Phases (#139): the step being completed is the one currently due.
# Resolved BEFORE the cursor advances; a pure backfill gets no phase
# attribution (we cannot know which step it was) and never rotates.
completed_phase_id: str | None = None
if is_latest and self.phases and self.phase_sequence:
from ..helpers.phases import clamp_phase_cursor
cursor = clamp_phase_cursor(self.phase_cursor, len(self.phase_sequence))
completed_phase_id = self.phase_sequence[cursor]
self.phase_cursor = (cursor + 1) % len(self.phase_sequence)
if is_latest:
# Save the PLANNED grid date as the anchor before resetting — not
# next_due, which returns a postpone override and would shift the
@@ -404,6 +434,7 @@ class MaintenanceTask:
used_parts=used_parts,
auto=auto,
timestamp=ts_iso,
phase_id=completed_phase_id,
)
# Shared tasks: rotate the "currently responsible" pointer to the next
@@ -508,6 +539,7 @@ class MaintenanceTask:
used_parts: list[dict[str, Any]] | None = None,
auto: bool = False,
timestamp: str | None = None,
phase_id: str | None = None,
) -> None:
"""Add an entry to the maintenance history.
@@ -520,6 +552,10 @@ class MaintenanceTask:
}
if auto:
entry["auto"] = True
# Phases (#139): which cycle step this completion performed — the key
# per-phase statistics and "last done" lines pool by.
if phase_id is not None:
entry["phase_id"] = phase_id
if notes is not None:
entry["notes"] = notes
if cost is not None:
@@ -630,6 +666,13 @@ class MaintenanceTask:
data["checklist"] = self.checklist
if self.adaptive_config is not None:
data["adaptive_config"] = self.adaptive_config
# Phases (#139): defs + sequence are static config (like
# trigger_config), the cursor is the dynamic pointer — emitted only
# for phase-carrying tasks so plain tasks stay lean.
if self.phases and self.phase_sequence:
data["phases"] = self.phases
data["phase_sequence"] = self.phase_sequence
data["phase_cursor"] = self.phase_cursor
return data
@classmethod
@@ -642,6 +685,9 @@ class MaintenanceTask:
``task.interval_days`` etc. is unaffected.
"""
sched = read_legacy_fields(data)
raw_phases = data.get("phases")
raw_seq = data.get("phase_sequence")
raw_cursor = data.get("phase_cursor", 0)
return cls(
id=data.get("id", uuid4().hex),
object_id=data.get("object_id", ""),
@@ -677,4 +723,7 @@ class MaintenanceTask:
checklist=data.get("checklist", []),
adaptive_config=data.get("adaptive_config"),
history=data.get("history", []),
phases=raw_phases if isinstance(raw_phases, dict) else None,
phase_sequence=list(raw_seq) if isinstance(raw_seq, list) else [],
phase_cursor=raw_cursor if isinstance(raw_cursor, int) else 0,
)
Loaded 100 of 149 files, more files were not shown because too many files have changed in this diff. Show more