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
+13414 -1155

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", "id": "88b90f2b1d6b45f0b44bee786ee1a525",
"url": "/taskmate/taskmate-attr-resolver.js?v=5.3.0", "url": "/taskmate/taskmate-attr-resolver.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "0796c92931dd458e81dc9c2677248274", "id": "0796c92931dd458e81dc9c2677248274",
"url": "/taskmate/taskmate-localize.js?v=5.3.0", "url": "/taskmate/taskmate-localize.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "d2e281920ac7409583eafff1f1db69bd", "id": "d2e281920ac7409583eafff1f1db69bd",
"url": "/taskmate/taskmate-design.js?v=5.3.0", "url": "/taskmate/taskmate-design.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "9eae9632f0734480be5edd784b030673", "id": "9eae9632f0734480be5edd784b030673",
"url": "/taskmate/taskmate-badges-card.js?v=5.3.0", "url": "/taskmate/taskmate-badges-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "5df49cd8a4eb420497b5df22cb31362c", "id": "5df49cd8a4eb420497b5df22cb31362c",
"url": "/taskmate/taskmate-child-card.js?v=5.3.0", "url": "/taskmate/taskmate-child-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "b44c87d0d74f468cb96c5eabc340f9ee", "id": "b44c87d0d74f468cb96c5eabc340f9ee",
"url": "/taskmate/taskmate-rewards-card.js?v=5.3.0", "url": "/taskmate/taskmate-rewards-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "787a96f9c30548708165a48457c99feb", "id": "787a96f9c30548708165a48457c99feb",
"url": "/taskmate/taskmate-approvals-card.js?v=5.3.0", "url": "/taskmate/taskmate-approvals-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "84031597de424ad68e7c2bc66d06b72e", "id": "84031597de424ad68e7c2bc66d06b72e",
"url": "/taskmate/taskmate-points-card.js?v=5.3.0", "url": "/taskmate/taskmate-points-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "02613225844746b699bfbfac9642eca2", "id": "02613225844746b699bfbfac9642eca2",
"url": "/taskmate/taskmate-reorder-card.js?v=5.3.0", "url": "/taskmate/taskmate-reorder-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "7a06a07e17ba4b31b712bc91b77360b4", "id": "7a06a07e17ba4b31b712bc91b77360b4",
"url": "/taskmate/taskmate-overview-card.js?v=5.3.0", "url": "/taskmate/taskmate-overview-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "08b42b53b33b4fa88db932455cf8d5aa", "id": "08b42b53b33b4fa88db932455cf8d5aa",
"url": "/taskmate/taskmate-activity-card.js?v=5.3.0", "url": "/taskmate/taskmate-activity-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "15907eff51784acdba88ff694b574509", "id": "15907eff51784acdba88ff694b574509",
"url": "/taskmate/taskmate-streak-card.js?v=5.3.0", "url": "/taskmate/taskmate-streak-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "1bd6bb5c0a6d4f0d916e3d82cb7fac39", "id": "1bd6bb5c0a6d4f0d916e3d82cb7fac39",
"url": "/taskmate/taskmate-weekly-card.js?v=5.3.0", "url": "/taskmate/taskmate-weekly-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "289ce5943f5a4972a593697db4c96ece", "id": "289ce5943f5a4972a593697db4c96ece",
"url": "/taskmate/taskmate-graph-card.js?v=5.3.0", "url": "/taskmate/taskmate-graph-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "d2904acee4c74a398dcc7b996c23e7e7", "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" "type": "module"
}, },
{ {
"id": "46d378736fd54a26be253f2dfb27202e", "id": "46d378736fd54a26be253f2dfb27202e",
"url": "/taskmate/taskmate-leaderboard-card.js?v=5.3.0", "url": "/taskmate/taskmate-leaderboard-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "db11ae36457842fa826c7991e1d9c6f4", "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" "type": "module"
}, },
{ {
"id": "ce85f301b59a412e9abfe5ffd51cb9a2", "id": "ce85f301b59a412e9abfe5ffd51cb9a2",
"url": "/taskmate/taskmate-penalties-card.js?v=5.3.0", "url": "/taskmate/taskmate-penalties-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "1959e1629de2422daa8adaf87069c288", "id": "1959e1629de2422daa8adaf87069c288",
"url": "/taskmate/taskmate-bonuses-card.js?v=5.3.0", "url": "/taskmate/taskmate-bonuses-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "2548524b269c4959bc7e0e967df943af", "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" "type": "module"
}, },
{ {
"id": "36ada49368db4187a283fbbbef45454f", "id": "36ada49368db4187a283fbbbef45454f",
"url": "/taskmate/taskmate-calendar-card.js?v=5.3.0", "url": "/taskmate/taskmate-calendar-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
"id": "67c32687acd2497b90030ef141f4b6db", "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" "type": "module"
}, },
{ {
"id": "6f0c8348a91f4b74a9b846ab3f62621f", "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" "type": "module"
}, },
{ {
@@ -276,7 +276,7 @@
}, },
{ {
"id": "73dbd2de03de4530b451a55bfe6c46cf", "id": "73dbd2de03de4530b451a55bfe6c46cf",
"url": "/taskmate/taskmate-routine-card.js?v=5.3.0", "url": "/taskmate/taskmate-routine-card.js?v=5.4.1",
"type": "module" "type": "module"
}, },
{ {
@@ -315,7 +315,7 @@
"type": "module" "type": "module"
}, },
{ {
"id": "9227b25fc2ea4cb5be22e4436912268a", "id": "17e81c8801114b2d941a7d5f22eb47e6",
"url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1", "url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1",
"type": "module" "type": "module"
} }
@@ -564,6 +564,12 @@ async def async_setup_entry(
_LOGGER.debug("Maintenance calendar entity created") _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): class MaintenanceCalendar(CalendarEntity):
"""Calendar entity aggregating all maintenance tasks.""" """Calendar entity aggregating all maintenance tasks."""
@@ -713,7 +719,7 @@ class MaintenanceCalendar(CalendarEntity):
today = dt_util.now().date() today = dt_util.now().date()
if start_d <= today <= end_d: if start_d <= today <= end_d:
return CalendarEvent( 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, start=today,
end=today + timedelta(days=1), end=today + timedelta(days=1),
description=_cal_t("manually_triggered", lang), description=_cal_t("manually_triggered", lang),
@@ -728,7 +734,7 @@ class MaintenanceCalendar(CalendarEntity):
today = dt_util.now().date() today = dt_util.now().date()
if start_d <= today <= end_d: if start_d <= today <= end_d:
return CalendarEvent( 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, start=today,
end=today + timedelta(days=1), end=today + timedelta(days=1),
description=_cal_t("sensor_triggered", lang, name=task.name), 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 pass # malformed schedule_time → fall back to all-day
return CalendarEvent( return CalendarEvent(
summary=f"{prefix} {task.name} ({object_name})", summary=f"{prefix} {_task_label(task)} ({object_name})",
start=start, start=start,
end=end, end=end,
description=( description=(
@@ -143,6 +143,9 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow):
menu.append("remove_trigger") menu.append("remove_trigger")
if global_opts.get(CONF_ADVANCED_CHECKLISTS, False): if global_opts.get(CONF_ADVANCED_CHECKLISTS, False):
menu.append("edit_checklist") 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): if global_opts.get(CONF_ADVANCED_ADAPTIVE, False):
menu.append("adaptive_scheduling") menu.append("adaptive_scheduling")
menu.extend(["delete_task", "manage_tasks"]) 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: async def async_step_delete_task(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Confirm and delete a task.""" """Confirm and delete a task."""
tasks_data = self.config_entry.data.get(CONF_TASKS, {}) tasks_data = self.config_entry.data.get(CONF_TASKS, {})
@@ -59,6 +59,16 @@ from .storage import MaintenanceStore
_LOGGER = logging.getLogger(__name__) _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]: def _inert_task_result(task: MaintenanceTask, status: str, **extra: Any) -> dict[str, Any]:
"""Coordinator payload for a task that gets NO live evaluation (archived / """Coordinator payload for a task that gets NO live evaluation (archived /
paused): due fields nulled, trigger off, only cost/history-derived fields paused): due fields nulled, trigger off, only cost/history-derived fields
@@ -744,7 +754,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
tasks=[ tasks=[
{ {
"task_id": tid, "task_id": tid,
"task_name": tr.get("name", ""), "task_name": _notify_task_label(tr),
"status": status, "status": status,
"days_until_due": tr.get("_days_until_due"), "days_until_due": tr.get("_days_until_due"),
} }
@@ -756,7 +766,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
await nm.async_task_status_changed( await nm.async_task_status_changed(
entry_id=self.entry.entry_id, entry_id=self.entry.entry_id,
task_id=task_id, task_id=task_id,
task_name=task_result.get("name", ""), task_name=_notify_task_label(task_result),
object_name=obj_name, object_name=obj_name,
new_status=new_status, new_status=new_status,
days_until_due=task_result.get("_days_until_due"), days_until_due=task_result.get("_days_until_due"),
@@ -862,6 +872,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
state["due_override"] = do state["due_override"] = do
elif "due_override" in state: elif "due_override" in state:
del state["due_override"] 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", [])) self._store.set_history(task_id, td.get("history", []))
if task.adaptive_config: if task.adaptive_config:
self._store.set_adaptive_config(task_id, 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 # will consume below — go on the record, so completions from the
# no-dialog surfaces (service, button, QR, to-do, voice, recovery) # no-dialog surfaces (service, button, QR, to-do, voice, recovery)
# stay correctable through the history editor like dialog ones. # stay correctable through the history editor like dialog ones.
from .helpers.phases import effective_field as _phase_effective
record_links = ( 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 enriched_used: list[dict[str, Any]] | None = None
if used_parts is not None or record_links: 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"), "required_completion_fields": tdata.get("required_completion_fields"),
"rotation_strategy": tdata.get("rotation_strategy"), "rotation_strategy": tdata.get("rotation_strategy"),
"reading_unit": tdata.get("reading_unit"), "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. # Spare parts: consumption links + the auto-buy-task marker.
"consumes_parts": tdata.get("consumes_parts"), "consumes_parts": tdata.get("consumes_parts"),
"part_ref": tdata.get("part_ref"), "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 * enforces the same list at every completion surface; blocking Save here
* just means the user never has to meet that rejection. */ * just means the user never has to meet that rejection. */
@property({ type: Array }) public requiredFields: string[] = []; @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 _open = false;
@state() private _notes = ""; @state() private _notes = "";
@state() private _cost = ""; @state() private _cost = "";
@@ -286,6 +289,7 @@ export class MaintenanceCompleteDialog extends LitElement {
return html` return html`
<ha-dialog open @closed=${this._close}> <ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t("complete_title", L)}${this.taskName}</div> <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"> <div class="content">
${this._error ? html`<div class="error">${this._error}</div>` : nothing} ${this._error ? html`<div class="error">${this._error}</div>` : nothing}
${this.checklist.length > 0 ? html` ${this.checklist.length > 0 ? html`
@@ -473,6 +477,12 @@ export class MaintenanceCompleteDialog extends LitElement {
font-weight: 500; font-weight: 500;
padding-bottom: 12px; padding-bottom: 12px;
} }
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content { .content {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -260,6 +260,18 @@ export class MaintenanceTaskDialog extends LitElement {
// Checklist (newline-separated steps, one per line) // Checklist (newline-separated steps, one per line)
@state() private _checklistText = ""; @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 /** Details this task demands on completion — enforced by the backend on
* every surface, so a button press or voice command cannot bypass it. */ * every surface, so a button press or voice command cannot bypass it. */
@state() private _requiredCompletion: string[] = []; @state() private _requiredCompletion: string[] = [];
@@ -388,6 +400,19 @@ export class MaintenanceTaskDialog extends LitElement {
this._rotationStrategy = task.rotation_strategy || ""; this._rotationStrategy = task.rotation_strategy || "";
this._checklistText = (task.checklist || []).join("\n"); 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._requiredCompletion = [...(task.required_completion_fields || [])];
this._scheduleTime = task.schedule_time || ""; this._scheduleTime = task.schedule_time || "";
@@ -506,6 +531,8 @@ export class MaintenanceTaskDialog extends LitElement {
this._assigneePool = []; this._assigneePool = [];
this._rotationStrategy = ""; this._rotationStrategy = "";
this._checklistText = ""; this._checklistText = "";
this._phaseDefs = [];
this._phaseSeq = [];
this._requiredCompletion = []; this._requiredCompletion = [];
this._scheduleTime = ""; this._scheduleTime = "";
this._environmentalEntity = ""; this._environmentalEntity = "";
@@ -993,6 +1020,103 @@ export class MaintenanceTaskDialog extends LitElement {
this._requiredCompletion = [...next]; 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> { private async _save(): Promise<void> {
if (this._loading) return; // synchronous re-entry guard (double-click) if (this._loading) return; // synchronous re-entry guard (double-click)
if (!this._name.trim()) return; if (!this._name.trim()) return;
@@ -1073,6 +1197,27 @@ export class MaintenanceTaskDialog extends LitElement {
data.last_performed = this._lastPerformed || null; data.last_performed = this._lastPerformed || null;
data.nfc_tag_id = this._nfcTagId || null; data.nfc_tag_id = this._nfcTagId || null;
data.reading_unit = this._readingUnit.trim() || 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 // Only send when a picker was actually rendered. A failed parts load
// leaves both lists empty, and sending [] then would silently wipe links // 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 // 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> ></textarea>
<div class="field-help">${t("checklist_help", L)}</div> <div class="field-help">${t("checklist_help", L)}</div>
` : nothing} ` : nothing}
${this._renderPhasesEditor(L)}
<h3>${t("require_on_completion", L)}</h3> <h3>${t("require_on_completion", L)}</h3>
<div class="required-completion"> <div class="required-completion">
${REQUIRED_COMPLETION_KEYS.map((field) => html` ${REQUIRED_COMPLETION_KEYS.map((field) => html`
@@ -2742,6 +2888,82 @@ export class MaintenanceTaskDialog extends LitElement {
gap: 8px; gap: 8px;
padding: 2px 0; 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 { .consumes-check {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -18,6 +18,7 @@ import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInter
import { describeWsError } from "../ws-errors"; import { describeWsError } from "../ws-errors";
import { isoDateLocal } from "../helpers/calendar-bucket"; import { isoDateLocal } from "../helpers/calendar-bucket";
import { partsForCompletion } from "../helpers/shared-parts"; import { partsForCompletion } from "../helpers/shared-parts";
import { effectivePhase, phaseLabel } from "../helpers/phases";
import { renderWeibullSection } from "../renderers/weibull"; import { renderWeibullSection } from "../renderers/weibull";
import { renderPredictionSection } from "../renderers/prediction"; import { renderPredictionSection } from "../renderers/prediction";
import { renderRecommendationBars } from "../renderers/recommendation"; import { renderRecommendationBars } from "../renderers/recommendation";
@@ -158,13 +159,17 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
import("../dialog-mount").then(async ({ openCompleteDialog }) => { import("../dialog-mount").then(async ({ openCompleteDialog }) => {
const task = this._task!; const task = this._task!;
const isBuy = !!(task as { part_ref?: string }).part_ref; 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"] = []; let parts: Parameters<typeof openCompleteDialog>[0]["parts"] = [];
if (!isBuy) { if (!isBuy) {
try { try {
const r = await this.hass.connection.sendMessagePromise<{ const r = await this.hass.connection.sendMessagePromise<{
objects: MaintenanceObjectResponse[]; objects: MaintenanceObjectResponse[];
}>({ type: "maintenance_supporter/objects", compact: true }); }>({ 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 { } catch {
// Parts stay empty — the dialog still completes without them. // Parts stay empty — the dialog still completes without them.
} }
@@ -173,13 +178,14 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
entry_id: this._entryId!, entry_id: this._entryId!,
task_id: this._taskId!, task_id: this._taskId!,
task_name: task.name, task_name: task.name,
checklist: task.checklist || [], checklist: phase ? phase.checklist : (task.checklist || []),
adaptive_enabled: !!task.adaptive_config?.enabled, 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 || "", task_type: task.type || "",
reading_unit: (task as { reading_unit?: string }).reading_unit || "", reading_unit: (task as { reading_unit?: string }).reading_unit || "",
parts, parts,
consumes_parts: isBuy ? [] : (task.consumes_parts || []), consumes_parts: isBuy ? [] : links,
phase_label: phaseLabel(task),
}); });
if (ok) { if (ok) {
this._notifyChanged("complete"); 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 ${(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>` ? html`<span><strong>${t("interval", L) || "Interval"}:</strong> ${formatRecurrence(task, L)}</span>`
: nothing} : nothing}
${phaseLabel(task)
? html`<span><strong>${t("phase_current", L)}:</strong> ${phaseLabel(task)}</span>`
: nothing}
</div> </div>
</div> </div>
@@ -289,6 +289,8 @@ export function openCompleteDialog(args: {
/** #99/#111: per-completion parts selection incl. shared pools. */ /** #99/#111: per-completion parts selection incl. shared pools. */
parts?: MaintenanceCompleteDialog["parts"]; parts?: MaintenanceCompleteDialog["parts"];
consumes_parts?: MaintenanceCompleteDialog["consumesParts"]; consumes_parts?: MaintenanceCompleteDialog["consumesParts"];
/** #139: "2/4 · Flip blades" — the phase this completion records. */
phase_label?: string;
}): boolean { }): boolean {
const dlg = getOrCreate<MaintenanceCompleteDialog>(COMPLETE_DIALOG_TAG); const dlg = getOrCreate<MaintenanceCompleteDialog>(COMPLETE_DIALOG_TAG);
if (!syncHass(dlg)) return false; if (!syncHass(dlg)) return false;
@@ -304,6 +306,7 @@ export function openCompleteDialog(args: {
dlg.readingUnit = args.reading_unit ?? ""; dlg.readingUnit = args.reading_unit ?? "";
dlg.parts = args.parts ?? []; dlg.parts = args.parts ?? [];
dlg.consumesParts = args.consumes_parts ?? []; dlg.consumesParts = args.consumes_parts ?? [];
dlg.phaseLabel = args.phase_label ?? "";
dlg.lang = (getHass()?.language) || "en"; dlg.lang = (getHass()?.language) || "en";
dlg.open(); dlg.open();
return true; return true;
@@ -889,5 +889,14 @@
"service_record_entries": "záznamů", "service_record_entries": "záznamů",
"completed_by": "Dokončil", "completed_by": "Dokončil",
"date_from": "Od", "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", "service_record_entries": "poster",
"completed_by": "Udført af", "completed_by": "Udført af",
"date_from": "Fra", "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", "service_record_entries": "Einträge",
"completed_by": "Erledigt von", "completed_by": "Erledigt von",
"date_from": "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", "service_record_entries": "entries",
"completed_by": "Completed by", "completed_by": "Completed by",
"date_from": "From", "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", "service_record_entries": "entradas",
"completed_by": "Realizado por", "completed_by": "Realizado por",
"date_from": "Desde", "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ää", "service_record_entries": "merkintää",
"completed_by": "Suorittanut", "completed_by": "Suorittanut",
"date_from": "Alkaen", "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", "service_record_entries": "entrées",
"completed_by": "Réalisé par", "completed_by": "Réalisé par",
"date_from": "Du", "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": "प्रविष्टियाँ", "service_record_entries": "प्रविष्टियाँ",
"completed_by": "द्वारा पूर्ण", "completed_by": "द्वारा पूर्ण",
"date_from": "से", "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", "service_record_entries": "bejegyzés",
"completed_by": "Elvégezte", "completed_by": "Elvégezte",
"date_from": "Ettől", "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", "service_record_entries": "voci",
"completed_by": "Completato da", "completed_by": "Completato da",
"date_from": "Dal", "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": "件", "service_record_entries": "件",
"completed_by": "実施者", "completed_by": "実施者",
"date_from": "開始", "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": "건", "service_record_entries": "건",
"completed_by": "수행자", "completed_by": "수행자",
"date_from": "시작", "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", "service_record_entries": "oppføringer",
"completed_by": "Utført av", "completed_by": "Utført av",
"date_from": "Fra", "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", "service_record_entries": "items",
"completed_by": "Voltooid door", "completed_by": "Voltooid door",
"date_from": "Van", "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", "service_record_entries": "wpisów",
"completed_by": "Wykonane przez", "completed_by": "Wykonane przez",
"date_from": "Od", "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", "service_record_entries": "registros",
"completed_by": "Concluído por", "completed_by": "Concluído por",
"date_from": "De", "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", "service_record_entries": "registos",
"completed_by": "Concluído por", "completed_by": "Concluído por",
"date_from": "De", "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": "записей", "service_record_entries": "записей",
"completed_by": "Выполнил", "completed_by": "Выполнил",
"date_from": "С", "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", "service_record_entries": "poster",
"completed_by": "Utförd av", "completed_by": "Utförd av",
"date_from": "Från", "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", "service_record_entries": "kayıt",
"completed_by": "Tamamlayan", "completed_by": "Tamamlayan",
"date_from": "Başlangıç", "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": "записів", "service_record_entries": "записів",
"completed_by": "Виконав", "completed_by": "Виконав",
"date_from": "Від", "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": "条记录", "service_record_entries": "条记录",
"completed_by": "完成人", "completed_by": "完成人",
"date_from": "从", "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"; } from "./types";
import { UserService } from "./user-service"; import { UserService } from "./user-service";
import { partsForCompletion } from "./helpers/shared-parts"; import { partsForCompletion } from "./helpers/shared-parts";
import { effectivePhase, phaseLabel } from "./helpers/phases";
import "./maintenance-card-editor"; import "./maintenance-card-editor";
import "./components/complete-dialog"; import "./components/complete-dialog";
// The Battery Fleet card ships in this globally-loaded bundle so it is // The Battery Fleet card ships in this globally-loaded bundle so it is
@@ -469,7 +470,7 @@ export class MaintenanceSupporterCard extends LitElement {
</div> </div>
${!compact ${!compact
? html`<div class="task-meta"> ? 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" ? html` · <span class="assignee"
><ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(task)}</span ><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 // Stop the row's open-task handler from also firing
e.stopPropagation(); e.stopPropagation();
const dlg = this.shadowRoot!.querySelector("maintenance-complete-dialog") as any; 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.entryId = entry_id;
dlg.taskId = task.id; dlg.taskId = task.id;
dlg.taskName = task.name; dlg.taskName = task.name;
dlg.checklist = task.checklist || []; dlg.checklist = phase ? phase.checklist : (task.checklist || []);
dlg.adaptiveEnabled = !!task.adaptive_config?.enabled; dlg.adaptiveEnabled = !!task.adaptive_config?.enabled;
dlg.taskType = task.type || ""; dlg.taskType = task.type || "";
dlg.readingUnit = (task as any).reading_unit || ""; 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; dlg.lang = L;
// #99: editable per-completion parts selection // #99: editable per-completion parts selection
// (skip on buy tasks — those restock instead). // (skip on buy tasks — those restock instead).
@@ -530,8 +536,9 @@ export class MaintenanceSupporterCard extends LitElement {
// owner — resolving against the object's own // owner — resolving against the object's own
// parts alone left a foreign link invisible. // parts alone left a foreign link invisible.
const isBuy = !!(task as any).part_ref; const isBuy = !!(task as any).part_ref;
dlg.parts = isBuy ? [] : partsForCompletion(task, entry_id, this._objects, L); const links = phase ? phase.consumesParts : (task.consumes_parts || []);
dlg.consumesParts = isBuy ? [] : (task.consumes_parts || []); dlg.parts = isBuy ? [] : partsForCompletion({ consumes_parts: links }, entry_id, this._objects, L);
dlg.consumesParts = isBuy ? [] : links;
dlg.open(); dlg.open();
}} }}
> >
@@ -17,6 +17,7 @@ import { OBJECT_COLUMNS, DEFAULT_OBJECTS_TABLE_COLUMNS, sanitizeColumns } from "
import { downloadTextFile } from "./helpers/download"; import { downloadTextFile } from "./helpers/download";
import { buildTaskWorksheetHtml, type WorksheetExcerpt, type WorksheetLabels } from "./helpers/worksheet"; import { buildTaskWorksheetHtml, type WorksheetExcerpt, type WorksheetLabels } from "./helpers/worksheet";
import { describePartLink, partsForCompletion } from "./helpers/shared-parts"; import { describePartLink, partsForCompletion } from "./helpers/shared-parts";
import { effectivePhase, phaseLabel } from "./helpers/phases";
import { describeWsError } from "./ws-errors"; import { describeWsError } from "./ws-errors";
import { panelStyles } from "./panel-styles"; import { panelStyles } from "./panel-styles";
import type { import type {
@@ -1861,7 +1862,9 @@ export class MaintenanceSupporterPanel extends LitElement {
const task = obj?.tasks.find((x) => x.id === taskId); const task = obj?.tasks.find((x) => x.id === taskId);
if (!task) return; if (!task) return;
const state: Record<string, boolean> = {}; 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; const current = task.checklist_progress?.[step] ?? false;
state[step] = step === item ? done : current; state[step] = step === item ? done : current;
} }
@@ -1888,11 +1891,20 @@ export class MaintenanceSupporterPanel extends LitElement {
const tk = this._objects const tk = this._objects
.find((o) => o.entry_id === entryId) .find((o) => o.entry_id === entryId)
?.tasks.find((tsk) => tsk.id === taskId); ?.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.taskType = tk?.type || "";
dlg.readingUnit = tk?.reading_unit || ""; dlg.readingUnit = tk?.reading_unit || "";
// #73: ticks recorded during the cycle prefill the dialog's checklist. // #73: ticks recorded during the cycle prefill the dialog's checklist.
dlg.checklistPrefill = tk?.checklist_progress || {}; 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 // Spare parts: a buy task gets an editable restock-qty field; a consuming
// task shows what it will decrement (incl. the storage location). // task shows what it will decrement (incl. the storage location).
const objParts = this._objects.find((o) => o.entry_id === entryId)?.parts || []; const objParts = this._objects.find((o) => o.entry_id === entryId)?.parts || [];
@@ -1904,15 +1916,16 @@ export class MaintenanceSupporterPanel extends LitElement {
dlg.currencySymbol = this._currencySymbol; dlg.currencySymbol = this._currencySymbol;
// #111: a link may point at another object's pool — name that object, and // #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). // 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), describePartLink(link, entryId, this._objects, this._lang),
); );
// #99: editable per-completion parts selection (not on buy tasks — those // #99: editable per-completion parts selection (not on buy tasks — those
// RESTOCK via the qty field instead of consuming). The list carries the // 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 // object's own parts plus the shared pools this task draws on, so a foreign
// link is visible and untickable rather than silently absent. // link is visible and untickable rather than silently absent.
dlg.parts = tk?.part_ref ? [] : partsForCompletion(tk, entryId, this._objects, this._lang); dlg.parts = tk?.part_ref ? [] : partsForCompletion({ consumes_parts: links }, entryId, this._objects, this._lang);
dlg.consumesParts = tk?.part_ref ? [] : (tk?.consumes_parts || []); dlg.consumesParts = tk?.part_ref ? [] : links;
dlg.open(); dlg.open();
} }
@@ -3535,6 +3548,7 @@ export class MaintenanceSupporterPanel extends LitElement {
lang: this._lang, lang: this._lang,
detailStatsData: this._detailStatsData, detailStatsData: this._detailStatsData,
hasStatsService: !!this._statsService, hasStatsService: !!this._statsService,
historyFallbackIds: this._statsService?.historyFallbackIds,
isCounterEntity: (tc) => this._isCounterEntity(tc), isCounterEntity: (tc) => this._isCounterEntity(tc),
rangeDays: this._chartRangeDays, rangeDays: this._chartRangeDays,
setRangeDays: (days) => this._setChartRange(days), setRangeDays: (days) => this._setChartRange(days),
@@ -3579,6 +3593,10 @@ export class MaintenanceSupporterPanel extends LitElement {
setSearch: (s) => { this._historySearch = s; }, setSearch: (s) => { this._historySearch = s; },
openEdit: (entry) => this._openHistoryEdit(entry), openEdit: (entry) => this._openHistoryEdit(entry),
readingUnit: task?.reading_unit ?? null, 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) => { readingDelta: (entry) => {
const idx = readings.findIndex((h) => h.timestamp === entry.timestamp); const idx = readings.findIndex((h) => h.timestamp === entry.timestamp);
if (idx <= 0) return null; if (idx <= 0) return null;
@@ -3604,6 +3622,12 @@ export class MaintenanceSupporterPanel extends LitElement {
objectManualDocs: obj?.object?.manual_docs ?? [], objectManualDocs: obj?.object?.manual_docs ?? [],
openManualDoc: (doc) => this._openManualDoc(doc), openManualDoc: (doc) => this._openManualDoc(doc),
setChecklistItem: (item, done) => this._setChecklistItem(entryId, taskId, item, done), 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, isOperator: this._isOperator,
actionLoading: this._actionLoading, actionLoading: this._actionLoading,
moreMenuOpen: this._moreMenuOpen, moreMenuOpen: this._moreMenuOpen,
@@ -12,12 +12,16 @@
"playwright": "1.62" "playwright": "1.62"
}, },
"devDependencies": { "devDependencies": {
"@mdi/js": "^7.4.47",
"@open-wc/testing": "^5.0.0", "@open-wc/testing": "^5.0.0",
"@types/react": "^19.2.18",
"@web/dev-server-esbuild": "^2.0.0", "@web/dev-server-esbuild": "^2.0.0",
"@web/test-runner": "^1.0.0", "@web/test-runner": "^1.0.0",
"@web/test-runner-playwright": "^1.0.0", "@web/test-runner-playwright": "^1.0.0",
"esbuild": "^0.28.1", "esbuild": "^0.28.1",
"lit": "^3.3.3", "lit": "^3.3.3",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"typescript": "^7.0.2" "typescript": "^7.0.2"
} }
}, },
@@ -550,6 +554,13 @@
"@lit-labs/ssr-dom-shim": "^1.5.0" "@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": { "node_modules/@mdn/browser-compat-data": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz", "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz",
@@ -1586,6 +1597,16 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@types/resolve": {
"version": "1.20.2", "version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
@@ -2921,6 +2942,13 @@
"node": ">= 8" "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": { "node_modules/data-uri-to-buffer": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "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": ">= 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": { "node_modules/readdirp": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
@@ -5221,6 +5272,13 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/semver": {
"version": "7.7.4", "version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "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" "test:watch": "web-test-runner --config web-test-runner.config.mjs --watch"
}, },
"devDependencies": { "devDependencies": {
"@mdi/js": "^7.4.47",
"@open-wc/testing": "^5.0.0", "@open-wc/testing": "^5.0.0",
"@types/react": "^19.2.18",
"@web/dev-server-esbuild": "^2.0.0", "@web/dev-server-esbuild": "^2.0.0",
"@web/test-runner": "^1.0.0", "@web/test-runner": "^1.0.0",
"@web/test-runner-playwright": "^1.0.0", "@web/test-runner-playwright": "^1.0.0",
"esbuild": "^0.28.1", "esbuild": "^0.28.1",
"lit": "^3.3.3", "lit": "^3.3.3",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"typescript": "^7.0.2" "typescript": "^7.0.2"
}, },
"dependencies": { "dependencies": {
@@ -1053,6 +1053,53 @@ export const panelStyles = css`
border-color: var(--primary-color); 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 (read-only display in task overview) */
.checklist-preview-card { .checklist-preview-card {
background: var(--card-background-color, #fff); background: var(--card-background-color, #fff);
@@ -27,6 +27,8 @@ export interface HistoryContext {
/** v2.20 (#83): unit + delta-vs-previous for reading-task entries. */ /** v2.20 (#83): unit + delta-vs-previous for reading-task entries. */
readingUnit?: string | null; readingUnit?: string | null;
readingDelta?: (entry: HistoryEntry) => number | 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 // 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-content">
<div class="history-row"> <div class="history-row">
<strong>${t(entry.type, L)}</strong> <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} ${entry.auto ? html`<span class="history-auto-badge">${t("history_auto", L)}</span>` : nothing}
${editable ${editable
? html`<button class="history-edit-btn" ? html`<button class="history-edit-btn"
@@ -22,6 +22,11 @@ export interface SparklineContext {
lang: string; lang: string;
detailStatsData: Map<string, StatisticsPoint[]>; detailStatsData: Map<string, StatisticsPoint[]>;
hasStatsService: boolean; 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; isCounterEntity: (tc: TriggerConfig) => boolean;
rangeDays: number; rangeDays: number;
setRangeDays: (days: number) => void; 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.push({ ts: Date.now(), val: task.trigger_current_value });
} }
points.sort((a, b) => a.ts - b.ts); 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)} @range-change=${(e: CustomEvent<{ days: number }>) => ctx.setRangeDays(e.detail.days)}
@outlier-toggle=${(e: CustomEvent<{ hide: boolean }>) => ctx.setHideOutliers(e.detail.hide)} @outlier-toggle=${(e: CustomEvent<{ hide: boolean }>) => ctx.setHideOutliers(e.detail.hide)}
></maintenance-trigger-chart> ></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> <ha-icon icon="mdi:information-outline"></ha-icon>
${t("chart_no_stats", ctx.lang)} ${t("chart_no_stats", ctx.lang)}
</div>` </div>`;
: nothing} }
return nothing;
})()}
`; `;
} }
@@ -21,6 +21,7 @@ import { renderSeasonalCardCompact, renderSeasonalCardExpanded } from "./seasona
import { renderCostDurationCard } from "./charts"; import { renderCostDurationCard } from "./charts";
import { renderDaysProgress } from "./progress"; import { renderDaysProgress } from "./progress";
import { renderHistoryFilters, renderHistoryList, type HistoryContext } from "./history"; import { renderHistoryFilters, renderHistoryList, type HistoryContext } from "./history";
import { clampPhaseCursor, effectivePhase, hasPhases } from "../helpers/phases";
import "../components/task-documents"; import "../components/task-documents";
export interface TaskDetailContext { export interface TaskDetailContext {
@@ -39,6 +40,8 @@ export interface TaskDetailContext {
openManualDoc: (doc: ManualDocRef) => void; openManualDoc: (doc: ManualDocRef) => void;
/** #73: persist one checklist tick (panel sends the full state via WS). */ /** #73: persist one checklist tick (panel sends the full state via WS). */
setChecklistItem: (item: string, done: boolean) => void; setChecklistItem: (item: string, done: boolean) => void;
/** #139: re-point the phase cursor (operator repair / mid-cycle adoption). */
setPhaseCursor: (cursor: number) => void;
isOperator: boolean; isOperator: boolean;
actionLoading: boolean; actionLoading: boolean;
moreMenuOpen: 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 * reloads, prefills the complete dialog) and resets when the task is
* completed or skipped. Only rendered when the Checklists feature is * completed or skipped. Only rendered when the Checklists feature is
* enabled and steps are set. */ * 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) { function renderChecklistCard(task: MaintenanceTask, ctx: TaskDetailContext) {
if (!ctx.features.checklists) return nothing; 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; if (items.length === 0) return nothing;
const L = ctx.lang; const L = ctx.lang;
const progress = task.checklist_progress || {}; const progress = task.checklist_progress || {};
@@ -439,6 +484,7 @@ export function renderOverviewTab(task: MaintenanceTask, ctx: TaskDetailContext)
</div> </div>
`, ctx) `, ctx)
: nothing} : nothing}
${renderPhasesCard(task, ctx)}
${renderChecklistCard(task, ctx)} ${renderChecklistCard(task, ctx)}
${renderRecentActivities(task, ctx)} ${renderRecentActivities(task, ctx)}
</div> </div>
@@ -8,11 +8,27 @@ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
const DETAIL_DAYS = 30; const DETAIL_DAYS = 30;
const MINI_DAYS = 14; 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 { export class StatisticsService {
private _hass: HomeAssistant; private _hass: HomeAssistant;
private _cache = new Map<string, EntityStatisticsCache>(); private _cache = new Map<string, EntityStatisticsCache>();
private _pending = new Map<string, Promise<StatisticsPoint[]>>(); 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) { constructor(hass: HomeAssistant) {
this._hass = hass; this._hass = hass;
} }
@@ -141,7 +157,23 @@ export class StatisticsService {
})) as Record<string, HAStatisticsRow[]>; })) as Record<string, HAStatisticsRow[]>;
const rows = result[entityId] || []; 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, { this._cache.set(cacheKey, {
entityId, 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( private async _fetchBatch(
entityIds: string[], entityIds: string[],
period: "hour" | "day", period: "hour" | "day",
@@ -604,6 +604,21 @@ export const sharedStyles = css`
color: var(--secondary-text-color); color: var(--secondary-text-color);
white-space: nowrap; 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 { .history-edit-btn {
background: transparent; background: transparent;
color: var(--secondary-text-color); color: var(--secondary-text-color);
@@ -103,6 +103,9 @@ export interface HistoryEntry {
/** v2.37: completion recorded by the system itself (trigger recovered), /** v2.37: completion recorded by the system itself (trigger recovered),
* not performed by a user in the UI. */ * not performed by a user in the UI. */
auto?: boolean; 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 { export interface AdaptiveConfig {
@@ -269,6 +272,22 @@ export interface MaintenanceTask {
consumes_parts?: TaskPartLink[] | null; consumes_parts?: TaskPartLink[] | null;
/** Present on an auto-created "buy" reminder: the owning part. */ /** Present on an auto-created "buy" reminder: the owning part. */
part_ref?: { part_id: string } | null; 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`. /** One entry of a task's `consumes_parts`.
@@ -889,5 +889,14 @@
"service_record_entries": "záznamů", "service_record_entries": "záznamů",
"completed_by": "Dokončil", "completed_by": "Dokončil",
"date_from": "Od", "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", "service_record_entries": "poster",
"completed_by": "Udført af", "completed_by": "Udført af",
"date_from": "Fra", "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", "service_record_entries": "Einträge",
"completed_by": "Erledigt von", "completed_by": "Erledigt von",
"date_from": "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", "service_record_entries": "entries",
"completed_by": "Completed by", "completed_by": "Completed by",
"date_from": "From", "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", "service_record_entries": "entradas",
"completed_by": "Realizado por", "completed_by": "Realizado por",
"date_from": "Desde", "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ää", "service_record_entries": "merkintää",
"completed_by": "Suorittanut", "completed_by": "Suorittanut",
"date_from": "Alkaen", "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", "service_record_entries": "entrées",
"completed_by": "Réalisé par", "completed_by": "Réalisé par",
"date_from": "Du", "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": "प्रविष्टियाँ", "service_record_entries": "प्रविष्टियाँ",
"completed_by": "द्वारा पूर्ण", "completed_by": "द्वारा पूर्ण",
"date_from": "से", "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", "service_record_entries": "bejegyzés",
"completed_by": "Elvégezte", "completed_by": "Elvégezte",
"date_from": "Ettől", "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", "service_record_entries": "voci",
"completed_by": "Completato da", "completed_by": "Completato da",
"date_from": "Dal", "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": "件", "service_record_entries": "件",
"completed_by": "実施者", "completed_by": "実施者",
"date_from": "開始", "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": "건", "service_record_entries": "건",
"completed_by": "수행자", "completed_by": "수행자",
"date_from": "시작", "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", "service_record_entries": "oppføringer",
"completed_by": "Utført av", "completed_by": "Utført av",
"date_from": "Fra", "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", "service_record_entries": "items",
"completed_by": "Voltooid door", "completed_by": "Voltooid door",
"date_from": "Van", "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", "service_record_entries": "wpisów",
"completed_by": "Wykonane przez", "completed_by": "Wykonane przez",
"date_from": "Od", "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", "service_record_entries": "registros",
"completed_by": "Concluído por", "completed_by": "Concluído por",
"date_from": "De", "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", "service_record_entries": "registos",
"completed_by": "Concluído por", "completed_by": "Concluído por",
"date_from": "De", "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": "записей", "service_record_entries": "записей",
"completed_by": "Выполнил", "completed_by": "Выполнил",
"date_from": "С", "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", "service_record_entries": "poster",
"completed_by": "Utförd av", "completed_by": "Utförd av",
"date_from": "Från", "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", "service_record_entries": "kayıt",
"completed_by": "Tamamlayan", "completed_by": "Tamamlayan",
"date_from": "Başlangıç", "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": "записів", "service_record_entries": "записів",
"completed_by": "Виконав", "completed_by": "Виконав",
"date_from": "Від", "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": "条记录", "service_record_entries": "条记录",
"completed_by": "完成人", "completed_by": "完成人",
"date_from": "从", "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 */ /*! maintenance_supporter frontend 2.65.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 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]`,ye=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ht=/-->/g,qt=/>/g,se=RegExp(`>|${Ze}(?:([^\\s"'>=/]+)(${Ze}*=${Ze}*(?:[^ \f\r]`,xe=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Mt=/-->/g,Dt=/>/g,re=RegExp(`>|${et}(?:([^\\s"'>=/]+)(${et}*=${et}*(?:[^
\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 \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 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 { display: flex; flex-direction: column; gap: 4px; }
.field-label { font-size: 12px; color: var(--secondary-text-color); } .field-label { font-size: 12px; color: var(--secondary-text-color); }
.field-input { .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; font-family: inherit; width: 100%; box-sizing: border-box;
} }
.field-input:focus { outline: none; border-color: var(--primary-color); } .field-input:focus { outline: none; border-color: var(--primary-color); }
`,Fe=A` `,Be=A`
:host { :host {
--maint-ok-color: var(--success-color, #4caf50); --maint-ok-color: var(--success-color, #4caf50);
--maint-due-soon-color: var(--warning-color, #ff9800); --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); color: var(--secondary-text-color);
white-space: nowrap; 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 { .history-edit-btn {
background: transparent; background: transparent;
color: var(--secondary-text-color); 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-item .stat-label { font-size: 11px; white-space: normal; text-align: center; line-height: 1.2; }
.stat-value { font-size: 20px; } .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" type="button"
class="cost-suggestion" class="cost-suggestion"
@click=${()=>this._cost=t.toFixed(2)} @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` >${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}> <ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div> <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"> <div class="content">
${this._error?o`<div class="error">${this._error}</div>`:u} ${this._error?o`<div class="error">${this._error}</div>`:u}
${this.checklist.length>0?o` ${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 <ha-button
@click=${this._complete} @click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0} .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)} ${this._loading?r("completing",e):r("complete",e)}
</ha-button> </ha-button>
</div> </div>
</ha-dialog> </ha-dialog>
`}};k.styles=[_i,A` `}};k.styles=[fi,A`
.req-mark { .req-mark {
color: var(--error-color, #f44336); color: var(--error-color, #f44336);
margin-left: 2px; margin-left: 2px;
@@ -1285,6 +1301,12 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
font-weight: 500; font-weight: 500;
padding-bottom: 12px; padding-bottom: 12px;
} }
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content { .content {
display: flex; display: flex;
flex-direction: column; 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); color: var(--text-primary-color, #fff);
border-color: var(--primary-color); 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"> <label class="field">
${this.label?o`<span class="label">${this.label}${this.required?o`<span class="req">*</span>`:u}</span>`:u} ${this.label?o`<span class="label">${this.label}${this.required?o`<span class="req">*</span>`:u}</span>`:u}
<input <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} ${this.helper?o`<span class="helper">${this.helper}</span>`:u}
</label> </label>
`}};H.styles=A` `}};q.styles=A`
:host { display: block; } :host { display: block; }
.field { .field {
display: flex; 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); color: var(--secondary-text-color);
font-style: italic; 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}> <ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t}</div> <div class="dialog-title">${t}</div>
<div class="content"> <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); color: var(--error-color, #f44336);
font-size: 13px; 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 `,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._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 `),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"> <details class="ca-section">
<summary>${r("on_complete_action_title",e)}</summary> <summary>${r("on_complete_action_title",e)}</summary>
<p class="field-help">${r("on_complete_action_desc",e)}</p> <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}}}} @input=${d=>{let p=parseFloat(d.target.value);this._consumesParts={...this._consumesParts,[i]:{...l,quantity:Number.isFinite(p)&&p>=.01?p:1}}}}
/>`:u} />`:u}
</div> </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(` `}_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`
`).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` <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> <h3>${r("trigger_configuration",e)}</h3>
<div class="select-row"> <div class="select-row">
<label>${r("trigger_type",e)}</label> <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} .value=${this._triggerType}
@change=${i=>this._triggerType=i.target.value} @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> </select>
</div> </div>
${t?this._renderCompoundEditor():o` ${t?this._renderCompoundEditor():o`
@@ -1739,7 +1825,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-form <ha-form
class="entity-picker-form" class="entity-picker-form"
.hass=${this.hass} .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]:[]}} .data=${{trigger_entities:this._triggerEntityIds.length>0?this._triggerEntityIds:this._triggerEntityId?[this._triggerEntityId]:[]}}
.computeLabel=${()=>r("entity_id",e)} .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("")}} @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> </select>
</div> </div>
`:u} `: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"> <div class="select-row">
<label>${r("compound_logic",e)}</label> <label>${r("compound_logic",e)}</label>
<select <select
@@ -1824,7 +1910,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-form <ha-form
class="entity-picker-form" class="entity-picker-form"
.hass=${this.hass} .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)}} .data=${{condition_entities:e.entityIds.split(",").map(l=>l.trim()).filter(Boolean)}}
.computeLabel=${()=>r("entity_id",i)} .computeLabel=${()=>r("entity_id",i)}
@value-changed=${l=>{let d=(l.detail.value.condition_entities||[]).filter(Boolean);this._patchCondition(t,{entityIds:d.join(", ")})}} @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} .value=${e.type}
@change=${l=>this._patchCondition(t,{type:l.target.value})} @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> </select>
</div> </div>
${this._renderConditionTypeFields(e,t)} ${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>`)} ${["days","weeks","months","years"].map(t=>o`<option value=${t} ?selected=${t===this._intervalUnit}>${r("unit_"+t,e)}</option>`)}
</select> </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"> <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} ${r("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?o` <span class="field-help">${r("schedule_preview_ends",e)}</span>`:u}
${n} ${n}
</div> </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> <label class="field-label">${r("season_window_label",e)}</label>
<div class="field-help">${r("season_window_hint",e)}</div> <div class="field-help">${r("season_window_hint",e)}</div>
<div class="weekday-chips season-chips"> <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} .value=${this._endsUntil}
@input=${n=>this._endsUntil=n.target.value} @input=${n=>this._endsUntil=n.target.value}
></ms-textfield>`:u} ></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> <label class="field-label">${r("recurrence_on_days",e)}</label>
<div class="weekday-chips"> <div class="weekday-chips">
${t.map((i,n)=>o` ${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" max="15"
.value=${this._calOffset} .value=${this._calOffset}
@input=${t=>this._calOffset=t.target.value} @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 <ms-textfield
label="${r("trigger_above",e)}" label="${r("trigger_above",e)}"
type="number" type="number"
@@ -2196,7 +2282,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._type} .value=${this._type}
@change=${i=>this._type=i.target.value} @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> </select>
</div> </div>
${this._type==="reading"?o` ${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} .value=${this._priority}
@change=${i=>this._priority=i.target.value} @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> </select>
</div> </div>
<div class="field"> <div class="field">
@@ -2249,7 +2335,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
.value=${this._scheduleType} .value=${this._scheduleType}
@change=${i=>this._scheduleType=i.target.value} @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> </select>
</div> </div>
${this._scheduleType==="time_based"?o` ${this._scheduleType==="time_based"?o`
@@ -2318,16 +2404,17 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
></textarea> ></textarea>
<div class="field-help">${r("checklist_help",e)}</div> <div class="field-help">${r("checklist_help",e)}</div>
`:u} `:u}
${this._renderPhasesEditor(e)}
<h3>${r("require_on_completion",e)}</h3> <h3>${r("require_on_completion",e)}</h3>
<div class="required-completion"> <div class="required-completion">
${fi.map(i=>o` ${yi.map(i=>o`
<label class="req-option"> <label class="req-option">
<input <input
type="checkbox" type="checkbox"
.checked=${this._requiredCompletion.includes(i)} .checked=${this._requiredCompletion.includes(i)}
@change=${n=>this._toggleRequired(i,n.target.checked)} @change=${n=>this._toggleRequired(i,n.target.checked)}
/> />
<span>${r(We[i],e)}</span> <span>${r(Ge[i],e)}</span>
</label> </label>
`)} `)}
</div> </div>
@@ -2386,7 +2473,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
<ha-form <ha-form
class="entity-picker-form" class="entity-picker-form"
.hass=${this.hass} .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}} .data=${{environmental_entity:this._environmentalEntity}}
.computeLabel=${()=>r("environmental_entity_optional",e)} .computeLabel=${()=>r("environmental_entity_optional",e)}
.computeHelper=${()=>r("environmental_entity_helper",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; gap: 8px;
padding: 2px 0; 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 { .consumes-check {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -2750,7 +2913,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
font-size: 14px; font-size: 14px;
cursor: pointer; 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="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true"> <div class="dialog" role="dialog" aria-modal="true">
<h2>${r("history_edit_title",e)||"Edit history entry"}</h2> <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-row-edit input[type="checkbox"] { width: auto; }
.part-label { flex: 1; color: var(--primary-text-color); } .part-label { flex: 1; color: var(--primary-text-color); }
.part-qty { width: 76px; } .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"> <meta name="color-scheme" content="light">
<title>${d}</title> <title>${d}</title>
<style> <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>`:""} ${p?`<div class="sub">${p}</div>`:""}
<div class="qr-row"> <div class="qr-row">
<div class="qr-col"> <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 class="qr-label">${v}</div>
</div> </div>
${_?`<div class="qr-col"> ${_?`<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 class="qr-label">${m}</div>
</div>`:""} </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> <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}> <ha-dialog open @closed=${this._close}>
<div class="dialog-title">${t}</div> <div class="dialog-title">${t}</div>
<div class="content"> <div class="content">
@@ -2981,7 +3144,7 @@ ${p?`<div class="sub">${p}</div>`:""}
</ha-button> </ha-button>
</div> </div>
</ha-dialog> </ha-dialog>
`}};M.styles=A` `}};D.styles=A`
.dialog-title { .dialog-title {
font-size: 18px; font-size: 18px;
font-weight: 500; font-weight: 500;
@@ -3099,26 +3262,26 @@ ${p?`<div class="sub">${p}</div>`:""}
color: var(--text-primary-color, #fff); color: var(--text-primary-color, #fff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15); 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-section">
<div class="weibull-title"> <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> <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)} ${r("weibull_reliability_curve",s)}
${Ks(t,s)} ${Zs(t,s)}
</div> </div>
${Gs(t,i,n,l,s)} ${Xs(t,i,n,l,s)}
${Ys(e,s)} ${er(e,s)}
${e?.confidence_interval_low!=null?Qs(e,a,s):u} ${e?.confidence_interval_low!=null?tr(e,a,s):u}
</div> </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}"> <span class="beta-badge ${e}">
<ha-svg-icon path="${t}"></ha-svg-icon> <ha-svg-icon path="${t}"></ha-svg-icon>
${r(i,s)} (\u03B2=${a.toFixed(2)}) ${r(i,s)} (\u03B2=${a.toFixed(2)})
</span> </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"> <div class="weibull-chart">
<svg viewBox="0 0 ${300} ${160}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${r("chart_weibull",i)}"> <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)}" <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}" /> 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)" <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="${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="${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(x)}</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" /> <path d="${P}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
<polyline points="${z}" fill="none" <polyline points="${H}" fill="none"
stroke="var(--primary-color, #03a9f4)" stroke-width="2" /> 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)}" <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" /> 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)" /> fill="var(--primary-color, #03a9f4)" />
<text x="${(E+4).toFixed(1)}" y="${(Le-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)" <text x="${(E+4).toFixed(1)}" y="${(Re-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)"
font-size="9" font-weight="600">R=${Gi}%</text> font-size="9" font-weight="600">R=${Ji}%</text>
`:u} `:u}
${t>0&&t!==e?pe` ${t>0&&t!==e?he`
<line x1="${At.toFixed(1)}" y1="${8}" x2="${At.toFixed(1)}" y2="${136 .toFixed(1)}" <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" /> stroke="var(--success-color, #4caf50)" stroke-width="1.5" stroke-dasharray="4,3" />
`:u} `: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} ${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} ${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> </div>
`}function Ys(a,s){return o` `}function er(a,s){return o`
<div class="weibull-info-row"> <div class="weibull-info-row">
<div class="weibull-info-item"> <div class="weibull-info-item">
<span>${r("characteristic_life",s)}</span> <span>${r("characteristic_life",s)}</span>
@@ -3171,22 +3334,22 @@ ${p?`<div class="sub">${p}</div>`:""}
</div> </div>
`:u} `:u}
</div> </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">
<div class="confidence-range-title"> <div class="confidence-range-title">
${r("confidence_interval",e)}: ${n} ${r("days",e)} (${t}\u2013${i}) ${r("confidence_interval",e)}: ${n} ${r("days",e)} (${t}\u2013${i})
</div> </div>
<div class="confidence-bar"> <div class="confidence-bar">
<div class="confidence-fill" style="left:${v.toFixed(1)}%;width:${m.toFixed(1)}%"></div> <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} ${y>=0?o`<div class="confidence-marker current" style="left:${y.toFixed(1)}%"></div>`:u}
<div class="confidence-marker recommended" style="left:${y.toFixed(1)}%"></div> <div class="confidence-marker recommended" style="left:${b.toFixed(1)}%"></div>
</div> </div>
<div class="confidence-labels"> <div class="confidence-labels">
<span class="confidence-text low">${r("confidence_conservative",e)} (${t}${r("days",e).charAt(0)})</span> <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> <span class="confidence-text high">${r("confidence_aggressive",e)} (${i}${r("days",e).charAt(0)})</span>
</div> </div>
</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"> <div class="prediction-section">
${a.sensor_prediction_urgency?o` ${a.sensor_prediction_urgency?o`
<div class="prediction-urgency-banner"> <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> <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-label">${r("environmental_adjustment",s)}</span>
<span class="prediction-value">${a.environmental_factor.toFixed(2)}x</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> </div>
`:u} `:u}
</div> </div>
</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-comparison">
<div class="interval-bar"> <div class="interval-bar">
<div class="interval-label"> <div class="interval-label">
@@ -3244,7 +3407,7 @@ ${p?`<div class="sub">${p}</div>`:""}
style="width: ${Math.min(s/i*100,100)}%"></div> style="width: ${Math.min(s/i*100,100)}%"></div>
</div> </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"> <div class="seasonal-card-compact">
<h4>${r("seasonal_awareness",s)}</h4> <h4>${r("seasonal_awareness",s)}</h4>
<div class="seasonal-mini-chart"> <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> <span class="legend-item"><span class="dot high"></span> ${r("longer",s)||"L\xE4nger"}</span>
</div> </div>
</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">
<div class="seasonal-chart-title"> <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> <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} ${t?o`<span class="source-tag">${t==="learned"?r("seasonal_learned",s):r("seasonal_manual",s)}</span>`:u}
</div> </div>
<svg viewBox="0 0 ${n} ${l}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${r("chart_seasonal",s)}"> <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" /> 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` ${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="${L.toFixed(1)}" y="${E.toFixed(1)}" <rect x="${P.toFixed(1)}" y="${E.toFixed(1)}"
width="${y.toFixed(1)}" height="${z.toFixed(1)}" width="${b.toFixed(1)}" height="${H.toFixed(1)}"
fill="${Le}" opacity="${N?1:.5}" rx="2" /> fill="${Re}" opacity="${N?1:.5}" rx="2" />
`})} `})}
</svg> </svg>
<div class="seasonal-labels"> <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>
</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"> <div class="recommendation-card">
<h4>${r("suggested_interval",t)}</h4> <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"> <div class="recommendation-actions">
<button class="btn primary" <button class="btn primary"
@click=${this._applySuggestion} ?disabled=${this._busy}> @click=${this._applySuggestion} ?disabled=${this._busy}>
@@ -3304,11 +3467,11 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="adaptive-stack"> <div class="adaptive-stack">
${this._toast?o`<div class="toast">${this._toast}</div>`:u} ${this._toast?o`<div class="toast">${this._toast}</div>`:u}
${i?this._renderRecommendation(e):u} ${i?this._renderRecommendation(e):u}
${n?Ri(e,t,this._features):u} ${n?Hi(e,t,this._features):u}
${l?Pi(e,t):u} ${l?Ni(e,t):u}
${d?o` ${d?o`
${Mi(e,t,this._features)} ${Fi(e,t,this._features)}
${e.seasonal_factors?.length===12||e.interval_analysis?.seasonal_factors?.length===12?Di(e,t):u} ${e.seasonal_factors?.length===12||e.interval_analysis?.seasonal_factors?.length===12?Ui(e,t):u}
`:u} `:u}
</div> </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` `}_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-entry">
<div class="history-line"> <div class="history-line">
<span class="history-type type-${p.type}">${r(p.type,t)}</span> <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" ${_?o`<button class="history-edit"
title="${r("history_edit_button",t)||"Edit"}" title="${r("history_edit_button",t)||"Edit"}"
@click=${()=>this._onEditHistoryEntry(p)}> @click=${()=>this._onEditHistoryEntry(p)}>
@@ -3361,7 +3524,7 @@ ${p?`<div class="sub">${p}</div>`:""}
${t?o` ${t?o`
<div class="header"> <div class="header">
<div class="title"> <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> <span class="task-name">${t.name}</span>
</div> </div>
<div class="object"> <div class="object">
@@ -3370,7 +3533,8 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="quick-info"> <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.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.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>
</div> </div>
@@ -3462,7 +3626,7 @@ ${p?`<div class="sub">${p}</div>`:""}
`} `}
`:o`<div class="loading">${r("loading",e)||"Loading\u2026"}</div>`} `:o`<div class="loading">${r("loading",e)||"Loading\u2026"}</div>`}
</div> </div>
`}};I.styles=[Fe,A` `}};I.styles=[Be,A`
:host { display: contents; } :host { display: contents; }
.backdrop { .backdrop {
position: fixed; inset: 0; z-index: 100; 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-section, .seasonal-card-compact { max-width: 100%; }
.weibull-chart svg { max-width: 100%; height: auto; } .weibull-chart svg { max-width: 100%; height: auto; }
.details-toggle { gap: 12px; flex-wrap: wrap; } .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="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true"> <div class="dialog" role="dialog" aria-modal="true">
${t&&i?o` ${t&&i?o`
@@ -3662,7 +3826,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="task-list"> <div class="task-list">
${n.map(d=>o` ${n.map(d=>o`
<div class="task-row" @click=${()=>this._onTaskClick(d.id)}> <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-name">${d.name}</span>
<span class="task-status">${r(d.status||"ok",e)}</span> <span class="task-status">${r(d.status||"ok",e)}</span>
</div> </div>
@@ -3705,7 +3869,7 @@ ${p?`<div class="sub">${p}</div>`:""}
${i.map(([n,l])=>o` ${i.map(([n,l])=>o`
<div class="meta-item"> <div class="meta-item">
<span class="meta-label">${n}</span> <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>
`)} `)}
</div> </div>
@@ -3768,7 +3932,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.btn ha-icon { --mdc-icon-size: 16px; } .btn ha-icon { --mdc-icon-size: 16px; }
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); } .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; } .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"> <div class="editor">
<ha-textfield <ha-textfield
label="${r("card_title",e)}" label="${r("card_title",e)}"
@@ -3780,7 +3944,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="field"> <div class="field">
<div class="field-label">${r("card_filter_status",e)}</div> <div class="field-label">${r("card_filter_status",e)}</div>
<div class="chip-row"> <div class="chip-row">
${Ps.map(f=>o` ${Hs.map(f=>o`
<label class="chip ${t.has(f)?"active":""}"> <label class="chip ${t.has(f)?"active":""}">
<input type="checkbox" <input type="checkbox"
.checked=${t.has(f)} .checked=${t.has(f)}
@@ -3827,11 +3991,11 @@ ${p?`<div class="sub">${p}</div>`:""}
</div> </div>
<div class="field-help">${r("card_filter_areas_help",e)}</div> <div class="field-help">${r("card_filter_areas_help",e)}</div>
</div>`:u} </div>`:u}
${y.length?o` ${b.length?o`
<div class="field"> <div class="field">
<div class="field-label">${r("labels",e)}</div> <div class="field-label">${r("labels",e)}</div>
<div class="object-list"> <div class="object-list">
${y.map(f=>o` ${b.map(f=>o`
<label class="object-row"> <label class="object-row">
<input type="checkbox" <input type="checkbox"
.checked=${v.has(f)} .checked=${v.has(f)}
@@ -3865,7 +4029,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.hass=${this.hass} .hass=${this.hass}
.value=${this._config.entity_ids||[]} .value=${this._config.entity_ids||[]}
.includeDomains=${["sensor","binary_sensor"]} .includeDomains=${["sensor","binary_sensor"]}
.includeEntities=${x} .includeEntities=${y}
@value-changed=${this._onEntitiesChanged} @value-changed=${this._onEntitiesChanged}
></ha-entities-picker> ></ha-entities-picker>
<div class="field-help">${r("card_filter_entities_help",e)}</div> <div class="field-help">${r("card_filter_entities_help",e)}</div>
@@ -4015,23 +4179,23 @@ ${p?`<div class="sub">${p}</div>`:""}
font-size: 14px; font-size: 14px;
max-width: 320px; 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" class="bf-spark"
viewBox="0 0 ${i} ${n}" viewBox="0 0 ${i} ${n}"
role="img" role="img"
aria-label=${r("battery_fleet_sparkline_hint",this._lang)} aria-label=${r("battery_fleet_sparkline_hint",this._lang)}
> >
<title>${r("battery_fleet_sparkline_hint",this._lang)}</title> <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> <polyline class="bf-spark-line" points=${f}></polyline>
${v!==null?o`<line ${v!==null?o`<line
class="bf-spark-proj" class="bf-spark-proj"
x1=${y(p).toFixed(1)} x1=${b(p).toFixed(1)}
y1=${x($).toFixed(1)} y1=${y($).toFixed(1)}
x2=${y(v).toFixed(1)} x2=${b(v).toFixed(1)}
y2=${z} y2=${H}
></line>`:u} ></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":""}" class="bf-type-chip ${this._typeFilter===t?"bf-type-chip-active":""}"
title=${r("battery_fleet_filter_type",this._lang)} title=${r("battery_fleet_filter_type",this._lang)}
@click=${()=>this._toggleTypeFilter(t)} @click=${()=>this._toggleTypeFilter(t)}
@@ -4202,7 +4366,7 @@ ${p?`<div class="sub">${p}</div>`:""}
`:u} `:u}
<div class="bf-total">${r("battery_fleet_total",e).replace("{n}",String(t.total))}</div> <div class="bf-total">${r("battery_fleet_total",e).replace("{n}",String(t.total))}</div>
</div> </div>
`}};q.styles=A` `}};O.styles=A`
.bf-card { .bf-card {
background: var(--card-background-color, #fff); background: var(--card-background-color, #fff);
border: 1px solid var(--divider-color); border: 1px solid var(--divider-color);
@@ -4590,20 +4754,20 @@ ${p?`<div class="sub">${p}</div>`:""}
accent-color: var(--primary-color); accent-color: var(--primary-color);
margin: 0; 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}> <ha-card .header=${this._config.title||void 0}>
<div class="content"> <div class="content">
<maintenance-battery-fleet-section flat .hass=${this.hass}></maintenance-battery-fleet-section> <maintenance-battery-fleet-section flat .hass=${this.hass}></maintenance-battery-fleet-section>
</div> </div>
</ha-card> </ha-card>
`:u}};_e.styles=A` `:u}};ge.styles=A`
ha-card { ha-card {
overflow: hidden; overflow: hidden;
} }
.content { .content {
padding: 12px 16px 14px; 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> <ha-card>
<div class="card-header"> <div class="card-header">
<h1>${t}</h1> <h1>${t}</h1>
@@ -4619,14 +4783,14 @@ ${p?`<div class="sub">${p}</div>`:""}
<mwc-icon-button <mwc-icon-button
class="hdr-add" class="hdr-add"
title="${r("new_object",e)}" title="${r("new_object",e)}"
@click=${()=>kt()} @click=${()=>At()}
> >
<ha-icon icon="mdi:plus-box"></ha-icon> <ha-icon icon="mdi:plus-box"></ha-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-icon-button <mwc-icon-button
class="hdr-add" class="hdr-add"
title="${r("add_task",e)}" title="${r("add_task",e)}"
@click=${()=>Et("",this._objects)} @click=${()=>Tt("",this._objects)}
> >
<ha-icon icon="mdi:playlist-plus"></ha-icon> <ha-icon icon="mdi:playlist-plus"></ha-icon>
</mwc-icon-button> </mwc-icon-button>
@@ -4646,7 +4810,7 @@ ${p?`<div class="sub">${p}</div>`:""}
<div class="task-item clickable" <div class="task-item clickable"
@click=${()=>this._openTaskDetail(_,m.id)} @click=${()=>this._openTaskDetail(_,m.id)}
title="${r("open_task",e)||"Open task"}"> 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-info">
<div class="task-name"> <div class="task-name">
${m.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)}"> ${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)} <ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(m)}
</div>`:u:o`<div class="task-meta"> </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 ><ha-icon icon="mdi:account"></ha-icon>${this._assigneeName(m)}</span
>`:u} >`:u}
</div>`} </div>`}
</div> </div>
${this._docsFor(_,m).length?o`<div class="doc-chips"> ${this._docsFor(_,m).length?o`<div class="doc-chips">
${this._docsFor(_,m).map(y=>o` ${this._docsFor(_,m).map(b=>o`
<button <button
type="button" type="button"
class="doc-chip" class="doc-chip"
title="${y.title}" title="${b.title}"
@click=${x=>{x.stopPropagation(),this._openDoc(y)}} @click=${y=>{y.stopPropagation(),this._openDoc(b)}}
> >
<ha-icon icon=${y.kind==="weblink"?"mdi:link-variant":"mdi:file-document-outline"}></ha-icon> <ha-icon icon=${b.kind==="weblink"?"mdi:link-variant":"mdi:file-document-outline"}></ha-icon>
<span>${y.title}</span> <span>${b.title}</span>
</button> </button>
`)} `)}
</div>`:u} </div>`:u}
<div class="task-due"> <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> </div>
${n?o` ${n?o`
<mwc-icon-button <mwc-icon-button
class="complete-btn" class="complete-btn"
title="${r("complete",e)}" 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> <ha-icon icon="mdi:check"></ha-icon>
</mwc-icon-button> </mwc-icon-button>
@@ -4698,7 +4862,7 @@ ${p?`<div class="sub">${p}</div>`:""}
.hass=${this.hass} .hass=${this.hass}
@task-completed=${this._onCompleted} @task-completed=${this._onCompleted}
></maintenance-complete-dialog> ></maintenance-complete-dialog>
`}};D.styles=[Fe,A` `}};z.styles=[Be,A`
ha-card { overflow: hidden; } ha-card { overflow: hidden; }
.card-header { .card-header {
@@ -4830,7 +4994,7 @@ ${p?`<div class="sub">${p}</div>`:""}
--mdc-icon-size: 18px; --mdc-icon-size: 18px;
color: var(--primary-color); 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: /*! Bundled license information:
@lit/reactive-element/css-tag.js: @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 */ /*! maintenance_supporter frontend 2.65.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{}})(); 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 */ /*! maintenance_supporter frontend 2.65.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 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> <style>
:host, .editor { display: block; padding: 16px 0; } :host, .editor { display: block; padding: 16px 0; }
label { display: block; font-weight: 500; margin-bottom: 8px; } 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. The "Overview" view is always first. Empty groups are skipped.
</div> </div>
</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": [ "requirements": [
"pypdf>=4.3.0" "pypdf>=4.3.0"
], ],
"version": "2.64.0" "version": "2.65.0"
} }
@@ -62,6 +62,14 @@ class MaintenanceTask:
# --- Trigger --- # --- Trigger ---
trigger_config: dict[str, Any] | None = None 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 --- # --- Metadata ---
notes: str | None = None notes: str | None = None
documentation_url: str | None = None documentation_url: str | None = None
@@ -136,6 +144,17 @@ class MaintenanceTask:
due_override=parse_iso_date(self.due_override), 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: def _planned_grid_due(self) -> date | None:
"""``next_due`` WITHOUT the postpone override — the drift-free grid. """``next_due`` WITHOUT the postpone override — the drift-free grid.
@@ -375,6 +394,17 @@ class MaintenanceTask:
anchors.append(self.last_performed) anchors.append(self.last_performed)
is_latest = ts_iso >= max(anchors, default="") 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: if is_latest:
# Save the PLANNED grid date as the anchor before resetting — not # Save the PLANNED grid date as the anchor before resetting — not
# next_due, which returns a postpone override and would shift the # next_due, which returns a postpone override and would shift the
@@ -404,6 +434,7 @@ class MaintenanceTask:
used_parts=used_parts, used_parts=used_parts,
auto=auto, auto=auto,
timestamp=ts_iso, timestamp=ts_iso,
phase_id=completed_phase_id,
) )
# Shared tasks: rotate the "currently responsible" pointer to the next # Shared tasks: rotate the "currently responsible" pointer to the next
@@ -508,6 +539,7 @@ class MaintenanceTask:
used_parts: list[dict[str, Any]] | None = None, used_parts: list[dict[str, Any]] | None = None,
auto: bool = False, auto: bool = False,
timestamp: str | None = None, timestamp: str | None = None,
phase_id: str | None = None,
) -> None: ) -> None:
"""Add an entry to the maintenance history. """Add an entry to the maintenance history.
@@ -520,6 +552,10 @@ class MaintenanceTask:
} }
if auto: if auto:
entry["auto"] = True 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: if notes is not None:
entry["notes"] = notes entry["notes"] = notes
if cost is not None: if cost is not None:
@@ -630,6 +666,13 @@ class MaintenanceTask:
data["checklist"] = self.checklist data["checklist"] = self.checklist
if self.adaptive_config is not None: if self.adaptive_config is not None:
data["adaptive_config"] = self.adaptive_config 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 return data
@classmethod @classmethod
@@ -642,6 +685,9 @@ class MaintenanceTask:
``task.interval_days`` etc. is unaffected. ``task.interval_days`` etc. is unaffected.
""" """
sched = read_legacy_fields(data) 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( return cls(
id=data.get("id", uuid4().hex), id=data.get("id", uuid4().hex),
object_id=data.get("object_id", ""), object_id=data.get("object_id", ""),
@@ -677,4 +723,7 @@ class MaintenanceTask:
checklist=data.get("checklist", []), checklist=data.get("checklist", []),
adaptive_config=data.get("adaptive_config"), adaptive_config=data.get("adaptive_config"),
history=data.get("history", []), 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