diff --git a/.ha_run.lock b/.ha_run.lock index 4fb57c3b..daf72f8c 100644 --- a/.ha_run.lock +++ b/.ha_run.lock @@ -1 +1 @@ -{"pid": 71, "version": 1, "ha_version": "2026.8.3", "start_ts": 1787776418.1303492} \ No newline at end of file +{"pid": 72, "version": 1, "ha_version": "2026.8.3", "start_ts": 1787864016.8899596} \ No newline at end of file diff --git a/.storage/lovelace_resources b/.storage/lovelace_resources index a2c6ca53..8e29d456 100644 --- a/.storage/lovelace_resources +++ b/.storage/lovelace_resources @@ -81,117 +81,117 @@ }, { "id": "88b90f2b1d6b45f0b44bee786ee1a525", - "url": "/taskmate/taskmate-attr-resolver.js?v=5.3.0", + "url": "/taskmate/taskmate-attr-resolver.js?v=5.4.1", "type": "module" }, { "id": "0796c92931dd458e81dc9c2677248274", - "url": "/taskmate/taskmate-localize.js?v=5.3.0", + "url": "/taskmate/taskmate-localize.js?v=5.4.1", "type": "module" }, { "id": "d2e281920ac7409583eafff1f1db69bd", - "url": "/taskmate/taskmate-design.js?v=5.3.0", + "url": "/taskmate/taskmate-design.js?v=5.4.1", "type": "module" }, { "id": "9eae9632f0734480be5edd784b030673", - "url": "/taskmate/taskmate-badges-card.js?v=5.3.0", + "url": "/taskmate/taskmate-badges-card.js?v=5.4.1", "type": "module" }, { "id": "5df49cd8a4eb420497b5df22cb31362c", - "url": "/taskmate/taskmate-child-card.js?v=5.3.0", + "url": "/taskmate/taskmate-child-card.js?v=5.4.1", "type": "module" }, { "id": "b44c87d0d74f468cb96c5eabc340f9ee", - "url": "/taskmate/taskmate-rewards-card.js?v=5.3.0", + "url": "/taskmate/taskmate-rewards-card.js?v=5.4.1", "type": "module" }, { "id": "787a96f9c30548708165a48457c99feb", - "url": "/taskmate/taskmate-approvals-card.js?v=5.3.0", + "url": "/taskmate/taskmate-approvals-card.js?v=5.4.1", "type": "module" }, { "id": "84031597de424ad68e7c2bc66d06b72e", - "url": "/taskmate/taskmate-points-card.js?v=5.3.0", + "url": "/taskmate/taskmate-points-card.js?v=5.4.1", "type": "module" }, { "id": "02613225844746b699bfbfac9642eca2", - "url": "/taskmate/taskmate-reorder-card.js?v=5.3.0", + "url": "/taskmate/taskmate-reorder-card.js?v=5.4.1", "type": "module" }, { "id": "7a06a07e17ba4b31b712bc91b77360b4", - "url": "/taskmate/taskmate-overview-card.js?v=5.3.0", + "url": "/taskmate/taskmate-overview-card.js?v=5.4.1", "type": "module" }, { "id": "08b42b53b33b4fa88db932455cf8d5aa", - "url": "/taskmate/taskmate-activity-card.js?v=5.3.0", + "url": "/taskmate/taskmate-activity-card.js?v=5.4.1", "type": "module" }, { "id": "15907eff51784acdba88ff694b574509", - "url": "/taskmate/taskmate-streak-card.js?v=5.3.0", + "url": "/taskmate/taskmate-streak-card.js?v=5.4.1", "type": "module" }, { "id": "1bd6bb5c0a6d4f0d916e3d82cb7fac39", - "url": "/taskmate/taskmate-weekly-card.js?v=5.3.0", + "url": "/taskmate/taskmate-weekly-card.js?v=5.4.1", "type": "module" }, { "id": "289ce5943f5a4972a593697db4c96ece", - "url": "/taskmate/taskmate-graph-card.js?v=5.3.0", + "url": "/taskmate/taskmate-graph-card.js?v=5.4.1", "type": "module" }, { "id": "d2904acee4c74a398dcc7b996c23e7e7", - "url": "/taskmate/taskmate-reward-progress-card.js?v=5.3.0", + "url": "/taskmate/taskmate-reward-progress-card.js?v=5.4.1", "type": "module" }, { "id": "46d378736fd54a26be253f2dfb27202e", - "url": "/taskmate/taskmate-leaderboard-card.js?v=5.3.0", + "url": "/taskmate/taskmate-leaderboard-card.js?v=5.4.1", "type": "module" }, { "id": "db11ae36457842fa826c7991e1d9c6f4", - "url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.3.0", + "url": "/taskmate/taskmate-parent-dashboard-card.js?v=5.4.1", "type": "module" }, { "id": "ce85f301b59a412e9abfe5ffd51cb9a2", - "url": "/taskmate/taskmate-penalties-card.js?v=5.3.0", + "url": "/taskmate/taskmate-penalties-card.js?v=5.4.1", "type": "module" }, { "id": "1959e1629de2422daa8adaf87069c288", - "url": "/taskmate/taskmate-bonuses-card.js?v=5.3.0", + "url": "/taskmate/taskmate-bonuses-card.js?v=5.4.1", "type": "module" }, { "id": "2548524b269c4959bc7e0e967df943af", - "url": "/taskmate/taskmate-points-display-card.js?v=5.3.0", + "url": "/taskmate/taskmate-points-display-card.js?v=5.4.1", "type": "module" }, { "id": "36ada49368db4187a283fbbbef45454f", - "url": "/taskmate/taskmate-calendar-card.js?v=5.3.0", + "url": "/taskmate/taskmate-calendar-card.js?v=5.4.1", "type": "module" }, { "id": "67c32687acd2497b90030ef141f4b6db", - "url": "/taskmate/taskmate-photo-gallery-card.js?v=5.3.0", + "url": "/taskmate/taskmate-photo-gallery-card.js?v=5.4.1", "type": "module" }, { "id": "6f0c8348a91f4b74a9b846ab3f62621f", - "url": "/taskmate/taskmate-family-goal-card.js?v=5.3.0", + "url": "/taskmate/taskmate-family-goal-card.js?v=5.4.1", "type": "module" }, { @@ -276,7 +276,7 @@ }, { "id": "73dbd2de03de4530b451a55bfe6c46cf", - "url": "/taskmate/taskmate-routine-card.js?v=5.3.0", + "url": "/taskmate/taskmate-routine-card.js?v=5.4.1", "type": "module" }, { @@ -315,7 +315,7 @@ "type": "module" }, { - "id": "9227b25fc2ea4cb5be22e4436912268a", + "id": "17e81c8801114b2d941a7d5f22eb47e6", "url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1", "type": "module" } diff --git a/custom_components/maintenance_supporter/calendar.py b/custom_components/maintenance_supporter/calendar.py index 368e4c2f..797e542c 100644 --- a/custom_components/maintenance_supporter/calendar.py +++ b/custom_components/maintenance_supporter/calendar.py @@ -564,6 +564,12 @@ async def async_setup_entry( _LOGGER.debug("Maintenance calendar entity created") + +def _task_label(task: MaintenanceTask) -> str: + """Task name, with the due cycle phase appended for phased tasks (#139).""" + phase = task.current_phase_name + return f"{task.name} · {phase}" if phase else task.name + class MaintenanceCalendar(CalendarEntity): """Calendar entity aggregating all maintenance tasks.""" @@ -713,7 +719,7 @@ class MaintenanceCalendar(CalendarEntity): today = dt_util.now().date() if start_d <= today <= end_d: return CalendarEvent( - summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {task.name} ({object_name})", + summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {_task_label(task)} ({object_name})", start=today, end=today + timedelta(days=1), description=_cal_t("manually_triggered", lang), @@ -728,7 +734,7 @@ class MaintenanceCalendar(CalendarEntity): today = dt_util.now().date() if start_d <= today <= end_d: return CalendarEvent( - summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {task.name} ({object_name})", + summary=f"{STATUS_PREFIX.get(MaintenanceStatus.TRIGGERED, '🔔')} {_task_label(task)} ({object_name})", start=today, end=today + timedelta(days=1), description=_cal_t("sensor_triggered", lang, name=task.name), @@ -770,7 +776,7 @@ class MaintenanceCalendar(CalendarEntity): pass # malformed schedule_time → fall back to all-day return CalendarEvent( - summary=f"{prefix} {task.name} ({object_name})", + summary=f"{prefix} {_task_label(task)} ({object_name})", start=start, end=end, description=( diff --git a/custom_components/maintenance_supporter/config_flow_options_task_base.py b/custom_components/maintenance_supporter/config_flow_options_task_base.py index a7acae73..2cf527cc 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_base.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_base.py @@ -143,6 +143,9 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow): menu.append("remove_trigger") if global_opts.get(CONF_ADVANCED_CHECKLISTS, False): menu.append("edit_checklist") + # #139: cycle phases are not feature-gated (the panel editor isn't + # either) — the minimal flow editor is always reachable. + menu.append("edit_phases") if global_opts.get(CONF_ADVANCED_ADAPTIVE, False): menu.append("adaptive_scheduling") menu.extend(["delete_task", "manage_tasks"]) diff --git a/custom_components/maintenance_supporter/config_flow_options_task_crud.py b/custom_components/maintenance_supporter/config_flow_options_task_crud.py index a33ccd24..eec2d601 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_crud.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_crud.py @@ -539,6 +539,73 @@ class TaskCrudMixin: }, ) + async def async_step_edit_phases(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: + """Minimal cycle-phase editor (#139): one textarea, one line per + sequence step, optional per-phase checklist as "Name: item; item". + + Names matching existing phases keep their ids (history phase_id and + the Store cursor stay stable) AND their panel-only overrides (parts / + required fields). Sanitizing + storage go through the SAME + ``_apply_phase_fields`` as the WS write path, and the Store cursor is + clamped the same way ws_update_task clamps it. + """ + from .helpers.phases import clamp_phase_cursor, parse_phases_text, phases_to_text + from .websocket.tasks_crud import _apply_phase_fields + + tasks_data = self.config_entry.data.get(CONF_TASKS, {}) + task = tasks_data.get(self._selected_task_id or "", {}) + + if user_input is not None: + if user_input.get("go_back"): + return self._show_task_action_menu() + + raw_defs, raw_seq = parse_phases_text( + user_input.get("phases_text", ""), task.get("phases") or {} + ) + new_data = dict(self.config_entry.data) + new_tasks = dict(new_data.get(CONF_TASKS, {})) + updated_task = dict(new_tasks.get(self._selected_task_id or "", {})) + _apply_phase_fields(self.hass, self.config_entry, updated_task, raw_defs, raw_seq) + new_tasks[self._selected_task_id or ""] = updated_task + new_data[CONF_TASKS] = new_tasks + self._update_config_entry(new_data) + + # Clamp/clear the Store cursor against the edited sequence — + # the Store wins on restore, so an out-of-range cursor would + # otherwise survive this edit forever (same block as ws_update). + rd = getattr(self.config_entry, "runtime_data", None) + store = getattr(rd, "store", None) + if store is not None and self._selected_task_id: + seq = updated_task.get("phase_sequence") or [] + if seq: + cur = store.get_task_state(self._selected_task_id).get("phase_cursor", 0) + store.set_phase_cursor(self._selected_task_id, clamp_phase_cursor(cur, len(seq))) + else: + store.get_task_state(self._selected_task_id).pop("phase_cursor", None) + store.async_delay_save() + + return self._show_task_action_menu() + + default_text = phases_to_text(task.get("phases") or {}, task.get("phase_sequence") or []) + + return self.async_show_form( + step_id="edit_phases", + data_schema=vol.Schema( + { + vol.Optional("phases_text", default=default_text): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT, + multiline=True, + ) + ), + vol.Optional("go_back", default=False): selector.BooleanSelector(), + } + ), + description_placeholders={ + "task_name": task.get("name", ""), + }, + ) + async def async_step_delete_task(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult: """Confirm and delete a task.""" tasks_data = self.config_entry.data.get(CONF_TASKS, {}) diff --git a/custom_components/maintenance_supporter/coordinator.py b/custom_components/maintenance_supporter/coordinator.py index 6f178e3e..4d02afcf 100644 --- a/custom_components/maintenance_supporter/coordinator.py +++ b/custom_components/maintenance_supporter/coordinator.py @@ -59,6 +59,16 @@ from .storage import MaintenanceStore _LOGGER = logging.getLogger(__name__) +def _notify_task_label(task: dict[str, Any]) -> str: + """Task name for notifications — phased tasks (#139) name the due step, + so "Mower blades · Replace blades" tells the user what the work IS.""" + from .helpers.phases import current_phase + + name = task.get("name", "") + phase = current_phase(task) + return f"{name} · {phase['name']}" if phase else name + + def _inert_task_result(task: MaintenanceTask, status: str, **extra: Any) -> dict[str, Any]: """Coordinator payload for a task that gets NO live evaluation (archived / paused): due fields nulled, trigger off, only cost/history-derived fields @@ -744,7 +754,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): tasks=[ { "task_id": tid, - "task_name": tr.get("name", ""), + "task_name": _notify_task_label(tr), "status": status, "days_until_due": tr.get("_days_until_due"), } @@ -756,7 +766,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): await nm.async_task_status_changed( entry_id=self.entry.entry_id, task_id=task_id, - task_name=task_result.get("name", ""), + task_name=_notify_task_label(task_result), object_name=obj_name, new_status=new_status, days_until_due=task_result.get("_days_until_due"), @@ -862,6 +872,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): state["due_override"] = do elif "due_override" in state: del state["due_override"] + # Phase cursor (#139): to_dict emits it only for phase-carrying tasks. + pc = td.get("phase_cursor") + if pc is not None: + self._store.set_phase_cursor(task_id, pc) self._store.set_history(task_id, td.get("history", [])) if task.adaptive_config: self._store.set_adaptive_config(task_id, task.adaptive_config) @@ -1061,8 +1075,10 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # will consume below — go on the record, so completions from the # no-dialog surfaces (service, button, QR, to-do, voice, recovery) # stay correctable through the history editor like dialog ones. + from .helpers.phases import effective_field as _phase_effective + record_links = ( - used_parts if used_parts is not None else (merged[task_id].get("consumes_parts") or []) + used_parts if used_parts is not None else (_phase_effective(merged[task_id], "consumes_parts") or []) ) enriched_used: list[dict[str, Any]] | None = None if used_parts is not None or record_links: diff --git a/custom_components/maintenance_supporter/export.py b/custom_components/maintenance_supporter/export.py index c978aad6..cc01f3b5 100644 --- a/custom_components/maintenance_supporter/export.py +++ b/custom_components/maintenance_supporter/export.py @@ -134,6 +134,12 @@ def _build_export_object( "required_completion_fields": tdata.get("required_completion_fields"), "rotation_strategy": tdata.get("rotation_strategy"), "reading_unit": tdata.get("reading_unit"), + # Task phases (#139): defs + cycle are static config; the cursor is + # Store-merged by merged_tasks, so a restore resumes mid-cycle + # instead of silently restarting at step one. + "phases": tdata.get("phases"), + "phase_sequence": tdata.get("phase_sequence"), + "phase_cursor": tdata.get("phase_cursor"), # Spare parts: consumption links + the auto-buy-task marker. "consumes_parts": tdata.get("consumes_parts"), "part_ref": tdata.get("part_ref"), diff --git a/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts index 2dba9d55..0c3de694 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts @@ -40,6 +40,9 @@ export class MaintenanceCompleteDialog extends LitElement { * enforces the same list at every completion surface; blocking Save here * just means the user never has to meet that rejection. */ @property({ type: Array }) public requiredFields: string[] = []; + /** #139: "2/4 · Flip blades" — which phase of the cycle this completion + * records. Purely informative; the backend advances the cursor itself. */ + @property() public phaseLabel = ""; @state() private _open = false; @state() private _notes = ""; @state() private _cost = ""; @@ -286,6 +289,7 @@ export class MaintenanceCompleteDialog extends LitElement { return html`
${t("complete_title", L)}${this.taskName}
+ ${this.phaseLabel ? html`
${t("phase_current", L)}: ${this.phaseLabel}
` : nothing}
${this._error ? html`
${this._error}
` : nothing} ${this.checklist.length > 0 ? html` @@ -473,6 +477,12 @@ export class MaintenanceCompleteDialog extends LitElement { font-weight: 500; padding-bottom: 12px; } + .phase-line { + margin-top: -8px; + padding-bottom: 12px; + font-size: 13px; + color: var(--secondary-text-color); + } .content { display: flex; flex-direction: column; diff --git a/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts index d26010cb..47fcf158 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts @@ -260,6 +260,18 @@ export class MaintenanceTaskDialog extends LitElement { // Checklist (newline-separated steps, one per line) @state() private _checklistText = ""; + // Task phases (#139): editable defs + the cycle order. The v1 editor keeps + // each phase to name + checklist + at most ONE own-object part (the mower + // "14 new blades" case); richer per-phase config exists at the WS level. + // `carry` holds def fields the editor has no form for (notes, + // required_completion_fields, future keys) and `extraParts` any links + // beyond the first — both re-merged on save so a no-op edit never wipes + // them (#106 pattern; same passthrough idea as trigger_config's carry). + @state() private _phaseDefs: Array<{ + id: string; name: string; checklistText: string; partId: string; partQty: string; + extraParts: TaskPartLink[]; carry: Record; + }> = []; + @state() private _phaseSeq: string[] = []; /** Details this task demands on completion — enforced by the backend on * every surface, so a button press or voice command cannot bypass it. */ @state() private _requiredCompletion: string[] = []; @@ -388,6 +400,19 @@ export class MaintenanceTaskDialog extends LitElement { this._rotationStrategy = task.rotation_strategy || ""; this._checklistText = (task.checklist || []).join("\n"); + this._phaseDefs = Object.entries(task.phases || {}).map(([id, def]) => { + const { name: _n, checklist: _c, consumes_parts: _p, ...carry } = def as unknown as Record; + return { + id, + name: def.name || id, + checklistText: (def.checklist || []).join("\n"), + partId: def.consumes_parts?.[0]?.part_id || "", + partQty: def.consumes_parts?.[0]?.quantity != null ? String(def.consumes_parts[0].quantity) : "", + extraParts: (def.consumes_parts || []).slice(1), + carry, + }; + }); + this._phaseSeq = [...(task.phase_sequence || [])]; this._requiredCompletion = [...(task.required_completion_fields || [])]; this._scheduleTime = task.schedule_time || ""; @@ -506,6 +531,8 @@ export class MaintenanceTaskDialog extends LitElement { this._assigneePool = []; this._rotationStrategy = ""; this._checklistText = ""; + this._phaseDefs = []; + this._phaseSeq = []; this._requiredCompletion = []; this._scheduleTime = ""; this._environmentalEntity = ""; @@ -993,6 +1020,103 @@ export class MaintenanceTaskDialog extends LitElement { this._requiredCompletion = [...next]; } + // ── Task phases (#139) ─────────────────────────────────────────────── + + private _phaseSlug(name: string): string { + const base = name.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 24) || "phase"; + let slug = base; + let n = 2; + while (this._phaseDefs.some((d) => d.id === slug)) slug = `${base}-${n++}`; + return slug; + } + + private _addPhaseDef(): void { + const id = this._phaseSlug(`phase-${this._phaseDefs.length + 1}`); + this._phaseDefs = [...this._phaseDefs, { id, name: "", checklistText: "", partId: "", partQty: "", extraParts: [], carry: {} }]; + } + + private _removePhaseDef(id: string): void { + this._phaseDefs = this._phaseDefs.filter((d) => d.id !== id); + this._phaseSeq = this._phaseSeq.filter((s) => s !== id); + } + + private _patchPhaseDef(id: string, patch: Partial<{ name: string; checklistText: string; partId: string; partQty: string }>): void { + this._phaseDefs = this._phaseDefs.map((d) => (d.id === id ? { ...d, ...patch } : d)); + } + + private _renderPhasesEditor(L: string) { + const defName = (id: string) => this._phaseDefs.find((d) => d.id === id)?.name || id; + return html` +

${t("phases_section", L)}

+
${t("phases_hint", L)}
+ ${this._phaseDefs.map((d) => html` +
+
+ this._patchPhaseDef(d.id, { name: (e.target as HTMLInputElement).value })} + > + ${this.parts.length ? html` + + ${d.partId ? html` + this._patchPhaseDef(d.id, { partQty: (e.target as HTMLInputElement).value })} /> + ` : nothing} + ` : nothing} + this._removePhaseDef(d.id)}> + + +
+ ${this.checklistsEnabled ? html` + + ` : nothing} +
+ `)} + + ${t("phase_add", L)} + + ${this._phaseDefs.some((d) => d.name.trim()) ? html` +
${t("phase_sequence_label", L)}
+
+ ${this._phaseSeq.map((id, i) => html` + + ${i + 1}. ${defName(id)} + + + `)} + +
+ ` : nothing} + `; + } + private async _save(): Promise { if (this._loading) return; // synchronous re-entry guard (double-click) if (!this._name.trim()) return; @@ -1073,6 +1197,27 @@ export class MaintenanceTaskDialog extends LitElement { data.last_performed = this._lastPerformed || null; data.nfc_tag_id = this._nfcTagId || null; data.reading_unit = this._readingUnit.trim() || null; + // Task phases (#139): always sent — null clears a removed cycle. + { + const defs: Record = {}; + for (const d of this._phaseDefs) { + if (!d.name.trim()) continue; + const def: Record = { ...d.carry, name: d.name.trim() }; + const items = d.checklistText.split("\n").map((s) => s.trim()).filter(Boolean); + if (items.length) def.checklist = items; + const links: TaskPartLink[] = []; + if (d.partId) { + const qty = parseFloat(d.partQty); + links.push({ part_id: d.partId, quantity: Number.isFinite(qty) && qty > 0 ? qty : 1 }); + } + links.push(...d.extraParts); + if (links.length) def.consumes_parts = links; + defs[d.id] = def; + } + const seq = this._phaseSeq.filter((id) => id in defs); + data.phases = Object.keys(defs).length && seq.length ? defs : null; + data.phase_sequence = data.phases ? seq : null; + } // Only send when a picker was actually rendered. A failed parts load // leaves both lists empty, and sending [] then would silently wipe links // the user never saw. entry_id is written ONLY for a foreign pick, so an @@ -2463,6 +2608,7 @@ export class MaintenanceTaskDialog extends LitElement { >
${t("checklist_help", L)}
` : nothing} + ${this._renderPhasesEditor(L)}

${t("require_on_completion", L)}

${REQUIRED_COMPLETION_KEYS.map((field) => html` @@ -2742,6 +2888,82 @@ export class MaintenanceTaskDialog extends LitElement { gap: 8px; padding: 2px 0; } + .phase-def { + border: 1px solid var(--divider-color); + border-radius: 6px; + padding: 8px; + margin: 6px 0; + } + .phase-def-head { + display: flex; + align-items: center; + gap: 8px; + } + .phase-def-head ms-textfield { + flex: 1; + min-width: 0; + } + .phase-part { + max-width: 160px; + padding: 6px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + .phase-qty { + width: 64px; + padding: 6px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + .phase-checklist { + min-height: 56px; + margin-top: 6px; + } + .phase-remove { + --mdc-icon-button-size: 36px; + color: var(--secondary-text-color); + } + .phase-seq-label { + font-size: 12px; + color: var(--secondary-text-color); + margin-top: 8px; + } + .phase-seq { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 4px 0; + } + .phase-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 12px; + background: var(--secondary-background-color); + font-size: 13px; + } + .phase-chip-x { + border: none; + background: none; + color: var(--secondary-text-color); + cursor: pointer; + padding: 0 2px; + font-size: 12px; + } + .phase-seq-add { + padding: 4px 8px; + border: 1px dashed var(--divider-color); + border-radius: 12px; + background: transparent; + color: var(--secondary-text-color); + font-size: 13px; + } .consumes-check { display: flex; align-items: center; diff --git a/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts index d145b38a..f1227a83 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts @@ -18,6 +18,7 @@ import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInter import { describeWsError } from "../ws-errors"; import { isoDateLocal } from "../helpers/calendar-bucket"; import { partsForCompletion } from "../helpers/shared-parts"; +import { effectivePhase, phaseLabel } from "../helpers/phases"; import { renderWeibullSection } from "../renderers/weibull"; import { renderPredictionSection } from "../renderers/prediction"; import { renderRecommendationBars } from "../renderers/recommendation"; @@ -158,13 +159,17 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { import("../dialog-mount").then(async ({ openCompleteDialog }) => { const task = this._task!; const isBuy = !!(task as { part_ref?: string }).part_ref; + // #139: a phased task completes the phase currently due, so the phase's + // checklist/parts/required fields replace the task-level ones here. + const phase = effectivePhase(task); + const links = phase ? phase.consumesParts : (task.consumes_parts || []); let parts: Parameters[0]["parts"] = []; if (!isBuy) { try { const r = await this.hass.connection.sendMessagePromise<{ objects: MaintenanceObjectResponse[]; }>({ type: "maintenance_supporter/objects", compact: true }); - parts = partsForCompletion(task, this._entryId!, r.objects || [], this._lang); + parts = partsForCompletion({ consumes_parts: links }, this._entryId!, r.objects || [], this._lang); } catch { // Parts stay empty — the dialog still completes without them. } @@ -173,13 +178,14 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { entry_id: this._entryId!, task_id: this._taskId!, task_name: task.name, - checklist: task.checklist || [], + checklist: phase ? phase.checklist : (task.checklist || []), adaptive_enabled: !!task.adaptive_config?.enabled, - required_completion_fields: task.required_completion_fields || [], + required_completion_fields: phase ? phase.requiredFields : (task.required_completion_fields || []), task_type: task.type || "", reading_unit: (task as { reading_unit?: string }).reading_unit || "", parts, - consumes_parts: isBuy ? [] : (task.consumes_parts || []), + consumes_parts: isBuy ? [] : links, + phase_label: phaseLabel(task), }); if (ok) { this._notifyChanged("complete"); @@ -542,6 +548,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { ${(task.schedule?.kind && !["manual", "one_time"].includes(task.schedule.kind)) || task.interval_days != null ? html`${t("interval", L) || "Interval"}: ${formatRecurrence(task, L)}` : nothing} + ${phaseLabel(task) + ? html`${t("phase_current", L)}: ${phaseLabel(task)}` + : nothing}
diff --git a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts index 405f2450..588c37e8 100644 --- a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts +++ b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts @@ -289,6 +289,8 @@ export function openCompleteDialog(args: { /** #99/#111: per-completion parts selection incl. shared pools. */ parts?: MaintenanceCompleteDialog["parts"]; consumes_parts?: MaintenanceCompleteDialog["consumesParts"]; + /** #139: "2/4 · Flip blades" — the phase this completion records. */ + phase_label?: string; }): boolean { const dlg = getOrCreate(COMPLETE_DIALOG_TAG); if (!syncHass(dlg)) return false; @@ -304,6 +306,7 @@ export function openCompleteDialog(args: { dlg.readingUnit = args.reading_unit ?? ""; dlg.parts = args.parts ?? []; dlg.consumesParts = args.consumes_parts ?? []; + dlg.phaseLabel = args.phase_label ?? ""; dlg.lang = (getHass()?.language) || "en"; dlg.open(); return true; diff --git a/custom_components/maintenance_supporter/frontend-src/locales/cs.json b/custom_components/maintenance_supporter/frontend-src/locales/cs.json index da868733..ba28dfd8 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/cs.json @@ -889,5 +889,14 @@ "service_record_entries": "záznamů", "completed_by": "Dokončil", "date_from": "Od", - "date_to": "Do" + "date_to": "Do", + "phases_section": "Fáze cyklu (volitelné)", + "phases_hint": "Různé práce ve stejném rytmu — každé dokončení posune na další krok (např. malý, malý, velký servis).", + "phase_add": "Přidat fázi", + "phase_name": "Název fáze", + "phase_sequence_label": "Pořadí cyklu", + "phase_sequence_add_step": "Přidat krok", + "phase_current": "Aktuální fáze", + "phase_set": "Nastavit jako aktuální", + "chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/da.json b/custom_components/maintenance_supporter/frontend-src/locales/da.json index c89c96ee..679e05e8 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/da.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/da.json @@ -889,5 +889,14 @@ "service_record_entries": "poster", "completed_by": "Udført af", "date_from": "Fra", - "date_to": "Til" + "date_to": "Til", + "phases_section": "Cyklusfaser (valgfrit)", + "phases_hint": "Forskelligt arbejde i samme rytme — hver udførelse går videre til næste trin (f.eks. lille, lille, stor service).", + "phase_add": "Tilføj fase", + "phase_name": "Fasenavn", + "phase_sequence_label": "Cyklusrækkefølge", + "phase_sequence_add_step": "Tilføj trin", + "phase_current": "Aktuel fase", + "phase_set": "Sæt som aktuel", + "chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/de.json b/custom_components/maintenance_supporter/frontend-src/locales/de.json index ed01d558..da16fa37 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/de.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/de.json @@ -889,5 +889,14 @@ "service_record_entries": "Einträge", "completed_by": "Erledigt von", "date_from": "Von", - "date_to": "Bis" + "date_to": "Bis", + "phases_section": "Zyklus-Phasen (optional)", + "phases_hint": "Unterschiedliche Arbeiten im gleichen Takt — jede Erledigung schaltet zum nächsten Schritt (z. B. klein, klein, groß).", + "phase_add": "Phase hinzufügen", + "phase_name": "Phasenname", + "phase_sequence_label": "Zyklus-Reihenfolge", + "phase_sequence_add_step": "Schritt anhängen", + "phase_current": "Aktuelle Phase", + "phase_set": "Als aktuell setzen", + "chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/en.json b/custom_components/maintenance_supporter/frontend-src/locales/en.json index 9de226a5..38ba33c0 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/en.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/en.json @@ -889,5 +889,14 @@ "service_record_entries": "entries", "completed_by": "Completed by", "date_from": "From", - "date_to": "To" + "date_to": "To", + "phases_section": "Cycle phases (optional)", + "phases_hint": "Different work on one shared schedule — each completion moves to the next step (e.g. small service, small service, big service).", + "phase_add": "Add phase", + "phase_name": "Phase name", + "phase_sequence_label": "Cycle order", + "phase_sequence_add_step": "Add step", + "phase_current": "Current phase", + "phase_set": "Set as current", + "chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/es.json b/custom_components/maintenance_supporter/frontend-src/locales/es.json index 534eac42..071fb3ae 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/es.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/es.json @@ -889,5 +889,14 @@ "service_record_entries": "entradas", "completed_by": "Realizado por", "date_from": "Desde", - "date_to": "Hasta" + "date_to": "Hasta", + "phases_section": "Fases del ciclo (opcional)", + "phases_hint": "Trabajos distintos con un mismo ritmo: cada finalización pasa al siguiente paso (p. ej. pequeño, pequeño, grande).", + "phase_add": "Añadir fase", + "phase_name": "Nombre de la fase", + "phase_sequence_label": "Orden del ciclo", + "phase_sequence_add_step": "Añadir paso", + "phase_current": "Fase actual", + "phase_set": "Establecer como actual", + "chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fi.json b/custom_components/maintenance_supporter/frontend-src/locales/fi.json index a47f3652..819640d7 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fi.json @@ -889,5 +889,14 @@ "service_record_entries": "merkintää", "completed_by": "Suorittanut", "date_from": "Alkaen", - "date_to": "Asti" + "date_to": "Asti", + "phases_section": "Syklin vaiheet (valinnainen)", + "phases_hint": "Eri työt samassa tahdissa — jokainen suoritus siirtyy seuraavaan vaiheeseen (esim. pieni, pieni, iso huolto).", + "phase_add": "Lisää vaihe", + "phase_name": "Vaiheen nimi", + "phase_sequence_label": "Syklin järjestys", + "phase_sequence_add_step": "Lisää askel", + "phase_current": "Nykyinen vaihe", + "phase_set": "Aseta nykyiseksi", + "chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fr.json b/custom_components/maintenance_supporter/frontend-src/locales/fr.json index dea343cd..61625953 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fr.json @@ -889,5 +889,14 @@ "service_record_entries": "entrées", "completed_by": "Réalisé par", "date_from": "Du", - "date_to": "Au" + "date_to": "Au", + "phases_section": "Phases du cycle (optionnel)", + "phases_hint": "Des travaux différents sur un même rythme — chaque réalisation passe à l'étape suivante (p. ex. petit, petit, grand entretien).", + "phase_add": "Ajouter une phase", + "phase_name": "Nom de la phase", + "phase_sequence_label": "Ordre du cycle", + "phase_sequence_add_step": "Ajouter une étape", + "phase_current": "Phase actuelle", + "phase_set": "Définir comme actuelle", + "chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hi.json b/custom_components/maintenance_supporter/frontend-src/locales/hi.json index 2109fbf5..9efae731 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hi.json @@ -889,5 +889,14 @@ "service_record_entries": "प्रविष्टियाँ", "completed_by": "द्वारा पूर्ण", "date_from": "से", - "date_to": "तक" + "date_to": "तक", + "phases_section": "चक्र चरण (वैकल्पिक)", + "phases_hint": "एक ही लय में अलग-अलग कार्य — प्रत्येक पूर्णता अगले चरण पर ले जाती है (जैसे छोटा, छोटा, बड़ा)।", + "phase_add": "चरण जोड़ें", + "phase_name": "चरण का नाम", + "phase_sequence_label": "चक्र क्रम", + "phase_sequence_add_step": "कदम जोड़ें", + "phase_current": "वर्तमान चरण", + "phase_set": "वर्तमान बनाएँ", + "chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hu.json b/custom_components/maintenance_supporter/frontend-src/locales/hu.json index 5abfabcc..686d207d 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hu.json @@ -889,5 +889,14 @@ "service_record_entries": "bejegyzés", "completed_by": "Elvégezte", "date_from": "Ettől", - "date_to": "Eddig" + "date_to": "Eddig", + "phases_section": "Ciklusfázisok (opcionális)", + "phases_hint": "Különböző munkák azonos ütemben — minden elvégzés a következő lépésre vált (pl. kis, kis, nagy szerviz).", + "phase_add": "Fázis hozzáadása", + "phase_name": "Fázis neve", + "phase_sequence_label": "Ciklus sorrendje", + "phase_sequence_add_step": "Lépés hozzáadása", + "phase_current": "Aktuális fázis", + "phase_set": "Beállítás aktuálisként", + "chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/it.json b/custom_components/maintenance_supporter/frontend-src/locales/it.json index d6c95703..bd06bd57 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/it.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/it.json @@ -889,5 +889,14 @@ "service_record_entries": "voci", "completed_by": "Completato da", "date_from": "Dal", - "date_to": "Al" + "date_to": "Al", + "phases_section": "Fasi del ciclo (opzionale)", + "phases_hint": "Lavori diversi sullo stesso ritmo — ogni completamento passa al passo successivo (es. piccolo, piccolo, grande).", + "phase_add": "Aggiungi fase", + "phase_name": "Nome della fase", + "phase_sequence_label": "Ordine del ciclo", + "phase_sequence_add_step": "Aggiungi passo", + "phase_current": "Fase attuale", + "phase_set": "Imposta come attuale", + "chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ja.json b/custom_components/maintenance_supporter/frontend-src/locales/ja.json index 52e5c56b..8daba1ba 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ja.json @@ -889,5 +889,14 @@ "service_record_entries": "件", "completed_by": "実施者", "date_from": "開始", - "date_to": "終了" + "date_to": "終了", + "phases_section": "サイクルフェーズ(任意)", + "phases_hint": "同じ周期で異なる作業を行います。完了するたびに次のステップへ進みます(例:小・小・大整備)。", + "phase_add": "フェーズを追加", + "phase_name": "フェーズ名", + "phase_sequence_label": "サイクルの順序", + "phase_sequence_add_step": "ステップを追加", + "phase_current": "現在のフェーズ", + "phase_set": "現在に設定", + "chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ko.json b/custom_components/maintenance_supporter/frontend-src/locales/ko.json index d5d7f2d1..080745a2 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ko.json @@ -889,5 +889,14 @@ "service_record_entries": "건", "completed_by": "수행자", "date_from": "시작", - "date_to": "종료" + "date_to": "종료", + "phases_section": "주기 단계(선택)", + "phases_hint": "같은 주기에 다른 작업을 수행합니다. 완료할 때마다 다음 단계로 넘어갑니다(예: 소·소·대 정비).", + "phase_add": "단계 추가", + "phase_name": "단계 이름", + "phase_sequence_label": "주기 순서", + "phase_sequence_add_step": "스텝 추가", + "phase_current": "현재 단계", + "phase_set": "현재로 설정", + "chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nb.json b/custom_components/maintenance_supporter/frontend-src/locales/nb.json index 2725f65a..e46e385e 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nb.json @@ -889,5 +889,14 @@ "service_record_entries": "oppføringer", "completed_by": "Utført av", "date_from": "Fra", - "date_to": "Til" + "date_to": "Til", + "phases_section": "Syklusfaser (valgfritt)", + "phases_hint": "Ulikt arbeid i samme rytme — hver fullføring går videre til neste trinn (f.eks. liten, liten, stor service).", + "phase_add": "Legg til fase", + "phase_name": "Fasenavn", + "phase_sequence_label": "Syklusrekkefølge", + "phase_sequence_add_step": "Legg til trinn", + "phase_current": "Gjeldende fase", + "phase_set": "Sett som gjeldende", + "chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nl.json b/custom_components/maintenance_supporter/frontend-src/locales/nl.json index 42f58f20..9037024d 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nl.json @@ -889,5 +889,14 @@ "service_record_entries": "items", "completed_by": "Voltooid door", "date_from": "Van", - "date_to": "Tot" + "date_to": "Tot", + "phases_section": "Cyclusfasen (optioneel)", + "phases_hint": "Verschillend werk op één ritme — elke afronding schuift naar de volgende stap (bijv. klein, klein, groot).", + "phase_add": "Fase toevoegen", + "phase_name": "Fasenaam", + "phase_sequence_label": "Cyclusvolgorde", + "phase_sequence_add_step": "Stap toevoegen", + "phase_current": "Huidige fase", + "phase_set": "Instellen als huidige", + "chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pl.json b/custom_components/maintenance_supporter/frontend-src/locales/pl.json index 59e010e8..0225dd10 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pl.json @@ -889,5 +889,14 @@ "service_record_entries": "wpisów", "completed_by": "Wykonane przez", "date_from": "Od", - "date_to": "Do" + "date_to": "Do", + "phases_section": "Fazy cyklu (opcjonalnie)", + "phases_hint": "Różne prace w jednym rytmie — każde ukończenie przechodzi do następnego kroku (np. mały, mały, duży).", + "phase_add": "Dodaj fazę", + "phase_name": "Nazwa fazy", + "phase_sequence_label": "Kolejność cyklu", + "phase_sequence_add_step": "Dodaj krok", + "phase_current": "Bieżąca faza", + "phase_set": "Ustaw jako bieżącą", + "chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json index 7a6e7891..a4da03d6 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json @@ -889,5 +889,14 @@ "service_record_entries": "registros", "completed_by": "Concluído por", "date_from": "De", - "date_to": "Até" + "date_to": "Até", + "phases_section": "Fases do ciclo (opcional)", + "phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o próximo passo (ex.: pequeno, pequeno, grande).", + "phase_add": "Adicionar fase", + "phase_name": "Nome da fase", + "phase_sequence_label": "Ordem do ciclo", + "phase_sequence_add_step": "Adicionar passo", + "phase_current": "Fase atual", + "phase_set": "Definir como atual", + "chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt.json b/custom_components/maintenance_supporter/frontend-src/locales/pt.json index 5630eb7d..82511e03 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt.json @@ -889,5 +889,14 @@ "service_record_entries": "registos", "completed_by": "Concluído por", "date_from": "De", - "date_to": "Até" + "date_to": "Até", + "phases_section": "Fases do ciclo (opcional)", + "phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o passo seguinte (ex.: pequeno, pequeno, grande).", + "phase_add": "Adicionar fase", + "phase_name": "Nome da fase", + "phase_sequence_label": "Ordem do ciclo", + "phase_sequence_add_step": "Adicionar passo", + "phase_current": "Fase atual", + "phase_set": "Definir como atual", + "chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ru.json b/custom_components/maintenance_supporter/frontend-src/locales/ru.json index 2fd0aafd..c82b30b3 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ru.json @@ -889,5 +889,14 @@ "service_record_entries": "записей", "completed_by": "Выполнил", "date_from": "С", - "date_to": "По" + "date_to": "По", + "phases_section": "Фазы цикла (необязательно)", + "phases_hint": "Разные работы в одном ритме — каждое выполнение переходит к следующему шагу (напр., малое, малое, большое ТО).", + "phase_add": "Добавить фазу", + "phase_name": "Название фазы", + "phase_sequence_label": "Порядок цикла", + "phase_sequence_add_step": "Добавить шаг", + "phase_current": "Текущая фаза", + "phase_set": "Сделать текущей", + "chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/sv.json b/custom_components/maintenance_supporter/frontend-src/locales/sv.json index 3d9a6cb9..3d857c24 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/sv.json @@ -889,5 +889,14 @@ "service_record_entries": "poster", "completed_by": "Utförd av", "date_from": "Från", - "date_to": "Till" + "date_to": "Till", + "phases_section": "Cykelfaser (valfritt)", + "phases_hint": "Olika arbeten i samma rytm — varje slutförande går vidare till nästa steg (t.ex. liten, liten, stor service).", + "phase_add": "Lägg till fas", + "phase_name": "Fasnamn", + "phase_sequence_label": "Cykelordning", + "phase_sequence_add_step": "Lägg till steg", + "phase_current": "Aktuell fas", + "phase_set": "Ange som aktuell", + "chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/tr.json b/custom_components/maintenance_supporter/frontend-src/locales/tr.json index 5baebb85..884664e5 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/tr.json @@ -889,5 +889,14 @@ "service_record_entries": "kayıt", "completed_by": "Tamamlayan", "date_from": "Başlangıç", - "date_to": "Bitiş" + "date_to": "Bitiş", + "phases_section": "Döngü aşamaları (isteğe bağlı)", + "phases_hint": "Aynı ritimde farklı işler — her tamamlama bir sonraki adıma geçer (örn. küçük, küçük, büyük bakım).", + "phase_add": "Aşama ekle", + "phase_name": "Aşama adı", + "phase_sequence_label": "Döngü sırası", + "phase_sequence_add_step": "Adım ekle", + "phase_current": "Geçerli aşama", + "phase_set": "Geçerli olarak ayarla", + "chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/uk.json b/custom_components/maintenance_supporter/frontend-src/locales/uk.json index 5b36b24b..91d90b6b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/uk.json @@ -889,5 +889,14 @@ "service_record_entries": "записів", "completed_by": "Виконав", "date_from": "Від", - "date_to": "До" + "date_to": "До", + "phases_section": "Фази циклу (необов'язково)", + "phases_hint": "Різні роботи в одному ритмі — кожне виконання переходить до наступного кроку (напр., мале, мале, велике ТО).", + "phase_add": "Додати фазу", + "phase_name": "Назва фази", + "phase_sequence_label": "Порядок циклу", + "phase_sequence_add_step": "Додати крок", + "phase_current": "Поточна фаза", + "phase_set": "Зробити поточною", + "chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/zh.json b/custom_components/maintenance_supporter/frontend-src/locales/zh.json index 2f0088b4..78e147a3 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/zh.json @@ -889,5 +889,14 @@ "service_record_entries": "条记录", "completed_by": "完成人", "date_from": "从", - "date_to": "至" + "date_to": "至", + "phases_section": "循环阶段(可选)", + "phases_hint": "同一节奏下的不同工作——每次完成后进入下一步(例如:小保养、小保养、大保养)。", + "phase_add": "添加阶段", + "phase_name": "阶段名称", + "phase_sequence_label": "循环顺序", + "phase_sequence_add_step": "添加步骤", + "phase_current": "当前阶段", + "phase_set": "设为当前", + "chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)" } diff --git a/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts b/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts index 9f8d5f56..b093f7ea 100644 --- a/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts @@ -19,6 +19,7 @@ import type { } from "./types"; import { UserService } from "./user-service"; import { partsForCompletion } from "./helpers/shared-parts"; +import { effectivePhase, phaseLabel } from "./helpers/phases"; import "./maintenance-card-editor"; import "./components/complete-dialog"; // The Battery Fleet card ships in this globally-loaded bundle so it is @@ -469,7 +470,7 @@ export class MaintenanceSupporterCard extends LitElement { ${!compact ? html`
- ${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` · ${this._assigneeName(task)}` @@ -514,14 +515,19 @@ export class MaintenanceSupporterCard extends LitElement { // Stop the row's open-task handler from also firing e.stopPropagation(); const dlg = this.shadowRoot!.querySelector("maintenance-complete-dialog") as any; + // #139: a phased task completes the phase + // currently due — its fields override the + // task-level ones (fallthrough included). + const phase = effectivePhase(task); dlg.entryId = entry_id; dlg.taskId = task.id; dlg.taskName = task.name; - dlg.checklist = task.checklist || []; + dlg.checklist = phase ? phase.checklist : (task.checklist || []); dlg.adaptiveEnabled = !!task.adaptive_config?.enabled; dlg.taskType = task.type || ""; dlg.readingUnit = (task as any).reading_unit || ""; - dlg.requiredFields = task.required_completion_fields || []; + dlg.requiredFields = phase ? phase.requiredFields : (task.required_completion_fields || []); + dlg.phaseLabel = phaseLabel(task); dlg.lang = L; // #99: editable per-completion parts selection // (skip on buy tasks — those restock instead). @@ -530,8 +536,9 @@ export class MaintenanceSupporterCard extends LitElement { // owner — resolving against the object's own // parts alone left a foreign link invisible. const isBuy = !!(task as any).part_ref; - dlg.parts = isBuy ? [] : partsForCompletion(task, entry_id, this._objects, L); - dlg.consumesParts = isBuy ? [] : (task.consumes_parts || []); + const links = phase ? phase.consumesParts : (task.consumes_parts || []); + dlg.parts = isBuy ? [] : partsForCompletion({ consumes_parts: links }, entry_id, this._objects, L); + dlg.consumesParts = isBuy ? [] : links; dlg.open(); }} > diff --git a/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts b/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts index 00c34581..d2729309 100644 --- a/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts +++ b/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts @@ -17,6 +17,7 @@ import { OBJECT_COLUMNS, DEFAULT_OBJECTS_TABLE_COLUMNS, sanitizeColumns } from " import { downloadTextFile } from "./helpers/download"; import { buildTaskWorksheetHtml, type WorksheetExcerpt, type WorksheetLabels } from "./helpers/worksheet"; import { describePartLink, partsForCompletion } from "./helpers/shared-parts"; +import { effectivePhase, phaseLabel } from "./helpers/phases"; import { describeWsError } from "./ws-errors"; import { panelStyles } from "./panel-styles"; import type { @@ -1861,7 +1862,9 @@ export class MaintenanceSupporterPanel extends LitElement { const task = obj?.tasks.find((x) => x.id === taskId); if (!task) return; const state: Record = {}; - for (const step of task.checklist || []) { + // #139: a phased task ticks the checklist of the phase currently due. + const steps = effectivePhase(task)?.checklist ?? (task.checklist || []); + for (const step of steps) { const current = task.checklist_progress?.[step] ?? false; state[step] = step === item ? done : current; } @@ -1888,11 +1891,20 @@ export class MaintenanceSupporterPanel extends LitElement { const tk = this._objects .find((o) => o.entry_id === entryId) ?.tasks.find((tsk) => tsk.id === taskId); + // #139: a phased task completes the phase currently due — its checklist, + // parts and required fields override the task-level values (fallthrough + // is already baked into effectivePhase). Resolved HERE so every call + // site of _openCompleteDialog rotates for free. + const phase = tk ? effectivePhase(tk) : null; + dlg.phaseLabel = phase ? phaseLabel(tk) : ""; dlg.taskType = tk?.type || ""; dlg.readingUnit = tk?.reading_unit || ""; // #73: ticks recorded during the cycle prefill the dialog's checklist. dlg.checklistPrefill = tk?.checklist_progress || {}; - dlg.requiredFields = tk?.required_completion_fields || []; + dlg.requiredFields = phase ? phase.requiredFields : (tk?.required_completion_fields || []); + if (phase) { + dlg.checklist = this._features.checklists ? phase.checklist : []; + } // Spare parts: a buy task gets an editable restock-qty field; a consuming // task shows what it will decrement (incl. the storage location). const objParts = this._objects.find((o) => o.entry_id === entryId)?.parts || []; @@ -1904,15 +1916,16 @@ export class MaintenanceSupporterPanel extends LitElement { dlg.currencySymbol = this._currencySymbol; // #111: a link may point at another object's pool — name that object, and // never drop a line that fails to resolve (the old .filter(Boolean) hid it). - dlg.consumesInfo = (tk?.consumes_parts || []).map((link) => + const links = phase ? phase.consumesParts : (tk?.consumes_parts || []); + dlg.consumesInfo = links.map((link) => describePartLink(link, entryId, this._objects, this._lang), ); // #99: editable per-completion parts selection (not on buy tasks — those // RESTOCK via the qty field instead of consuming). The list carries the // object's own parts plus the shared pools this task draws on, so a foreign // link is visible and untickable rather than silently absent. - dlg.parts = tk?.part_ref ? [] : partsForCompletion(tk, entryId, this._objects, this._lang); - dlg.consumesParts = tk?.part_ref ? [] : (tk?.consumes_parts || []); + dlg.parts = tk?.part_ref ? [] : partsForCompletion({ consumes_parts: links }, entryId, this._objects, this._lang); + dlg.consumesParts = tk?.part_ref ? [] : links; dlg.open(); } @@ -3535,6 +3548,7 @@ export class MaintenanceSupporterPanel extends LitElement { lang: this._lang, detailStatsData: this._detailStatsData, hasStatsService: !!this._statsService, + historyFallbackIds: this._statsService?.historyFallbackIds, isCounterEntity: (tc) => this._isCounterEntity(tc), rangeDays: this._chartRangeDays, setRangeDays: (days) => this._setChartRange(days), @@ -3579,6 +3593,10 @@ export class MaintenanceSupporterPanel extends LitElement { setSearch: (s) => { this._historySearch = s; }, openEdit: (entry) => this._openHistoryEdit(entry), readingUnit: task?.reading_unit ?? null, + // #139: name the phase badge on entries stamped with a phase_id. + phaseNames: Object.fromEntries( + Object.entries(task?.phases || {}).map(([id, def]) => [id, def.name]), + ), readingDelta: (entry) => { const idx = readings.findIndex((h) => h.timestamp === entry.timestamp); if (idx <= 0) return null; @@ -3604,6 +3622,12 @@ export class MaintenanceSupporterPanel extends LitElement { objectManualDocs: obj?.object?.manual_docs ?? [], openManualDoc: (doc) => this._openManualDoc(doc), setChecklistItem: (item, done) => this._setChecklistItem(entryId, taskId, item, done), + setPhaseCursor: (cursor) => { + void this._runAction({ + type: "maintenance_supporter/task/set_phase", + entry_id: entryId, task_id: taskId, cursor, + }); + }, isOperator: this._isOperator, actionLoading: this._actionLoading, moreMenuOpen: this._moreMenuOpen, diff --git a/custom_components/maintenance_supporter/frontend-src/package-lock.json b/custom_components/maintenance_supporter/frontend-src/package-lock.json index 6fcf2a21..1d626fb6 100644 --- a/custom_components/maintenance_supporter/frontend-src/package-lock.json +++ b/custom_components/maintenance_supporter/frontend-src/package-lock.json @@ -12,12 +12,16 @@ "playwright": "1.62" }, "devDependencies": { + "@mdi/js": "^7.4.47", "@open-wc/testing": "^5.0.0", + "@types/react": "^19.2.18", "@web/dev-server-esbuild": "^2.0.0", "@web/test-runner": "^1.0.0", "@web/test-runner-playwright": "^1.0.0", "esbuild": "^0.28.1", "lit": "^3.3.3", + "react": "^19.2.8", + "react-dom": "^19.2.8", "typescript": "^7.0.2" } }, @@ -550,6 +554,13 @@ "@lit-labs/ssr-dom-shim": "^1.5.0" } }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@mdn/browser-compat-data": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@mdn/browser-compat-data/-/browser-compat-data-4.2.1.tgz", @@ -1586,6 +1597,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -2921,6 +2942,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", @@ -4981,6 +5009,29 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -5221,6 +5272,13 @@ "dev": true, "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", diff --git a/custom_components/maintenance_supporter/frontend-src/package.json b/custom_components/maintenance_supporter/frontend-src/package.json index ab2bfc3b..e6a09521 100644 --- a/custom_components/maintenance_supporter/frontend-src/package.json +++ b/custom_components/maintenance_supporter/frontend-src/package.json @@ -9,12 +9,16 @@ "test:watch": "web-test-runner --config web-test-runner.config.mjs --watch" }, "devDependencies": { + "@mdi/js": "^7.4.47", "@open-wc/testing": "^5.0.0", + "@types/react": "^19.2.18", "@web/dev-server-esbuild": "^2.0.0", "@web/test-runner": "^1.0.0", "@web/test-runner-playwright": "^1.0.0", "esbuild": "^0.28.1", "lit": "^3.3.3", + "react": "^19.2.8", + "react-dom": "^19.2.8", "typescript": "^7.0.2" }, "dependencies": { diff --git a/custom_components/maintenance_supporter/frontend-src/panel-styles.ts b/custom_components/maintenance_supporter/frontend-src/panel-styles.ts index 52891716..9c0b1dae 100644 --- a/custom_components/maintenance_supporter/frontend-src/panel-styles.ts +++ b/custom_components/maintenance_supporter/frontend-src/panel-styles.ts @@ -1053,6 +1053,53 @@ export const panelStyles = css` border-color: var(--primary-color); } + /* #139: cycle-phase strip in the task overview */ + .phases-card { + background: var(--card-background-color, #fff); + border-radius: 8px; + padding: 12px 16px; + border: 1px solid var(--divider-color); + margin-top: 8px; + } + .phases-card-header { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 500; + margin-bottom: 8px; + } + .phases-card-header ha-icon { + --mdc-icon-size: 18px; + color: var(--secondary-text-color); + } + .phases-strip { + display: flex; + flex-wrap: wrap; + gap: 8px; + } + .phase-step { + display: flex; + flex-direction: column; + gap: 2px; + padding: 6px 12px; + border-radius: 8px; + border: 1px solid var(--divider-color); + font-size: 13px; + cursor: pointer; + } + .phase-step.current { + border-color: var(--primary-color); + background: color-mix(in srgb, var(--primary-color) 12%, transparent); + font-weight: 500; + cursor: default; + } + .phase-step-last { + font-size: 11px; + color: var(--secondary-text-color); + font-weight: 400; + } + /* Checklist preview card (read-only display in task overview) */ .checklist-preview-card { background: var(--card-background-color, #fff); diff --git a/custom_components/maintenance_supporter/frontend-src/renderers/history.ts b/custom_components/maintenance_supporter/frontend-src/renderers/history.ts index c6c54475..b335d15e 100644 --- a/custom_components/maintenance_supporter/frontend-src/renderers/history.ts +++ b/custom_components/maintenance_supporter/frontend-src/renderers/history.ts @@ -27,6 +27,8 @@ export interface HistoryContext { /** v2.20 (#83): unit + delta-vs-previous for reading-task entries. */ readingUnit?: string | null; readingDelta?: (entry: HistoryEntry) => number | null; + /** #139: phase id → display name, for entries stamped with a phase_id. */ + phaseNames?: Record; } // Every HistoryEntryType is filterable (drift audit 2026-08: the repair-flow @@ -96,6 +98,7 @@ export function renderHistoryEntry(entry: HistoryEntry, ctx: HistoryContext) {
${t(entry.type, L)} + ${entry.phase_id ? html`${ctx.phaseNames?.[entry.phase_id] || entry.phase_id}` : nothing} ${entry.auto ? html`${t("history_auto", L)}` : nothing} ${editable ? html`
`, ctx) : nothing} + ${renderPhasesCard(task, ctx)} ${renderChecklistCard(task, ctx)} ${renderRecentActivities(task, ctx)}
diff --git a/custom_components/maintenance_supporter/frontend-src/statistics-service.ts b/custom_components/maintenance_supporter/frontend-src/statistics-service.ts index f555514d..4a331451 100644 --- a/custom_components/maintenance_supporter/frontend-src/statistics-service.ts +++ b/custom_components/maintenance_supporter/frontend-src/statistics-service.ts @@ -8,11 +8,27 @@ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes const DETAIL_DAYS = 30; const MINI_DAYS = 14; +/** history/history_during_period rows — compressed keys over WS ({s, lu}); + * the uncompressed REST shape is tolerated defensively. */ +interface HAHistoryRow { + s?: string; + lu?: number; + state?: string; + last_updated?: string | number; + last_changed?: string | number; +} + export class StatisticsService { private _hass: HomeAssistant; private _cache = new Map(); private _pending = new Map>(); + /** #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(); + constructor(hass: HomeAssistant) { this._hass = hass; } @@ -141,7 +157,23 @@ export class StatisticsService { })) as Record; const rows = result[entityId] || []; - const points = this._normalizeRows(rows, isCounter); + let points = this._normalizeRows(rows, isCounter); + + // #141: entities without long-term statistics (binary sensors, sensors + // without a state_class) always come back empty here — fall back to + // recorder STATE HISTORY (typically ~10 days) so a problem sensor's + // on/off timeline draws as a 0/1 step line instead of a flat value. + if (points.length < 2) { + const historyPoints = await this._fetchHistoryFallback(entityId, startTime); + if (historyPoints.length >= 2) { + points = historyPoints; + this.historyFallbackIds.add(entityId); + } else { + this.historyFallbackIds.delete(entityId); + } + } else { + this.historyFallbackIds.delete(entityId); + } this._cache.set(cacheKey, { entityId, @@ -157,6 +189,60 @@ export class StatisticsService { } } + /** Recorder state history as chart points. Binary states map to 0/1, and + * every value CHANGE gets a doubled point ((t, old), (t, new)) so the + * line renderer draws a step instead of a slope between samples. */ + private async _fetchHistoryFallback(entityId: string, startTime: string): Promise { + 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; + + let rows = result?.[entityId] || []; + // A chatty numeric entity can return thousands of rows — stride-sample + // BEFORE the step expansion (the expansion preserves the edges that + // survive sampling). + if (rows.length > 1000) { + const stride = Math.ceil(rows.length / 500); + rows = rows.filter((_, i) => i % stride === 0 || i === rows.length - 1); + } + + const points: StatisticsPoint[] = []; + let prev: number | null = null; + for (const row of rows) { + const state = row.s ?? row.state; + if (state == null || state === "unknown" || state === "unavailable") continue; + let val: number; + if (state === "on" || state === "open" || state === "true") val = 1; + else if (state === "off" || state === "closed" || state === "false") val = 0; + else { + val = parseFloat(state); + if (!Number.isFinite(val)) continue; + } + const raw = row.lu ?? row.last_updated ?? row.last_changed; + const ts = typeof raw === "number" ? raw * 1000 : raw != null ? Date.parse(raw) : NaN; + if (!Number.isFinite(ts)) continue; + if (prev != null && prev !== val) points.push({ ts, val: prev }); + points.push({ ts, val }); + prev = val; + } + points.sort((a, b) => a.ts - b.ts); + // Extend the last known state to "now" so the line reaches the right + // edge (the caller must NOT append trigger_current_value here — for a + // state_change trigger that is the change COUNTER, not the state). + if (points.length && prev != null) points.push({ ts: Date.now(), val: prev }); + return points; + } catch (err) { + console.warn(`[maintenance-supporter] History fallback failed for ${entityId}:`, err); + return []; + } + } + private async _fetchBatch( entityIds: string[], period: "hour" | "day", diff --git a/custom_components/maintenance_supporter/frontend-src/styles.ts b/custom_components/maintenance_supporter/frontend-src/styles.ts index cafaf2fb..89412708 100644 --- a/custom_components/maintenance_supporter/frontend-src/styles.ts +++ b/custom_components/maintenance_supporter/frontend-src/styles.ts @@ -604,6 +604,21 @@ export const sharedStyles = css` color: var(--secondary-text-color); white-space: nowrap; } + /* #139 — names the cycle phase a completion recorded. Shares the auto-badge + look; carries the left-pinning margin itself unless the auto badge (which + already has it) follows. */ + .history-phase-badge { + margin-right: auto; + font-size: 11px; + padding: 1px 8px; + border-radius: 10px; + background: var(--secondary-background-color); + color: var(--secondary-text-color); + white-space: nowrap; + } + .history-phase-badge:has(+ .history-auto-badge) { + margin-right: 0; + } .history-edit-btn { background: transparent; color: var(--secondary-text-color); diff --git a/custom_components/maintenance_supporter/frontend-src/types.ts b/custom_components/maintenance_supporter/frontend-src/types.ts index 8c327fc2..569d3ff0 100644 --- a/custom_components/maintenance_supporter/frontend-src/types.ts +++ b/custom_components/maintenance_supporter/frontend-src/types.ts @@ -103,6 +103,9 @@ export interface HistoryEntry { /** v2.37: completion recorded by the system itself (trigger recovered), * not performed by a user in the UI. */ auto?: boolean; + /** #139: which cycle phase this LATEST completion recorded (backfills + * carry none — they never advanced the cursor). */ + phase_id?: string | null; } export interface AdaptiveConfig { @@ -269,6 +272,22 @@ export interface MaintenanceTask { consumes_parts?: TaskPartLink[] | null; /** Present on an auto-created "buy" reminder: the owning part. */ part_ref?: { part_id: string } | null; + /** Task phases (#139): cyclic content rotation on one shared cadence. */ + phases?: Record | 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`. diff --git a/custom_components/maintenance_supporter/frontend/locales/cs.json b/custom_components/maintenance_supporter/frontend/locales/cs.json index da868733..ba28dfd8 100644 --- a/custom_components/maintenance_supporter/frontend/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend/locales/cs.json @@ -889,5 +889,14 @@ "service_record_entries": "záznamů", "completed_by": "Dokončil", "date_from": "Od", - "date_to": "Do" + "date_to": "Do", + "phases_section": "Fáze cyklu (volitelné)", + "phases_hint": "Různé práce ve stejném rytmu — každé dokončení posune na další krok (např. malý, malý, velký servis).", + "phase_add": "Přidat fázi", + "phase_name": "Název fáze", + "phase_sequence_label": "Pořadí cyklu", + "phase_sequence_add_step": "Přidat krok", + "phase_current": "Aktuální fáze", + "phase_set": "Nastavit jako aktuální", + "chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/da.json b/custom_components/maintenance_supporter/frontend/locales/da.json index c89c96ee..679e05e8 100644 --- a/custom_components/maintenance_supporter/frontend/locales/da.json +++ b/custom_components/maintenance_supporter/frontend/locales/da.json @@ -889,5 +889,14 @@ "service_record_entries": "poster", "completed_by": "Udført af", "date_from": "Fra", - "date_to": "Til" + "date_to": "Til", + "phases_section": "Cyklusfaser (valgfrit)", + "phases_hint": "Forskelligt arbejde i samme rytme — hver udførelse går videre til næste trin (f.eks. lille, lille, stor service).", + "phase_add": "Tilføj fase", + "phase_name": "Fasenavn", + "phase_sequence_label": "Cyklusrækkefølge", + "phase_sequence_add_step": "Tilføj trin", + "phase_current": "Aktuel fase", + "phase_set": "Sæt som aktuel", + "chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/de.json b/custom_components/maintenance_supporter/frontend/locales/de.json index ed01d558..da16fa37 100644 --- a/custom_components/maintenance_supporter/frontend/locales/de.json +++ b/custom_components/maintenance_supporter/frontend/locales/de.json @@ -889,5 +889,14 @@ "service_record_entries": "Einträge", "completed_by": "Erledigt von", "date_from": "Von", - "date_to": "Bis" + "date_to": "Bis", + "phases_section": "Zyklus-Phasen (optional)", + "phases_hint": "Unterschiedliche Arbeiten im gleichen Takt — jede Erledigung schaltet zum nächsten Schritt (z. B. klein, klein, groß).", + "phase_add": "Phase hinzufügen", + "phase_name": "Phasenname", + "phase_sequence_label": "Zyklus-Reihenfolge", + "phase_sequence_add_step": "Schritt anhängen", + "phase_current": "Aktuelle Phase", + "phase_set": "Als aktuell setzen", + "chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/en.json b/custom_components/maintenance_supporter/frontend/locales/en.json index 9de226a5..38ba33c0 100644 --- a/custom_components/maintenance_supporter/frontend/locales/en.json +++ b/custom_components/maintenance_supporter/frontend/locales/en.json @@ -889,5 +889,14 @@ "service_record_entries": "entries", "completed_by": "Completed by", "date_from": "From", - "date_to": "To" + "date_to": "To", + "phases_section": "Cycle phases (optional)", + "phases_hint": "Different work on one shared schedule — each completion moves to the next step (e.g. small service, small service, big service).", + "phase_add": "Add phase", + "phase_name": "Phase name", + "phase_sequence_label": "Cycle order", + "phase_sequence_add_step": "Add step", + "phase_current": "Current phase", + "phase_set": "Set as current", + "chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/es.json b/custom_components/maintenance_supporter/frontend/locales/es.json index 534eac42..071fb3ae 100644 --- a/custom_components/maintenance_supporter/frontend/locales/es.json +++ b/custom_components/maintenance_supporter/frontend/locales/es.json @@ -889,5 +889,14 @@ "service_record_entries": "entradas", "completed_by": "Realizado por", "date_from": "Desde", - "date_to": "Hasta" + "date_to": "Hasta", + "phases_section": "Fases del ciclo (opcional)", + "phases_hint": "Trabajos distintos con un mismo ritmo: cada finalización pasa al siguiente paso (p. ej. pequeño, pequeño, grande).", + "phase_add": "Añadir fase", + "phase_name": "Nombre de la fase", + "phase_sequence_label": "Orden del ciclo", + "phase_sequence_add_step": "Añadir paso", + "phase_current": "Fase actual", + "phase_set": "Establecer como actual", + "chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/fi.json b/custom_components/maintenance_supporter/frontend/locales/fi.json index a47f3652..819640d7 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend/locales/fi.json @@ -889,5 +889,14 @@ "service_record_entries": "merkintää", "completed_by": "Suorittanut", "date_from": "Alkaen", - "date_to": "Asti" + "date_to": "Asti", + "phases_section": "Syklin vaiheet (valinnainen)", + "phases_hint": "Eri työt samassa tahdissa — jokainen suoritus siirtyy seuraavaan vaiheeseen (esim. pieni, pieni, iso huolto).", + "phase_add": "Lisää vaihe", + "phase_name": "Vaiheen nimi", + "phase_sequence_label": "Syklin järjestys", + "phase_sequence_add_step": "Lisää askel", + "phase_current": "Nykyinen vaihe", + "phase_set": "Aseta nykyiseksi", + "chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/fr.json b/custom_components/maintenance_supporter/frontend/locales/fr.json index dea343cd..61625953 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend/locales/fr.json @@ -889,5 +889,14 @@ "service_record_entries": "entrées", "completed_by": "Réalisé par", "date_from": "Du", - "date_to": "Au" + "date_to": "Au", + "phases_section": "Phases du cycle (optionnel)", + "phases_hint": "Des travaux différents sur un même rythme — chaque réalisation passe à l'étape suivante (p. ex. petit, petit, grand entretien).", + "phase_add": "Ajouter une phase", + "phase_name": "Nom de la phase", + "phase_sequence_label": "Ordre du cycle", + "phase_sequence_add_step": "Ajouter une étape", + "phase_current": "Phase actuelle", + "phase_set": "Définir comme actuelle", + "chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/hi.json b/custom_components/maintenance_supporter/frontend/locales/hi.json index 2109fbf5..9efae731 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend/locales/hi.json @@ -889,5 +889,14 @@ "service_record_entries": "प्रविष्टियाँ", "completed_by": "द्वारा पूर्ण", "date_from": "से", - "date_to": "तक" + "date_to": "तक", + "phases_section": "चक्र चरण (वैकल्पिक)", + "phases_hint": "एक ही लय में अलग-अलग कार्य — प्रत्येक पूर्णता अगले चरण पर ले जाती है (जैसे छोटा, छोटा, बड़ा)।", + "phase_add": "चरण जोड़ें", + "phase_name": "चरण का नाम", + "phase_sequence_label": "चक्र क्रम", + "phase_sequence_add_step": "कदम जोड़ें", + "phase_current": "वर्तमान चरण", + "phase_set": "वर्तमान बनाएँ", + "chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/hu.json b/custom_components/maintenance_supporter/frontend/locales/hu.json index 5abfabcc..686d207d 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend/locales/hu.json @@ -889,5 +889,14 @@ "service_record_entries": "bejegyzés", "completed_by": "Elvégezte", "date_from": "Ettől", - "date_to": "Eddig" + "date_to": "Eddig", + "phases_section": "Ciklusfázisok (opcionális)", + "phases_hint": "Különböző munkák azonos ütemben — minden elvégzés a következő lépésre vált (pl. kis, kis, nagy szerviz).", + "phase_add": "Fázis hozzáadása", + "phase_name": "Fázis neve", + "phase_sequence_label": "Ciklus sorrendje", + "phase_sequence_add_step": "Lépés hozzáadása", + "phase_current": "Aktuális fázis", + "phase_set": "Beállítás aktuálisként", + "chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/it.json b/custom_components/maintenance_supporter/frontend/locales/it.json index d6c95703..bd06bd57 100644 --- a/custom_components/maintenance_supporter/frontend/locales/it.json +++ b/custom_components/maintenance_supporter/frontend/locales/it.json @@ -889,5 +889,14 @@ "service_record_entries": "voci", "completed_by": "Completato da", "date_from": "Dal", - "date_to": "Al" + "date_to": "Al", + "phases_section": "Fasi del ciclo (opzionale)", + "phases_hint": "Lavori diversi sullo stesso ritmo — ogni completamento passa al passo successivo (es. piccolo, piccolo, grande).", + "phase_add": "Aggiungi fase", + "phase_name": "Nome della fase", + "phase_sequence_label": "Ordine del ciclo", + "phase_sequence_add_step": "Aggiungi passo", + "phase_current": "Fase attuale", + "phase_set": "Imposta come attuale", + "chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ja.json b/custom_components/maintenance_supporter/frontend/locales/ja.json index 52e5c56b..8daba1ba 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend/locales/ja.json @@ -889,5 +889,14 @@ "service_record_entries": "件", "completed_by": "実施者", "date_from": "開始", - "date_to": "終了" + "date_to": "終了", + "phases_section": "サイクルフェーズ(任意)", + "phases_hint": "同じ周期で異なる作業を行います。完了するたびに次のステップへ進みます(例:小・小・大整備)。", + "phase_add": "フェーズを追加", + "phase_name": "フェーズ名", + "phase_sequence_label": "サイクルの順序", + "phase_sequence_add_step": "ステップを追加", + "phase_current": "現在のフェーズ", + "phase_set": "現在に設定", + "chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ko.json b/custom_components/maintenance_supporter/frontend/locales/ko.json index d5d7f2d1..080745a2 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend/locales/ko.json @@ -889,5 +889,14 @@ "service_record_entries": "건", "completed_by": "수행자", "date_from": "시작", - "date_to": "종료" + "date_to": "종료", + "phases_section": "주기 단계(선택)", + "phases_hint": "같은 주기에 다른 작업을 수행합니다. 완료할 때마다 다음 단계로 넘어갑니다(예: 소·소·대 정비).", + "phase_add": "단계 추가", + "phase_name": "단계 이름", + "phase_sequence_label": "주기 순서", + "phase_sequence_add_step": "스텝 추가", + "phase_current": "현재 단계", + "phase_set": "현재로 설정", + "chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/nb.json b/custom_components/maintenance_supporter/frontend/locales/nb.json index 2725f65a..e46e385e 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend/locales/nb.json @@ -889,5 +889,14 @@ "service_record_entries": "oppføringer", "completed_by": "Utført av", "date_from": "Fra", - "date_to": "Til" + "date_to": "Til", + "phases_section": "Syklusfaser (valgfritt)", + "phases_hint": "Ulikt arbeid i samme rytme — hver fullføring går videre til neste trinn (f.eks. liten, liten, stor service).", + "phase_add": "Legg til fase", + "phase_name": "Fasenavn", + "phase_sequence_label": "Syklusrekkefølge", + "phase_sequence_add_step": "Legg til trinn", + "phase_current": "Gjeldende fase", + "phase_set": "Sett som gjeldende", + "chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/nl.json b/custom_components/maintenance_supporter/frontend/locales/nl.json index 42f58f20..9037024d 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend/locales/nl.json @@ -889,5 +889,14 @@ "service_record_entries": "items", "completed_by": "Voltooid door", "date_from": "Van", - "date_to": "Tot" + "date_to": "Tot", + "phases_section": "Cyclusfasen (optioneel)", + "phases_hint": "Verschillend werk op één ritme — elke afronding schuift naar de volgende stap (bijv. klein, klein, groot).", + "phase_add": "Fase toevoegen", + "phase_name": "Fasenaam", + "phase_sequence_label": "Cyclusvolgorde", + "phase_sequence_add_step": "Stap toevoegen", + "phase_current": "Huidige fase", + "phase_set": "Instellen als huidige", + "chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/pl.json b/custom_components/maintenance_supporter/frontend/locales/pl.json index 59e010e8..0225dd10 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend/locales/pl.json @@ -889,5 +889,14 @@ "service_record_entries": "wpisów", "completed_by": "Wykonane przez", "date_from": "Od", - "date_to": "Do" + "date_to": "Do", + "phases_section": "Fazy cyklu (opcjonalnie)", + "phases_hint": "Różne prace w jednym rytmie — każde ukończenie przechodzi do następnego kroku (np. mały, mały, duży).", + "phase_add": "Dodaj fazę", + "phase_name": "Nazwa fazy", + "phase_sequence_label": "Kolejność cyklu", + "phase_sequence_add_step": "Dodaj krok", + "phase_current": "Bieżąca faza", + "phase_set": "Ustaw jako bieżącą", + "chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/pt-br.json b/custom_components/maintenance_supporter/frontend/locales/pt-br.json index 7a6e7891..a4da03d6 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt-br.json @@ -889,5 +889,14 @@ "service_record_entries": "registros", "completed_by": "Concluído por", "date_from": "De", - "date_to": "Até" + "date_to": "Até", + "phases_section": "Fases do ciclo (opcional)", + "phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o próximo passo (ex.: pequeno, pequeno, grande).", + "phase_add": "Adicionar fase", + "phase_name": "Nome da fase", + "phase_sequence_label": "Ordem do ciclo", + "phase_sequence_add_step": "Adicionar passo", + "phase_current": "Fase atual", + "phase_set": "Definir como atual", + "chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/pt.json b/custom_components/maintenance_supporter/frontend/locales/pt.json index 5630eb7d..82511e03 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt.json @@ -889,5 +889,14 @@ "service_record_entries": "registos", "completed_by": "Concluído por", "date_from": "De", - "date_to": "Até" + "date_to": "Até", + "phases_section": "Fases do ciclo (opcional)", + "phases_hint": "Trabalhos diferentes no mesmo ritmo — cada conclusão avança para o passo seguinte (ex.: pequeno, pequeno, grande).", + "phase_add": "Adicionar fase", + "phase_name": "Nome da fase", + "phase_sequence_label": "Ordem do ciclo", + "phase_sequence_add_step": "Adicionar passo", + "phase_current": "Fase atual", + "phase_set": "Definir como atual", + "chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ru.json b/custom_components/maintenance_supporter/frontend/locales/ru.json index 2fd0aafd..c82b30b3 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend/locales/ru.json @@ -889,5 +889,14 @@ "service_record_entries": "записей", "completed_by": "Выполнил", "date_from": "С", - "date_to": "По" + "date_to": "По", + "phases_section": "Фазы цикла (необязательно)", + "phases_hint": "Разные работы в одном ритме — каждое выполнение переходит к следующему шагу (напр., малое, малое, большое ТО).", + "phase_add": "Добавить фазу", + "phase_name": "Название фазы", + "phase_sequence_label": "Порядок цикла", + "phase_sequence_add_step": "Добавить шаг", + "phase_current": "Текущая фаза", + "phase_set": "Сделать текущей", + "chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/sv.json b/custom_components/maintenance_supporter/frontend/locales/sv.json index 3d9a6cb9..3d857c24 100644 --- a/custom_components/maintenance_supporter/frontend/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend/locales/sv.json @@ -889,5 +889,14 @@ "service_record_entries": "poster", "completed_by": "Utförd av", "date_from": "Från", - "date_to": "Till" + "date_to": "Till", + "phases_section": "Cykelfaser (valfritt)", + "phases_hint": "Olika arbeten i samma rytm — varje slutförande går vidare till nästa steg (t.ex. liten, liten, stor service).", + "phase_add": "Lägg till fas", + "phase_name": "Fasnamn", + "phase_sequence_label": "Cykelordning", + "phase_sequence_add_step": "Lägg till steg", + "phase_current": "Aktuell fas", + "phase_set": "Ange som aktuell", + "chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/tr.json b/custom_components/maintenance_supporter/frontend/locales/tr.json index 5baebb85..884664e5 100644 --- a/custom_components/maintenance_supporter/frontend/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend/locales/tr.json @@ -889,5 +889,14 @@ "service_record_entries": "kayıt", "completed_by": "Tamamlayan", "date_from": "Başlangıç", - "date_to": "Bitiş" + "date_to": "Bitiş", + "phases_section": "Döngü aşamaları (isteğe bağlı)", + "phases_hint": "Aynı ritimde farklı işler — her tamamlama bir sonraki adıma geçer (örn. küçük, küçük, büyük bakım).", + "phase_add": "Aşama ekle", + "phase_name": "Aşama adı", + "phase_sequence_label": "Döngü sırası", + "phase_sequence_add_step": "Adım ekle", + "phase_current": "Geçerli aşama", + "phase_set": "Geçerli olarak ayarla", + "chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/uk.json b/custom_components/maintenance_supporter/frontend/locales/uk.json index 5b36b24b..91d90b6b 100644 --- a/custom_components/maintenance_supporter/frontend/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend/locales/uk.json @@ -889,5 +889,14 @@ "service_record_entries": "записів", "completed_by": "Виконав", "date_from": "Від", - "date_to": "До" + "date_to": "До", + "phases_section": "Фази циклу (необов'язково)", + "phases_hint": "Різні роботи в одному ритмі — кожне виконання переходить до наступного кроку (напр., мале, мале, велике ТО).", + "phase_add": "Додати фазу", + "phase_name": "Назва фази", + "phase_sequence_label": "Порядок циклу", + "phase_sequence_add_step": "Додати крок", + "phase_current": "Поточна фаза", + "phase_set": "Зробити поточною", + "chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)" } diff --git a/custom_components/maintenance_supporter/frontend/locales/zh.json b/custom_components/maintenance_supporter/frontend/locales/zh.json index 2f0088b4..78e147a3 100644 --- a/custom_components/maintenance_supporter/frontend/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend/locales/zh.json @@ -889,5 +889,14 @@ "service_record_entries": "条记录", "completed_by": "完成人", "date_from": "从", - "date_to": "至" + "date_to": "至", + "phases_section": "循环阶段(可选)", + "phases_hint": "同一节奏下的不同工作——每次完成后进入下一步(例如:小保养、小保养、大保养)。", + "phase_add": "添加阶段", + "phase_name": "阶段名称", + "phase_sequence_label": "循环顺序", + "phase_sequence_add_step": "添加步骤", + "phase_current": "当前阶段", + "phase_set": "设为当前", + "chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)" } diff --git a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js index d63af4d3..90ca145f 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js @@ -1,9 +1,9 @@ -/*! maintenance_supporter frontend 2.64.0 */ -var xt=Object.defineProperty;var Zi=Object.getOwnPropertyDescriptor;var w=(a,r,e)=>()=>{if(e)throw e[0];try{return a&&(r=a(a=0)),r}catch(t){throw e=[t],t}};var Xi=(a,r)=>{for(var e in r)xt(a,e,{get:r[e],enumerable:!0})};var d=(a,r,e,t)=>{for(var i=t>1?void 0:t?Zi(r,e):r,n=a.length-1,o;n>=0;n--)(o=a[n])&&(i=(t?o(r,e,i):o(i))||i);return t&&i&&xt(r,e,i),i};var Ne,He,Ze,wt,be,$t,S,kt,Xe,et=w(()=>{Ne=globalThis,He=Ne.ShadowRoot&&(Ne.ShadyCSS===void 0||Ne.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Ze=Symbol(),wt=new WeakMap,be=class{constructor(r,e,t){if(this._$cssResult$=!0,t!==Ze)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=r,this.t=e}get styleSheet(){let r=this.o,e=this.t;if(He&&r===void 0){let t=e!==void 0&&e.length===1;t&&(r=wt.get(e)),r===void 0&&((this.o=r=new CSSStyleSheet).replaceSync(this.cssText),t&&wt.set(e,r))}return r}toString(){return this.cssText}},$t=a=>new be(typeof a=="string"?a:a+"",void 0,Ze),S=(a,...r)=>{let e=a.length===1?a[0]:r.reduce((t,i,n)=>t+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new be(e,a,Ze)},kt=(a,r)=>{if(He)a.adoptedStyleSheets=r.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of r){let t=document.createElement("style"),i=Ne.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Xe=He?a=>a:a=>a instanceof CSSStyleSheet?(r=>{let e="";for(let t of r.cssRules)e+=t.cssText;return $t(e)})(a):a});var er,tr,ir,rr,sr,ar,qe,Et,nr,or,xe,we,Me,St,G,$e=w(()=>{et();et();({is:er,defineProperty:tr,getOwnPropertyDescriptor:ir,getOwnPropertyNames:rr,getOwnPropertySymbols:sr,getPrototypeOf:ar}=Object),qe=globalThis,Et=qe.trustedTypes,nr=Et?Et.emptyScript:"",or=qe.reactiveElementPolyfillSupport,xe=(a,r)=>a,we={toAttribute(a,r){switch(r){case Boolean:a=a?nr:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,r){let e=a;switch(r){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}},Me=(a,r)=>!er(a,r),St={attribute:!0,type:String,converter:we,reflect:!1,useDefault:!1,hasChanged:Me};Symbol.metadata??=Symbol("metadata"),qe.litPropertyMetadata??=new WeakMap;G=class extends HTMLElement{static addInitializer(r){this._$Ei(),(this.l??=[]).push(r)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(r,e=St){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(r)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(r,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(r,t,e);i!==void 0&&tr(this.prototype,r,i)}}static getPropertyDescriptor(r,e,t){let{get:i,set:n}=ir(this.prototype,r)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let p=i?.call(this);n?.call(this,o),this.requestUpdate(r,p,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(r){return this.elementProperties.get(r)??St}static _$Ei(){if(this.hasOwnProperty(xe("elementProperties")))return;let r=ar(this);r.finalize(),r.l!==void 0&&(this.l=[...r.l]),this.elementProperties=new Map(r.elementProperties)}static finalize(){if(this.hasOwnProperty(xe("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(xe("properties"))){let e=this.properties,t=[...rr(e),...sr(e)];for(let i of t)this.createProperty(i,e[i])}let r=this[Symbol.metadata];if(r!==null){let e=litPropertyMetadata.get(r);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(r){let e=[];if(Array.isArray(r)){let t=new Set(r.flat(1/0).reverse());for(let i of t)e.unshift(Xe(i))}else r!==void 0&&e.push(Xe(r));return e}static _$Eu(r,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof r=="string"?r.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(r=>this.enableUpdating=r),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(r=>r(this))}addController(r){(this._$EO??=new Set).add(r),this.renderRoot!==void 0&&this.isConnected&&r.hostConnected?.()}removeController(r){this._$EO?.delete(r)}_$E_(){let r=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(r.set(t,this[t]),delete this[t]);r.size>0&&(this._$Ep=r)}createRenderRoot(){let r=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return kt(r,this.constructor.elementStyles),r}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(r=>r.hostConnected?.())}enableUpdating(r){}disconnectedCallback(){this._$EO?.forEach(r=>r.hostDisconnected?.())}attributeChangedCallback(r,e,t){this._$AK(r,t)}_$ET(r,e){let t=this.constructor.elementProperties.get(r),i=this.constructor._$Eu(r,t);if(i!==void 0&&t.reflect===!0){let n=(t.converter?.toAttribute!==void 0?t.converter:we).toAttribute(e,t.type);this._$Em=r,n==null?this.removeAttribute(i):this.setAttribute(i,n),this._$Em=null}}_$AK(r,e){let t=this.constructor,i=t._$Eh.get(r);if(i!==void 0&&this._$Em!==i){let n=t.getPropertyOptions(i),o=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:we;this._$Em=i;let p=o.fromAttribute(e,n.type);this[i]=p??this._$Ej?.get(i)??p,this._$Em=null}}requestUpdate(r,e,t,i=!1,n){if(r!==void 0){let o=this.constructor;if(i===!1&&(n=this[r]),t??=o.getPropertyOptions(r),!((t.hasChanged??Me)(n,e)||t.useDefault&&t.reflect&&n===this._$Ej?.get(r)&&!this.hasAttribute(o._$Eu(r,t))))return;this.C(r,e,t)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(r,e,{useDefault:t,reflect:i,wrapped:n},o){t&&!(this._$Ej??=new Map).has(r)&&(this._$Ej.set(r,o??e??this[r]),n!==!0||o!==void 0)||(this._$AL.has(r)||(this.hasUpdated||t||(e=void 0),this._$AL.set(r,e)),i===!0&&this._$Em!==r&&(this._$Eq??=new Set).add(r))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let r=this.scheduleUpdate();return r!=null&&await r,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,n]of this._$Ep)this[i]=n;this._$Ep=void 0}let t=this.constructor.elementProperties;if(t.size>0)for(let[i,n]of t){let{wrapped:o}=n,p=this[i];o!==!0||this._$AL.has(i)||p===void 0||this.C(i,void 0,n,p)}}let r=!1,e=this._$AL;try{r=this.shouldUpdate(e),r?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(t){throw r=!1,this._$EM(),t}r&&this._$AE(e)}willUpdate(r){}_$AE(r){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(r)),this.updated(r)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(r){return!0}update(r){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(r){}firstUpdated(r){}};G.elementStyles=[],G.shadowRootOptions={mode:"open"},G[xe("elementProperties")]=new Map,G[xe("finalized")]=new Map,or?.({ReactiveElement:G}),(qe.reactiveElementVersions??=[]).push("2.1.2")});function qt(a,r){if(!lt(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return Tt!==void 0?Tt.createHTML(r):r}function _e(a,r,e=a,t){if(r===ae)return r;let i=t!==void 0?e._$Co?.[t]:e._$Cl,n=Se(r)?void 0:r._$litDirective$;return i?.constructor!==n&&(i?._$AO?.(!1),n===void 0?i=void 0:(i=new n(a),i._$AT(a,e,t)),t!==void 0?(e._$Co??=[])[t]=i:e._$Cl=i),i!==void 0&&(r=_e(a,i._$AS(a,r.values),i,t)),r}var ot,At,Oe,Tt,jt,ee,Nt,lr,se,Ee,Se,lt,dr,tt,ke,Ct,It,ie,Lt,Pt,Ht,dt,l,he,us,ae,h,Rt,re,cr,Ae,it,Te,ue,rt,st,at,nt,pr,Mt,De=w(()=>{ot=globalThis,At=a=>a,Oe=ot.trustedTypes,Tt=Oe?Oe.createPolicy("lit-html",{createHTML:a=>a}):void 0,jt="$lit$",ee=`lit$${Math.random().toFixed(9).slice(2)}$`,Nt="?"+ee,lr=`<${Nt}>`,se=document,Ee=()=>se.createComment(""),Se=a=>a===null||typeof a!="object"&&typeof a!="function",lt=Array.isArray,dr=a=>lt(a)||typeof a?.[Symbol.iterator]=="function",tt=`[ -\f\r]`,ke=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ct=/-->/g,It=/>/g,ie=RegExp(`>|${tt}(?:([^\\s"'>=/]+)(${tt}*=${tt}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),Lt=/'/g,Pt=/"/g,Ht=/^(?:script|style|textarea|title)$/i,dt=a=>(r,...e)=>({_$litType$:a,strings:r,values:e}),l=dt(1),he=dt(2),us=dt(3),ae=Symbol.for("lit-noChange"),h=Symbol.for("lit-nothing"),Rt=new WeakMap,re=se.createTreeWalker(se,129);cr=(a,r)=>{let e=a.length-1,t=[],i,n=r===2?"":r===3?"":"",o=ke;for(let p=0;p"?(o=i??ke,m=-1):f[1]===void 0?m=-2:(m=o.lastIndex-f[2].length,_=f[1],o=f[3]===void 0?ie:f[3]==='"'?Pt:Lt):o===Pt||o===Lt?o=ie:o===Ct||o===It?o=ke:(o=ie,i=void 0);let b=o===ie&&a[p+1].startsWith("/>")?" ":"";n+=o===ke?c+lr:m>=0?(t.push(_),c.slice(0,m)+jt+c.slice(m)+ee+b):c+ee+(m===-2?p:b)}return[qt(a,n+(a[e]||"")+(r===2?"":r===3?"":"")),t]},Ae=class a{constructor({strings:r,_$litType$:e},t){let i;this.parts=[];let n=0,o=0,p=r.length-1,c=this.parts,[_,f]=cr(r,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&&c.length0){i.textContent=Oe?Oe.emptyScript:"";for(let b=0;b2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=h}_$AI(r,e=this,t,i){let n=this.strings,o=!1;if(n===void 0)r=_e(this,r,e,0),o=!Se(r)||r!==this._$AH&&r!==ae,o&&(this._$AH=r);else{let p=r,c,_;for(r=n[0],c=0;c{let t=e?.renderBefore??r,i=t._$litPart$;if(i===void 0){let n=e?.renderBefore??null;t._$litPart$=i=new Te(r.insertBefore(Ee(),n),n,void 0,e??{})}return i._$AI(a),i}});var ct,A,_r,Ot=w(()=>{$e();$e();De();De();ct=globalThis,A=class extends G{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let r=super.createRenderRoot();return this.renderOptions.renderBefore??=r.firstChild,r}update(r){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(r),this._$Do=Mt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ae}};A._$litElement$=!0,A.finalized=!0,ct.litElementHydrateSupport?.({LitElement:A});_r=ct.litElementPolyfillSupport;_r?.({LitElement:A});(ct.litElementVersions??=[]).push("4.2.2")});var Dt=w(()=>{});var P=w(()=>{$e();De();Ot();Dt()});var Ft=w(()=>{});function x(a){return(r,e)=>typeof e=="object"?hr(a,r,e):((t,i,n)=>{let o=i.hasOwnProperty(n);return i.constructor.createProperty(n,t),o?Object.getOwnPropertyDescriptor(i,n):void 0})(a,r,e)}var ur,hr,pt=w(()=>{$e();ur={attribute:!0,type:String,converter:we,reflect:!1,hasChanged:Me},hr=(a=ur,r,e)=>{let{kind:t,metadata:i}=e,n=globalThis.litPropertyMetadata.get(i);if(n===void 0&&globalThis.litPropertyMetadata.set(i,n=new Map),t==="setter"&&((a=Object.create(a)).wrapped=!0),n.set(e.name,a),t==="accessor"){let{name:o}=e;return{set(p){let c=r.get.call(this);r.set.call(this,p),this.requestUpdate(o,c,a,!0,p)},init(p){return p!==void 0&&this.C(o,void 0,a,p),p}}}if(t==="setter"){let{name:o}=e;return function(p){let c=this[o];r.call(this,p),this.requestUpdate(o,c,a,!0,p)}}throw Error("Unsupported decorator location: "+t)}});function u(a){return x({...a,state:!0,attribute:!1})}var zt=w(()=>{pt();});var Ut=w(()=>{});var ge=w(()=>{});var Bt=w(()=>{ge();});var Wt=w(()=>{ge();});var Vt=w(()=>{ge();});var Kt=w(()=>{ge();});var Gt=w(()=>{ge();});var U=w(()=>{Ft();pt();zt();Ut();Bt();Wt();Vt();Kt();Gt()});function Yt(a,r){return!a||a<=0?0:a*(gr[r||"days"]??1)}var gr,Qt=w(()=>{"use strict";gr={days:1,weeks:7,months:30.4368,years:365.25}});function ne(a){let r=a.getFullYear(),e=String(a.getMonth()+1).padStart(2,"0"),t=String(a.getDate()).padStart(2,"0");return`${r}-${e}-${t}`}function mr(a,r){let e=[];for(let t=0;te.cost).filter(e=>typeof e=="number");return r.length===0?null:r.reduce((e,t)=>e+t,0)/r.length}function vr(a){let{windowStart:r,windowEnd:e,task:t,entryId:i,objectName:n}=a,o=[],p=(m,y)=>({date:m,entry_id:i,task_id:t.id,task_name:t.name,object_name:n,status:y&&(t.status==="overdue"||t.status==="triggered")?"ok":t.status,days_until_due:y?null:t.days_until_due??null,projected:y,schedule_type:t.schedule_type,interval_days:t.interval_days??null,interval_unit:t.interval_unit??null,responsible_user_id:t.responsible_user_id??null,avg_cost:fr(t.history),adaptive_enabled:!!t.adaptive_config?.enabled,prediction_confidence:t.threshold_prediction_confidence??null}),c=Math.max(1,Math.round(Yt(t.interval_days,t.interval_unit)));if(t.status==="overdue"||t.status==="triggered"){if(o.push(p(r,!1)),t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0){let m=ze(r,c),y=1;for(;m<=e&&y=r&&f<=e)o.push(p(f,!1));else if(f>e)return o;if(t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0){let m=ze(f,c),y=o.length;for(;m<=e&&y=r&&(o.push(p(m,!0)),y++),m=ze(m,c)}return o}function Xt(a,r,e,t=null){let i=mr(r,e),n=i[0],o=i[i.length-1],p=[];for(let _ of a){let f=_.object?.name||"",m=_.entry_id,y=_.tasks||[];for(let b of y){if(t&&b.responsible_user_id!==t||b.enabled===!1)continue;let k=vr({windowStart:n,windowEnd:o,task:b,entryId:m,objectName:f});p.push(...k)}}let c=new Map;for(let _ of i)c.set(_,[]);for(let _ of p){let f=c.get(_.date);f&&f.push(_)}for(let[,_]of c)_.sort((f,m)=>{let y=Zt[f.status]??99,b=Zt[m.status]??99;if(y!==b)return y-b;if(f.projected!==m.projected)return f.projected?1:-1;let k=f.object_name.localeCompare(m.object_name);return k!==0?k:f.task_name.localeCompare(m.task_name)});return i.map(_=>({date:_,events:c.get(_)??[]}))}function br(a,r){let e=[];for(let t=r-1;t>=0;t--){let i=new Date(a);i.setDate(i.getDate()-t),i.setHours(0,0,0,0),e.push(ne(i))}return e}function ei(a,r,e,t=null){let i=br(r,e),n=i[0],o=i[i.length-1],p=new Map;for(let _ of i)p.set(_,[]);for(let _ of a){let f=_.object?.name||"",m=_.entry_id,y=_.tasks||[];for(let b of y){if(t&&b.responsible_user_id!==t)continue;let k=b.history||[];for(let E of k){if(typeof E?.timestamp!="string")continue;let D=E.timestamp.slice(0,10);if(Do)continue;let K=p.get(D);if(!K)continue;let M=E.type??"completed";K.push({date:D,entry_id:m,task_id:b.id,task_name:b.name,object_name:f,status:yr[M]??"ok",days_until_due:null,projected:!1,schedule_type:b.schedule_type,interval_days:b.interval_days??null,responsible_user_id:b.responsible_user_id??null,avg_cost:typeof E.cost=="number"?E.cost:null,adaptive_enabled:!!b.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:E.timestamp,history_type:M,history_cost:typeof E.cost=="number"?E.cost:null,history_notes:typeof E.notes=="string"?E.notes:null,history_duration:typeof E.duration=="number"?E.duration:null})}}}let c={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,_]of p)_.sort((f,m)=>{let y=c[f.history_type??""]??99,b=c[m.history_type??""]??99;if(y!==b)return y-b;let k=f.object_name.localeCompare(m.object_name);return k!==0?k:f.task_name.localeCompare(m.task_name)});return i.map(_=>({date:_,events:p.get(_)??[]}))}var Jt,Zt,yr,_t=w(()=>{"use strict";Qt();Jt=5;Zt={overdue:0,triggered:1,due_soon:2,ok:3};yr={completed:"ok",reset:"ok",skipped:"due_soon",missed:"overdue",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"}});var ri,ii=w(()=>{ri={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",trigger_removed:"Trigger removed",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter +/*! maintenance_supporter frontend 2.65.0 */ +var $t=Object.defineProperty;var ts=Object.getOwnPropertyDescriptor;var w=(r,s,e)=>()=>{if(e)throw e[0];try{return r&&(s=r(r=0)),s}catch(t){throw e=[t],t}};var is=(r,s)=>{for(var e in s)$t(r,e,{get:s[e],enumerable:!0})};var c=(r,s,e,t)=>{for(var i=t>1?void 0:t?ts(s,e):s,n=r.length-1,o;n>=0;n--)(o=r[n])&&(i=(t?o(s,e,i):o(i))||i);return t&&i&&$t(s,e,i),i};var je,Ne,Xe,kt,be,Et,S,St,et,tt=w(()=>{je=globalThis,Ne=je.ShadowRoot&&(je.ShadyCSS===void 0||je.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Xe=Symbol(),kt=new WeakMap,be=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Xe)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=kt.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&kt.set(e,s))}return s}toString(){return this.cssText}},Et=r=>new be(typeof r=="string"?r:r+"",void 0,Xe),S=(r,...s)=>{let e=r.length===1?r[0]:s.reduce((t,i,n)=>t+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+r[n+1],r[0]);return new be(e,r,Xe)},St=(r,s)=>{if(Ne)r.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,r.appendChild(t)}},et=Ne?r=>r:r=>r instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return Et(e)})(r):r});var ss,rs,as,ns,os,ls,He,At,cs,ds,xe,we,De,Tt,G,$e=w(()=>{tt();tt();({is:ss,defineProperty:rs,getOwnPropertyDescriptor:as,getOwnPropertyNames:ns,getOwnPropertySymbols:os,getPrototypeOf:ls}=Object),He=globalThis,At=He.trustedTypes,cs=At?At.emptyScript:"",ds=He.reactiveElementPolyfillSupport,xe=(r,s)=>r,we={toAttribute(r,s){switch(s){case Boolean:r=r?cs:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,s){let e=r;switch(s){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},De=(r,s)=>!ss(r,s),Tt={attribute:!0,type:String,converter:we,reflect:!1,useDefault:!1,hasChanged:De};Symbol.metadata??=Symbol("metadata"),He.litPropertyMetadata??=new WeakMap;G=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=Tt){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&&rs(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=as(this.prototype,s)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let p=i?.call(this);n?.call(this,o),this.requestUpdate(s,p,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??Tt}static _$Ei(){if(this.hasOwnProperty(xe("elementProperties")))return;let s=ls(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(xe("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(xe("properties"))){let e=this.properties,t=[...ns(e),...os(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(et(i))}else s!==void 0&&e.push(et(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 St(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(s=>s.hostConnected?.())}enableUpdating(s){}disconnectedCallback(){this._$EO?.forEach(s=>s.hostDisconnected?.())}attributeChangedCallback(s,e,t){this._$AK(s,t)}_$ET(s,e){let t=this.constructor.elementProperties.get(s),i=this.constructor._$Eu(s,t);if(i!==void 0&&t.reflect===!0){let n=(t.converter?.toAttribute!==void 0?t.converter:we).toAttribute(e,t.type);this._$Em=s,n==null?this.removeAttribute(i):this.setAttribute(i,n),this._$Em=null}}_$AK(s,e){let t=this.constructor,i=t._$Eh.get(s);if(i!==void 0&&this._$Em!==i){let n=t.getPropertyOptions(i),o=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:we;this._$Em=i;let p=o.fromAttribute(e,n.type);this[i]=p??this._$Ej?.get(i)??p,this._$Em=null}}requestUpdate(s,e,t,i=!1,n){if(s!==void 0){let o=this.constructor;if(i===!1&&(n=this[s]),t??=o.getPropertyOptions(s),!((t.hasChanged??De)(n,e)||t.useDefault&&t.reflect&&n===this._$Ej?.get(s)&&!this.hasAttribute(o._$Eu(s,t))))return;this.C(s,e,t)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(s,e,{useDefault:t,reflect:i,wrapped:n},o){t&&!(this._$Ej??=new Map).has(s)&&(this._$Ej.set(s,o??e??this[s]),n!==!0||o!==void 0)||(this._$AL.has(s)||(this.hasUpdated||t||(e=void 0),this._$AL.set(s,e)),i===!0&&this._$Em!==s&&(this._$Eq??=new Set).add(s))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let s=this.scheduleUpdate();return s!=null&&await s,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,n]of this._$Ep)this[i]=n;this._$Ep=void 0}let t=this.constructor.elementProperties;if(t.size>0)for(let[i,n]of t){let{wrapped:o}=n,p=this[i];o!==!0||this._$AL.has(i)||p===void 0||this.C(i,void 0,n,p)}}let s=!1,e=this._$AL;try{s=this.shouldUpdate(e),s?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(t){throw s=!1,this._$EM(),t}s&&this._$AE(e)}willUpdate(s){}_$AE(s){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(s)),this.updated(s)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(s){return!0}update(s){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(s){}firstUpdated(s){}};G.elementStyles=[],G.shadowRootOptions={mode:"open"},G[xe("elementProperties")]=new Map,G[xe("finalized")]=new Map,ds?.({ReactiveElement:G}),(He.reactiveElementVersions??=[]).push("2.1.2")});function Mt(r,s){if(!ct(r)||!r.hasOwnProperty("raw"))throw Error("invalid template strings array");return It!==void 0?It.createHTML(s):s}function ue(r,s,e=r,t){if(s===ae)return s;let i=t!==void 0?e._$Co?.[t]:e._$Cl,n=Se(s)?void 0:s._$litDirective$;return i?.constructor!==n&&(i?._$AO?.(!1),n===void 0?i=void 0:(i=new n(r),i._$AT(r,e,t)),t!==void 0?(e._$Co??=[])[t]=i:e._$Cl=i),i!==void 0&&(s=ue(r,i._$AS(r,s.values),i,t)),s}var lt,Ct,Me,It,Nt,ee,Ht,ps,re,Ee,Se,ct,us,it,ke,Pt,Lt,ie,Rt,qt,Dt,dt,l,_e,vr,ae,_,jt,se,hs,Ae,st,Te,he,rt,at,nt,ot,_s,Ot,Oe=w(()=>{lt=globalThis,Ct=r=>r,Me=lt.trustedTypes,It=Me?Me.createPolicy("lit-html",{createHTML:r=>r}):void 0,Nt="$lit$",ee=`lit$${Math.random().toFixed(9).slice(2)}$`,Ht="?"+ee,ps=`<${Ht}>`,re=document,Ee=()=>re.createComment(""),Se=r=>r===null||typeof r!="object"&&typeof r!="function",ct=Array.isArray,us=r=>ct(r)||typeof r?.[Symbol.iterator]=="function",it=`[ +\f\r]`,ke=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Pt=/-->/g,Lt=/>/g,ie=RegExp(`>|${it}(?:([^\\s"'>=/]+)(${it}*=${it}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),Rt=/'/g,qt=/"/g,Dt=/^(?:script|style|textarea|title)$/i,dt=r=>(s,...e)=>({_$litType$:r,strings:s,values:e}),l=dt(1),_e=dt(2),vr=dt(3),ae=Symbol.for("lit-noChange"),_=Symbol.for("lit-nothing"),jt=new WeakMap,se=re.createTreeWalker(re,129);hs=(r,s)=>{let e=r.length-1,t=[],i,n=s===2?"":s===3?"":"",o=ke;for(let p=0;p"?(o=i??ke,f=-1):m[1]===void 0?f=-2:(f=o.lastIndex-m[2].length,u=m[1],o=m[3]===void 0?ie:m[3]==='"'?qt:Rt):o===qt||o===Rt?o=ie:o===Pt||o===Lt?o=ke:(o=ie,i=void 0);let b=o===ie&&r[p+1].startsWith("/>")?" ":"";n+=o===ke?d+ps:f>=0?(t.push(u),d.slice(0,f)+Nt+d.slice(f)+ee+b):d+ee+(f===-2?p:b)}return[Mt(r,n+(r[e]||"")+(s===2?"":s===3?"":"")),t]},Ae=class r{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,o=0,p=s.length-1,d=this.parts,[u,m]=hs(s,e);if(this.el=r.createElement(u,t),se.currentNode=this.el.content,e===2||e===3){let f=this.el.content.firstChild;f.replaceWith(...f.childNodes)}for(;(i=se.nextNode())!==null&&d.length0){i.textContent=Me?Me.emptyScript:"";for(let b=0;b2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=_}_$AI(s,e=this,t,i){let n=this.strings,o=!1;if(n===void 0)s=ue(this,s,e,0),o=!Se(s)||s!==this._$AH&&s!==ae,o&&(this._$AH=s);else{let p=s,d,u;for(s=n[0],d=0;d{let t=e?.renderBefore??s,i=t._$litPart$;if(i===void 0){let n=e?.renderBefore??null;t._$litPart$=i=new Te(s.insertBefore(Ee(),n),n,void 0,e??{})}return i._$AI(r),i}});var pt,A,gs,zt=w(()=>{$e();$e();Oe();Oe();pt=globalThis,A=class extends G{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let s=super.createRenderRoot();return this.renderOptions.renderBefore??=s.firstChild,s}update(s){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(s),this._$Do=Ot(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ae}};A._$litElement$=!0,A.finalized=!0,pt.litElementHydrateSupport?.({LitElement:A});gs=pt.litElementPolyfillSupport;gs?.({LitElement:A});(pt.litElementVersions??=[]).push("4.2.2")});var Ft=w(()=>{});var L=w(()=>{$e();Oe();zt();Ft()});var Ut=w(()=>{});function x(r){return(s,e)=>typeof e=="object"?fs(r,s,e):((t,i,n)=>{let o=i.hasOwnProperty(n);return i.constructor.createProperty(n,t),o?Object.getOwnPropertyDescriptor(i,n):void 0})(r,s,e)}var ms,fs,ut=w(()=>{$e();ms={attribute:!0,type:String,converter:we,reflect:!1,hasChanged:De},fs=(r=ms,s,e)=>{let{kind:t,metadata:i}=e,n=globalThis.litPropertyMetadata.get(i);if(n===void 0&&globalThis.litPropertyMetadata.set(i,n=new Map),t==="setter"&&((r=Object.create(r)).wrapped=!0),n.set(e.name,r),t==="accessor"){let{name:o}=e;return{set(p){let d=s.get.call(this);s.set.call(this,p),this.requestUpdate(o,d,r,!0,p)},init(p){return p!==void 0&&this.C(o,void 0,r,p),p}}}if(t==="setter"){let{name:o}=e;return function(p){let d=this[o];s.call(this,p),this.requestUpdate(o,d,r,!0,p)}}throw Error("Unsupported decorator location: "+t)}});function h(r){return x({...r,state:!0,attribute:!1})}var Bt=w(()=>{ut();});var Wt=w(()=>{});var ge=w(()=>{});var Vt=w(()=>{ge();});var Kt=w(()=>{ge();});var Gt=w(()=>{ge();});var Yt=w(()=>{ge();});var Qt=w(()=>{ge();});var U=w(()=>{Ut();ut();Bt();Wt();Vt();Kt();Gt();Yt();Qt()});function Jt(r,s){return!r||r<=0?0:r*(vs[s||"days"]??1)}var vs,Zt=w(()=>{"use strict";vs={days:1,weeks:7,months:30.4368,years:365.25}});function ne(r){let s=r.getFullYear(),e=String(r.getMonth()+1).padStart(2,"0"),t=String(r.getDate()).padStart(2,"0");return`${s}-${e}-${t}`}function ys(r,s){let e=[];for(let t=0;te.cost).filter(e=>typeof e=="number");return s.length===0?null:s.reduce((e,t)=>e+t,0)/s.length}function xs(r){let{windowStart:s,windowEnd:e,task:t,entryId:i,objectName:n}=r,o=[],p=(f,v)=>({date:f,entry_id:i,task_id:t.id,task_name:t.name,object_name:n,status:v&&(t.status==="overdue"||t.status==="triggered")?"ok":t.status,days_until_due:v?null:t.days_until_due??null,projected:v,schedule_type:t.schedule_type,interval_days:t.interval_days??null,interval_unit:t.interval_unit??null,responsible_user_id:t.responsible_user_id??null,avg_cost:bs(t.history),adaptive_enabled:!!t.adaptive_config?.enabled,prediction_confidence:t.threshold_prediction_confidence??null}),d=Math.max(1,Math.round(Jt(t.interval_days,t.interval_unit)));if(t.status==="overdue"||t.status==="triggered"){if(o.push(p(s,!1)),t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0){let f=Fe(s,d),v=1;for(;f<=e&&v=s&&m<=e)o.push(p(m,!1));else if(m>e)return o;if(t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0){let f=Fe(m,d),v=o.length;for(;f<=e&&v=s&&(o.push(p(f,!0)),v++),f=Fe(f,d)}return o}function ti(r,s,e,t=null){let i=ys(s,e),n=i[0],o=i[i.length-1],p=[];for(let u of r){let m=u.object?.name||"",f=u.entry_id,v=u.tasks||[];for(let b of v){if(t&&b.responsible_user_id!==t||b.enabled===!1)continue;let k=xs({windowStart:n,windowEnd:o,task:b,entryId:f,objectName:m});p.push(...k)}}let d=new Map;for(let u of i)d.set(u,[]);for(let u of p){let m=d.get(u.date);m&&m.push(u)}for(let[,u]of d)u.sort((m,f)=>{let v=ei[m.status]??99,b=ei[f.status]??99;if(v!==b)return v-b;if(m.projected!==f.projected)return m.projected?1:-1;let k=m.object_name.localeCompare(f.object_name);return k!==0?k:m.task_name.localeCompare(f.task_name)});return i.map(u=>({date:u,events:d.get(u)??[]}))}function $s(r,s){let e=[];for(let t=s-1;t>=0;t--){let i=new Date(r);i.setDate(i.getDate()-t),i.setHours(0,0,0,0),e.push(ne(i))}return e}function ii(r,s,e,t=null){let i=$s(s,e),n=i[0],o=i[i.length-1],p=new Map;for(let u of i)p.set(u,[]);for(let u of r){let m=u.object?.name||"",f=u.entry_id,v=u.tasks||[];for(let b of v){if(t&&b.responsible_user_id!==t)continue;let k=b.history||[];for(let E of k){if(typeof E?.timestamp!="string")continue;let O=E.timestamp.slice(0,10);if(Oo)continue;let K=p.get(O);if(!K)continue;let D=E.type??"completed";K.push({date:O,entry_id:f,task_id:b.id,task_name:b.name,object_name:m,status:ws[D]??"ok",days_until_due:null,projected:!1,schedule_type:b.schedule_type,interval_days:b.interval_days??null,responsible_user_id:b.responsible_user_id??null,avg_cost:typeof E.cost=="number"?E.cost:null,adaptive_enabled:!!b.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:E.timestamp,history_type:D,history_cost:typeof E.cost=="number"?E.cost:null,history_notes:typeof E.notes=="string"?E.notes:null,history_duration:typeof E.duration=="number"?E.duration:null})}}}let d={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,u]of p)u.sort((m,f)=>{let v=d[m.history_type??""]??99,b=d[f.history_type??""]??99;if(v!==b)return v-b;let k=m.object_name.localeCompare(f.object_name);return k!==0?k:m.task_name.localeCompare(f.task_name)});return i.map(u=>({date:u,events:p.get(u)??[]}))}var Xt,ei,ws,ht=w(()=>{"use strict";Zt();Xt=5;ei={overdue:0,triggered:1,due_soon:2,ok:3};ws={completed:"ok",reset:"ok",skipped:"due_soon",missed:"overdue",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"}});var ai,ri=w(()=>{ai={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",trigger_removed:"Trigger removed",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter 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",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_window_7:"7 days",cal_window_14:"14 days",cal_window_30:"30 days",cal_window_365:"1 year",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",qr_print:"Print",qr_download:"Download SVG",qr_action:"Action on scan",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",analysis:"Analysis",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",no_advanced_features:"No advanced features enabled",no_advanced_features_hint:"Enable \u201CAdaptive Intervals\u201D or \u201CSeasonal Patterns\u201D in the integration settings to see analysis data here.",analysis_not_enough_data:"Not enough data for analysis yet.",analysis_not_enough_data_hint:"Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",analysis_manual_task_hint:"Manual tasks without an interval do not generate analysis data.",completions:"completions",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_priority_help:"Empty = show all priorities. Tasks without an explicit priority count as Normal.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects and tasks\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_export_settings:"Export settings (JSON)",settings_import_csv:"Import CSV",settings_import_placeholder:"Paste JSON or CSV content here\u2026",settings_import_btn:"Import",settings_import_success:"{count} objects imported successfully.",settings_export_success:"Export downloaded.",settings_saved:"Setting saved.",settings_include_history:"Include history",settings_export_selection:"Limit to selected objects (optional)",settings_docs_archive:"Documents archive (with files)",settings_docs_archive_hint:"The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",settings_docs_export_btn:"Download documents ZIP",settings_docs_import_btn:"Restore documents ZIP",settings_docs_import_success:"Restored: {blobs} files, {docs} documents",sort_alphabetical:"Alphabetical",sort_due_soonest:"Due soonest",sort_task_count:"Task count",sort_area:"Area",sort_assigned_user:"Assigned user",sort_group:"Group",groupby_none:"No grouping",groupby_area:"By area",groupby_group:"By group",groupby_user:"By user",filter_label:"Filter",user_label:"User",photo_label:"Photo",sort_label:"Sort",group_by_label:"Group by",state_value_help:'Use the HA state value (usually lowercase, e.g. "on"/"off"). Case is normalised on save.',target_changes_help:"Number of matching transitions before the trigger fires (default: 1).",for_minutes_state_help:"0 counts every change immediately. Set minutes and the new state must hold that long first \u2014 brief flickers then neither trigger nor count.",qr_print_title:"Print QR codes",qr_print_desc:"Generate a printable page of QR codes to cut out and stick on your equipment.",qr_print_load:"Load objects",qr_print_filter:"Filter",qr_print_objects:"Objects",qr_print_actions:"Actions",qr_print_url_mode:"Link type",qr_print_estimate:"Estimated QR codes",qr_print_over_limit:"cap is 200, narrow the filter",qr_print_generate:"Generate QR codes",qr_print_generating:"Generating\u2026",qr_print_ready:"QR codes ready",qr_print_print_button:"Print",qr_print_empty:"Nothing to generate",qr_action_skip:"Skip",vacation_title:"Vacation mode",vacation_active:"active",vacation_ended:"ended",vacation_desc:"Plan a vacation: notifications are paused during the period plus a buffer of days. You can opt specific tasks back in.",vacation_enable:"Enable vacation mode",vacation_start:"Start",vacation_end:"End",vacation_buffer:"Buffer (days)",vacation_exempt_title:"Notify anyway during vacation",vacation_exempt_desc:"Pick tasks that should still notify during vacation (e.g. critical pool chemistry).",vacation_load_tasks:"Load tasks",vacation_preview_btn:"Show preview",vacation_preview_affected:"tasks affected",vacation_event_due_soon:"becomes due soon",vacation_event_overdue:"becomes overdue",vacation_event_triggered_est:"sensor trigger possible",vacation_sensor_based:"(sensor-based)",vacation_action_notify:"Notify anyway",vacation_action_unsilence:"Silence again",vacation_marked_complete:"Marked complete",vacation_marked_skip:"Skipped",vacation_end_now:"End vacation now",add:"Add",show_stats:"Show stats + graphs",hide_stats:"Hide stats",adaptive_no_data:"Not enough completion history yet for adaptive analysis. Complete this task a few more times to unlock interval recommendations and reliability charts.",suggestion_applied:"Suggested interval applied",vacation_mode:"Vacation mode",vacation_status_active:"Active now",vacation_status_scheduled:"Scheduled",vacation_status_inactive:"Inactive",vacation_end_now_confirm:"End vacation immediately?",vacation_exempt_count:"exempt",vacation_advanced:"Advanced\u2026",vacation_open_panel:"Open in panel",enable:"Enable",saved:"Saved",budget_monthly_set:"Set monthly",budget_yearly_set:"Set yearly",budget_advanced:"Currency, alerts\u2026",budget_open_panel:"Open in panel",groups_empty:"No groups yet.",group_new_placeholder:"Add group\u2026",group_delete_confirm:'Delete group "{name}"?',groups_manage_tasks:"Manage task assignments\u2026",groups_open_panel:"Open in panel",unassigned:"Unassigned",no_area:"No area",has_overdue:"Has overdue tasks",object:"Object",settings_panel_access:"Panel access",settings_panel_access_desc:"Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below \u2014 everyone else sees only Complete and Skip.",settings_operator_write:"Allow selected users to create, edit & delete",settings_operator_write_desc:"Off: only admins can change content. On: the selected users below get full access too.",no_non_admin_users:"No non-admin users found. Add some in Settings \u2192 People.",owner_label:"Owner",feat_completion_actions:"Completion actions",feat_completion_actions_desc:"Per-task HA action on complete + quick-complete QR with pre-set values.",on_complete_action_title:"On complete: trigger HA action (optional)",on_complete_action_desc:"Calls an HA service when the task is completed \u2014 e.g. reset a counter on the device.",on_complete_action_service:"Service",on_complete_action_target:"Target entity",on_complete_action_target_hint:"Note: the entity domain must match the service \u2014 e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",on_complete_action_data:"Data (JSON, optional)",on_complete_action_test:"Validate configuration",on_complete_action_test_success:"\u2713 Configuration valid (action will fire only on task completion)",on_complete_action_test_failed:"Failed",quick_complete_defaults_title:"Quick-complete defaults (for QR scans, optional)",quick_complete_defaults_desc:"Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",quick_complete_defaults_notes:"Notes",quick_complete_defaults_cost:"Cost",quick_complete_defaults_duration:"Duration (minutes)",quick_complete_defaults_feedback_none:"No feedback",quick_complete_defaults_feedback_needed:"Was needed",quick_complete_defaults_feedback_not_needed:"Not needed",quick_complete_success:"Quickly marked complete",show_all_objects:"Show all objects",show_all_tasks:"Clear filter \u2014 show all tasks",filter_to_overdue:"Filter task list to overdue only",filter_to_due_soon:"Filter task list to due-soon only",filter_to_triggered:"Filter task list to triggered only",open_task:"Open task",show_details:"Show history + stats",hide_details:"Hide details",history_empty:"No history yet.",history_edit_button:"Edit entry",total_cost:"Total cost",times_performed:"Performed",older_entries:"older",open_in_panel:"Open in Maintenance panel",skip_reason:"Skip reason (optional)",reset_to_date:"Reset last_performed to",delete_task_confirm:"Delete this task and its history?",delete_object_confirm:"Delete this object and all its tasks?",loading:"Loading\u2026",archive:"Archive",undo:"Undo",task_archived:"Task archived",object_archived:"Object archived",unarchive:"Unarchive",archived:"Archived",show_archived:"Show archived",hide_archived:"Hide archived",confirm_archive_object:"Archive this object and its tasks? They keep their history and can be unarchived later.",settings_archive:"Archive & Retention",settings_archive_desc:"Retire completed one-off tasks without deleting them. Archived items are hidden and inert but keep their history and cost.",settings_archive_oneoff_days:"Auto-archive completed one-off tasks after (days, 0 = off)",settings_delete_archived_oneoff_days:"Auto-delete archived one-off tasks after (days, 0 = never)",archive_object:"Archive object",unarchive_object:"Unarchive object",documents:"Documents",documents_empty:"No documents yet.",doc_upload:"Upload file",doc_uploading:"Uploading\u2026",doc_add_link:"Add link",doc_link_url:"URL (https://\u2026)",doc_link_title:"Title (optional)",doc_open:"Open",doc_delete_confirm:'Delete "{name}"?',doc_too_large:"File is too large (max 25 MB).",doc_upload_failed:"Upload failed.",completion_photo_optional:"Completion photo (optional)",add_photo:"Add photo",uploading:"Uploading\u2026",remove:"Remove",doc_deduped:"Already stored elsewhere \u2014 shared, no extra space used.",doc_dup_in_object:"This file is already attached to this object.",doc_link_invalid:"Only http/https links are allowed.",doc_cat_manual:"Manual",doc_cat_warranty:"Warranty",doc_cat_invoice:"Invoice",doc_cat_spare_parts:"Spare parts",doc_cat_photo:"Photo",doc_cat_other:"Other",doc_link_badge:"Link",doc_storage_title:"Document storage",doc_storage_saved:"Saved via deduplication",doc_storage_refresh:"Refresh",doc_download:"Download",doc_close:"Close",doc_camera:"Take photo",doc_drop_hint:"Drop files here",doc_task_none:"No documents linked to this task.",doc_link_existing:"Link a document\u2026",doc_attach:"Link",doc_unlink:"Unlink",doc_page:"Page",chart_range_7d:"7d",chart_range_30d:"30d",chart_range_90d:"90d",chart_range_1y:"1y",chart_since_service:"since last service",chart_no_stats:"No long-term statistics for this entity \u2014 showing maintenance-event values only",auto_complete_on_recovery:"Auto-complete when the sensor recovers",auto_complete_on_recovery_help:"Records a completion (sets last performed) when the trigger clears itself \u2014 e.g. salt refilled, filter replaced.",doc_search:"Search documents\u2026",doc_search_none:"No matching documents",link_device_optional:"Link to existing device (optional)",parent_object_optional:"Parent object (optional)",parent_none:"(No parent)",paused:"Paused",pause_object:"Pause",resume_object:"Resume",pause_until_prompt:"Freeze this object's schedules \u2014 nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",pause_until_label:"Resume on (optional)",object_paused:"Object paused",object_resumed:"Object resumed \u2014 schedules restarted",object_paused_badge:"Paused",paused_until_label:"until",replace_object:"Replace\u2026",replace_object_prompt:"Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",replace_name_label:"Successor name",object_replaced:"Object replaced \u2014 successor created",reading_unit_label:"Reading unit (e.g. kWh, m\xB3)",reading_unit_help:"Shown next to the recorded value when completing this task.",reading_value_label:"Reading value",reading_label:"Reading",settings_templates_label:"Template gallery",settings_templates_hint:`Untick templates you'll never need \u2014 they disappear from the "From template" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.`,worksheet:"Work sheet",worksheet_scan_view:"Scan to open the task",worksheet_scan_complete:"Scan to complete",worksheet_manual_excerpt:"Manual excerpt",worksheet_pages:"pages",worksheet_printed:"Printed",worksheet_never:"Never",card_all_caught_up:"All caught up \u2014 nothing needs attention",postpone:"Postpone",postpone_date_prompt:"Postpone this occurrence to which date?",postpone_date_label:"New due date",postponed:"Postponed",postponed_to:"Postponed to",season_window_label:"Seasonal window (months)",season_window_hint:"Only due in the selected months; off-season dates roll to the next active month. None = all year.",series_end_label:"Ends",series_end_never:"Never (repeats indefinitely)",series_end_after_count:"After a number of times",series_end_until:"On a date",series_end_count_label:"Number of times",series_end_until_label:"End date",parts_section:"Parts & consumables",parts_inventory_value:"Inventory value",part_add:"Add part",part_name:"Name",part_vendor:"Manufacturer",part_storage_location:"Storage location",part_product_url:"Product URL",part_unit:"Unit",part_cost:"Unit price",part_stock:"Stock",part_reorder_threshold:"Reorder at",part_restock_quantity:"Restock quantity",part_auto_buy:"Auto-create buy task when low",part_restock:"Adjust stock",parts_used_by:"Used by",restock_quantity_label:"Quantity bought",consumes_parts_label:"Consumes parts",shared_parts_other_objects:"Parts from other objects",shared_parts_help:"Several objects can share one stock. Completing this task takes from the owning object.",shared_part_unknown:"Unknown part",parts_load_failed:"Couldn't load this object's parts \u2014 the consumes-parts options are unavailable right now.",adopt_problem_button:"Adopt problem sensors",adopt_problem_title:"Adopt problem sensors",adopt_problem_hint:"Turn HA problem sensors (printer errors, filter warnings, low battery) into maintenance tasks that trigger while the problem is active and clear themselves when it resolves.",adopt_problem_none:"No problem sensors found that aren't already tracked.",adopt_problem_active:"active",adopt_problem_ok:"ok",adopt_problem_new_object:"(new)",adopt_problem_adopt:"Adopt selected",adopt_problem_done:"Adopted {tasks} problem sensor(s)",views_label:"Views",views_none:"\u2014 No view \u2014",views_manage:"Save / manage views",views_dialog_title:"Saved views",views_dialog_hint:"Save the current filters as a named view everyone can reuse.",views_name_placeholder:"View name",views_save_current:"Save current filters",views_none_yet:"No saved views yet.",close:"Close",trigger_hint_now:"The sensor reads {value} right now.",trigger_hint_above:"The task triggers once it rises above {target}.",trigger_hint_below:"It triggers once it falls below {target}.",trigger_hint_counter_delta:"Counts from the current reading ({value}): due at {due} (+{target}), and the count restarts after each completion.",trigger_hint_counter_delta_edit:"Counts usage since the last completion: due after +{target}; the count restarts after each completion.",trigger_hint_counter_abs:"The task becomes due once the sensor reaches {target}.",trigger_hint_runtime:"The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",trigger_hint_state_change:"The task becomes due after {count} state change(s).",trigger_hint_state_change_to:"The task becomes due after {count} change(s) to \u201C{state}\u201D.",trigger_hint_state_now:"Current state: {value}.",adopt_problem_part:"Uses part: {name}",label_filter:"Label",all_labels:"All labels",settings_notify_scope:"Notify only for view",settings_notify_scope_all:"All tasks",settings_notify_scope_hint:"Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",card_saved_view:"Saved view",card_saved_view_none:"None",card_saved_view_help:"Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",doc_part_none:"No documents linked to this part.",settings_templates_toggle_group:"Enable or disable all templates in this group",setups_button:"Suggested setups",setups_title:"Suggested setups (Beta)",setups_hint:"Devices of supported integrations whose consumable sensors can drive maintenance tasks. Adopting creates the object and wires each task to its sensor \u2014 it triggers when the consumable runs low and resolves itself after replacement.",setups_none:"No supported devices with unwired consumable sensors found.",setups_adopt:"Set up selected",setups_done:"{tasks} sensor-wired tasks created.",complete_parts_used:"Parts used this time",part_delete_confirm:"Delete part '{name}'? Its stock tracking, task links and any open buy reminder will be removed.",baseline_start_value:"Start reading (optional)",baseline_start_help:"Counting starts from this reading. Leave empty to count from the current value; enter the reading at the last service so usage since then already counts.",setups_baseline_hint:"reading at last service (optional)",baseline_start_help_edit:"Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",baseline_current_effective:"Currently effective start value: {value}",runtime_on_states:"Active states",runtime_on_states_help:"States that count as running \u2014 default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",setups_target_new:"Create new: {name}",schedule_preview_title:"Next dates",schedule_preview_ontime:"Assuming on-time completion.",schedule_preview_ends:"(series ends)",adopt_problem_responsible:"Responsible user for all adopted tasks (optional)",adopt_for_minutes_hint:"Only trigger once the problem has persisted this long \u2014 0 reacts to the first flicker.",adopt_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_add:"Add a battery",battery_fleet_add_hint:"Pick a battery sensor the automatic discovery missed \u2014 it joins the roster immediately.",battery_fleet_track_self:"Track self-charging batteries",battery_fleet_track_self_hint:"Phones, vacuums and other devices that recharge themselves appear as rechargeables \u2014 a low one asks for a charge, never for new cells.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",cal_editor_window:"Default window",cal_editor_window_week:"Week (7 days)",cal_editor_window_fortnight:"Fortnight (14 days)",cal_editor_window_month:"Month (30 days, default)",cal_editor_window_year:"Year (365 days, empty days collapsed)",cal_editor_show_chips:"Show window chips inside the card",cal_editor_chips_hint:"Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",cal_editor_show_user_filter:"Show user filter dropdown",cal_editor_default_user:"Default user filter",cal_editor_my_tasks:"My tasks (current user)",cal_editor_show_object_filter:"Show object filter dropdown",cal_editor_object_hint:'Pre-select one object via YAML: object_filter: "" \u2014 or a list of names to restrict the card to several objects.',object_history_section:"History (all tasks)",object_history_all_tasks:"All tasks",object_history_empty:"No entries in this range.",object_history_cap_note:"History keeps up to 500 entries per task \u2014 very old entries may be missing.",service_record_title:"Service record",service_record_print:"Service record (PDF)",date:"Date",service_record_entries:"entries",completed_by:"Completed by",date_from:"From",date_to:"To"}});var si,ai=w(()=>{"use strict";si="2.64.0"});var Ce,ni=w(()=>{"use strict";Ce={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)"}});function wr(a){oe.en=Object.assign({},a,oe.en??{})}function We(a){let r=(a||ut).toLowerCase();return r.startsWith("pt")&&r.endsWith("br")?"pt-br":r.substring(0,2)}function s(a,r){let e=We(r);return oe[e]?.[a]??oe.en[a]??a}function di(a,r){r.has("hass")&&Sr(a.hass?.locale);let e=a.hass?.language;e&&!Re(e)&&je(e).then(()=>a.requestUpdate())}function N(a){return a?.language||"en"}function Re(a){let r=We(a);return r===ut||r in oe}function je(a){let r=We(a);return r===ut||r in oe||!$r.has(r)?Promise.resolve():(r in Ie||(Ie[r]=fetch(`${kr}/${r}.json?v=${si}`).then(e=>e.ok?e.json():null).then(e=>{e?oe[r]=e:delete Ie[r]}).catch(()=>{delete Ie[r]})),Ie[r])}function Le(a){let r=We(a);return{de:"de-DE",en:"en-US",nl:"nl-NL",fr:"fr-FR",it:"it-IT",es:"es-ES",pt:"pt-PT",ru:"ru-RU",uk:"uk-UA",zh:"zh-CN",da:"da-DK",fi:"fi-FI",nb:"nb-NO",ja:"ja-JP",hi:"hi-IN",pl:"pl-PL",cs:"cs-CZ",sv:"sv-SE","pt-br":"pt-BR",hu:"hu-HU",ko:"ko-KR",tr:"tr-TR"}[r]??"en-US"}function Sr(a){a&&(Ue.date=a.date_format,Ue.time=a.time_format)}function ci(a,r){let e=String(a.getDate()).padStart(2,"0"),t=String(a.getMonth()+1).padStart(2,"0"),i=String(a.getFullYear());switch(Ue.date){case"DMY":return`${e}/${t}/${i}`;case"MDY":return`${t}/${e}/${i}`;case"YMD":return`${i}-${t}-${e}`;case"system":return a.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return a.toLocaleDateString(Le(r),{day:"2-digit",month:"2-digit",year:"numeric"})}}function Ar(a,r){switch(Ue.time){case"12":return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return a.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit"})}}function Y(a,r){if(!a)return"\u2014";try{let e=a.includes("T")?a:a+"T00:00:00";return ci(new Date(e),r)}catch{return a}}function pi(a,r){if(!a)return"\u2014";try{let e=new Date(a);return ci(e,r)+" "+Ar(e,r)}catch{return a}}function _i(a,r){if(a==null)return"\u2014";let e=r||"en";return a<0?`${Math.abs(a)} ${s("d_overdue",e)}`:a===0?s("today",e):`${a} ${s(a===1?"day":"days",e)}`}function Be(a,r,e){return a==null?"\u2014":`${a} ${s("unit_"+(r||"days"),e)}`}function Pe(a,r,e="long"){return new Date(Date.UTC(2024,0,1+a)).toLocaleDateString(Le(r),{weekday:e,timeZone:"UTC"})}function ui(a,r){let e=a.schedule,t=e?.offset?` ${e.offset>0?"+":"\u2212"}${Math.abs(e.offset)}d`:"";switch(e?.kind){case"weekdays":return((e.weekdays||[]).map(i=>Pe(i,r,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?s("ord_last",r):s("ord_"+e.nth,r)} ${Pe(e.weekday,r,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?s(e.business?"last_business_day_month":"last_day_month",r):`${s("day_word",r)} ${e.day}`)+t;case"one_time":return a.due_date?Y(a.due_date,r):s("one_time",r);case"manual":return s("manual",r);case"interval":return Be(e.every,e.unit,r)}return a.schedule_type==="one_time"?a.due_date?Y(a.due_date,r):s("one_time",r):a.schedule_type==="manual"?s("manual",r):a.schedule_type==="sensor_based"?s("sensor_based",r):a.interval_days!=null?Be(a.interval_days,a.interval_unit,r):"\u2014"}function hi(a,r){a.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:r},bubbles:!0,composed:!0}))}var oi,ut,li,oe,$r,kr,Ie,Er,Ue,gi,Ve,R=w(()=>{"use strict";P();ii();ai();ni();oi="\u20AC",ut="en",li=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),oe=li.store;wr(ri);$r=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),kr="/maintenance_supporter_locales",Ie=li.inflight;Er=window,Ue=Er.__msDateTimePrefs??={};gi=S` +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",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_window_7:"7 days",cal_window_14:"14 days",cal_window_30:"30 days",cal_window_365:"1 year",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",qr_print:"Print",qr_download:"Download SVG",qr_action:"Action on scan",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",analysis:"Analysis",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",no_advanced_features:"No advanced features enabled",no_advanced_features_hint:"Enable \u201CAdaptive Intervals\u201D or \u201CSeasonal Patterns\u201D in the integration settings to see analysis data here.",analysis_not_enough_data:"Not enough data for analysis yet.",analysis_not_enough_data_hint:"Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",analysis_manual_task_hint:"Manual tasks without an interval do not generate analysis data.",completions:"completions",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_priority_help:"Empty = show all priorities. Tasks without an explicit priority count as Normal.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects and tasks\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_export_settings:"Export settings (JSON)",settings_import_csv:"Import CSV",settings_import_placeholder:"Paste JSON or CSV content here\u2026",settings_import_btn:"Import",settings_import_success:"{count} objects imported successfully.",settings_export_success:"Export downloaded.",settings_saved:"Setting saved.",settings_include_history:"Include history",settings_export_selection:"Limit to selected objects (optional)",settings_docs_archive:"Documents archive (with files)",settings_docs_archive_hint:"The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",settings_docs_export_btn:"Download documents ZIP",settings_docs_import_btn:"Restore documents ZIP",settings_docs_import_success:"Restored: {blobs} files, {docs} documents",sort_alphabetical:"Alphabetical",sort_due_soonest:"Due soonest",sort_task_count:"Task count",sort_area:"Area",sort_assigned_user:"Assigned user",sort_group:"Group",groupby_none:"No grouping",groupby_area:"By area",groupby_group:"By group",groupby_user:"By user",filter_label:"Filter",user_label:"User",photo_label:"Photo",sort_label:"Sort",group_by_label:"Group by",state_value_help:'Use the HA state value (usually lowercase, e.g. "on"/"off"). Case is normalised on save.',target_changes_help:"Number of matching transitions before the trigger fires (default: 1).",for_minutes_state_help:"0 counts every change immediately. Set minutes and the new state must hold that long first \u2014 brief flickers then neither trigger nor count.",qr_print_title:"Print QR codes",qr_print_desc:"Generate a printable page of QR codes to cut out and stick on your equipment.",qr_print_load:"Load objects",qr_print_filter:"Filter",qr_print_objects:"Objects",qr_print_actions:"Actions",qr_print_url_mode:"Link type",qr_print_estimate:"Estimated QR codes",qr_print_over_limit:"cap is 200, narrow the filter",qr_print_generate:"Generate QR codes",qr_print_generating:"Generating\u2026",qr_print_ready:"QR codes ready",qr_print_print_button:"Print",qr_print_empty:"Nothing to generate",qr_action_skip:"Skip",vacation_title:"Vacation mode",vacation_active:"active",vacation_ended:"ended",vacation_desc:"Plan a vacation: notifications are paused during the period plus a buffer of days. You can opt specific tasks back in.",vacation_enable:"Enable vacation mode",vacation_start:"Start",vacation_end:"End",vacation_buffer:"Buffer (days)",vacation_exempt_title:"Notify anyway during vacation",vacation_exempt_desc:"Pick tasks that should still notify during vacation (e.g. critical pool chemistry).",vacation_load_tasks:"Load tasks",vacation_preview_btn:"Show preview",vacation_preview_affected:"tasks affected",vacation_event_due_soon:"becomes due soon",vacation_event_overdue:"becomes overdue",vacation_event_triggered_est:"sensor trigger possible",vacation_sensor_based:"(sensor-based)",vacation_action_notify:"Notify anyway",vacation_action_unsilence:"Silence again",vacation_marked_complete:"Marked complete",vacation_marked_skip:"Skipped",vacation_end_now:"End vacation now",add:"Add",show_stats:"Show stats + graphs",hide_stats:"Hide stats",adaptive_no_data:"Not enough completion history yet for adaptive analysis. Complete this task a few more times to unlock interval recommendations and reliability charts.",suggestion_applied:"Suggested interval applied",vacation_mode:"Vacation mode",vacation_status_active:"Active now",vacation_status_scheduled:"Scheduled",vacation_status_inactive:"Inactive",vacation_end_now_confirm:"End vacation immediately?",vacation_exempt_count:"exempt",vacation_advanced:"Advanced\u2026",vacation_open_panel:"Open in panel",enable:"Enable",saved:"Saved",budget_monthly_set:"Set monthly",budget_yearly_set:"Set yearly",budget_advanced:"Currency, alerts\u2026",budget_open_panel:"Open in panel",groups_empty:"No groups yet.",group_new_placeholder:"Add group\u2026",group_delete_confirm:'Delete group "{name}"?',groups_manage_tasks:"Manage task assignments\u2026",groups_open_panel:"Open in panel",unassigned:"Unassigned",no_area:"No area",has_overdue:"Has overdue tasks",object:"Object",settings_panel_access:"Panel access",settings_panel_access_desc:"Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below \u2014 everyone else sees only Complete and Skip.",settings_operator_write:"Allow selected users to create, edit & delete",settings_operator_write_desc:"Off: only admins can change content. On: the selected users below get full access too.",no_non_admin_users:"No non-admin users found. Add some in Settings \u2192 People.",owner_label:"Owner",feat_completion_actions:"Completion actions",feat_completion_actions_desc:"Per-task HA action on complete + quick-complete QR with pre-set values.",on_complete_action_title:"On complete: trigger HA action (optional)",on_complete_action_desc:"Calls an HA service when the task is completed \u2014 e.g. reset a counter on the device.",on_complete_action_service:"Service",on_complete_action_target:"Target entity",on_complete_action_target_hint:"Note: the entity domain must match the service \u2014 e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",on_complete_action_data:"Data (JSON, optional)",on_complete_action_test:"Validate configuration",on_complete_action_test_success:"\u2713 Configuration valid (action will fire only on task completion)",on_complete_action_test_failed:"Failed",quick_complete_defaults_title:"Quick-complete defaults (for QR scans, optional)",quick_complete_defaults_desc:"Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",quick_complete_defaults_notes:"Notes",quick_complete_defaults_cost:"Cost",quick_complete_defaults_duration:"Duration (minutes)",quick_complete_defaults_feedback_none:"No feedback",quick_complete_defaults_feedback_needed:"Was needed",quick_complete_defaults_feedback_not_needed:"Not needed",quick_complete_success:"Quickly marked complete",show_all_objects:"Show all objects",show_all_tasks:"Clear filter \u2014 show all tasks",filter_to_overdue:"Filter task list to overdue only",filter_to_due_soon:"Filter task list to due-soon only",filter_to_triggered:"Filter task list to triggered only",open_task:"Open task",show_details:"Show history + stats",hide_details:"Hide details",history_empty:"No history yet.",history_edit_button:"Edit entry",total_cost:"Total cost",times_performed:"Performed",older_entries:"older",open_in_panel:"Open in Maintenance panel",skip_reason:"Skip reason (optional)",reset_to_date:"Reset last_performed to",delete_task_confirm:"Delete this task and its history?",delete_object_confirm:"Delete this object and all its tasks?",loading:"Loading\u2026",archive:"Archive",undo:"Undo",task_archived:"Task archived",object_archived:"Object archived",unarchive:"Unarchive",archived:"Archived",show_archived:"Show archived",hide_archived:"Hide archived",confirm_archive_object:"Archive this object and its tasks? They keep their history and can be unarchived later.",settings_archive:"Archive & Retention",settings_archive_desc:"Retire completed one-off tasks without deleting them. Archived items are hidden and inert but keep their history and cost.",settings_archive_oneoff_days:"Auto-archive completed one-off tasks after (days, 0 = off)",settings_delete_archived_oneoff_days:"Auto-delete archived one-off tasks after (days, 0 = never)",archive_object:"Archive object",unarchive_object:"Unarchive object",documents:"Documents",documents_empty:"No documents yet.",doc_upload:"Upload file",doc_uploading:"Uploading\u2026",doc_add_link:"Add link",doc_link_url:"URL (https://\u2026)",doc_link_title:"Title (optional)",doc_open:"Open",doc_delete_confirm:'Delete "{name}"?',doc_too_large:"File is too large (max 25 MB).",doc_upload_failed:"Upload failed.",completion_photo_optional:"Completion photo (optional)",add_photo:"Add photo",uploading:"Uploading\u2026",remove:"Remove",doc_deduped:"Already stored elsewhere \u2014 shared, no extra space used.",doc_dup_in_object:"This file is already attached to this object.",doc_link_invalid:"Only http/https links are allowed.",doc_cat_manual:"Manual",doc_cat_warranty:"Warranty",doc_cat_invoice:"Invoice",doc_cat_spare_parts:"Spare parts",doc_cat_photo:"Photo",doc_cat_other:"Other",doc_link_badge:"Link",doc_storage_title:"Document storage",doc_storage_saved:"Saved via deduplication",doc_storage_refresh:"Refresh",doc_download:"Download",doc_close:"Close",doc_camera:"Take photo",doc_drop_hint:"Drop files here",doc_task_none:"No documents linked to this task.",doc_link_existing:"Link a document\u2026",doc_attach:"Link",doc_unlink:"Unlink",doc_page:"Page",chart_range_7d:"7d",chart_range_30d:"30d",chart_range_90d:"90d",chart_range_1y:"1y",chart_since_service:"since last service",chart_no_stats:"No long-term statistics for this entity \u2014 showing maintenance-event values only",auto_complete_on_recovery:"Auto-complete when the sensor recovers",auto_complete_on_recovery_help:"Records a completion (sets last performed) when the trigger clears itself \u2014 e.g. salt refilled, filter replaced.",doc_search:"Search documents\u2026",doc_search_none:"No matching documents",link_device_optional:"Link to existing device (optional)",parent_object_optional:"Parent object (optional)",parent_none:"(No parent)",paused:"Paused",pause_object:"Pause",resume_object:"Resume",pause_until_prompt:"Freeze this object's schedules \u2014 nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",pause_until_label:"Resume on (optional)",object_paused:"Object paused",object_resumed:"Object resumed \u2014 schedules restarted",object_paused_badge:"Paused",paused_until_label:"until",replace_object:"Replace\u2026",replace_object_prompt:"Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",replace_name_label:"Successor name",object_replaced:"Object replaced \u2014 successor created",reading_unit_label:"Reading unit (e.g. kWh, m\xB3)",reading_unit_help:"Shown next to the recorded value when completing this task.",reading_value_label:"Reading value",reading_label:"Reading",settings_templates_label:"Template gallery",settings_templates_hint:`Untick templates you'll never need \u2014 they disappear from the "From template" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.`,worksheet:"Work sheet",worksheet_scan_view:"Scan to open the task",worksheet_scan_complete:"Scan to complete",worksheet_manual_excerpt:"Manual excerpt",worksheet_pages:"pages",worksheet_printed:"Printed",worksheet_never:"Never",card_all_caught_up:"All caught up \u2014 nothing needs attention",postpone:"Postpone",postpone_date_prompt:"Postpone this occurrence to which date?",postpone_date_label:"New due date",postponed:"Postponed",postponed_to:"Postponed to",season_window_label:"Seasonal window (months)",season_window_hint:"Only due in the selected months; off-season dates roll to the next active month. None = all year.",series_end_label:"Ends",series_end_never:"Never (repeats indefinitely)",series_end_after_count:"After a number of times",series_end_until:"On a date",series_end_count_label:"Number of times",series_end_until_label:"End date",parts_section:"Parts & consumables",parts_inventory_value:"Inventory value",part_add:"Add part",part_name:"Name",part_vendor:"Manufacturer",part_storage_location:"Storage location",part_product_url:"Product URL",part_unit:"Unit",part_cost:"Unit price",part_stock:"Stock",part_reorder_threshold:"Reorder at",part_restock_quantity:"Restock quantity",part_auto_buy:"Auto-create buy task when low",part_restock:"Adjust stock",parts_used_by:"Used by",restock_quantity_label:"Quantity bought",consumes_parts_label:"Consumes parts",shared_parts_other_objects:"Parts from other objects",shared_parts_help:"Several objects can share one stock. Completing this task takes from the owning object.",shared_part_unknown:"Unknown part",parts_load_failed:"Couldn't load this object's parts \u2014 the consumes-parts options are unavailable right now.",adopt_problem_button:"Adopt problem sensors",adopt_problem_title:"Adopt problem sensors",adopt_problem_hint:"Turn HA problem sensors (printer errors, filter warnings, low battery) into maintenance tasks that trigger while the problem is active and clear themselves when it resolves.",adopt_problem_none:"No problem sensors found that aren't already tracked.",adopt_problem_active:"active",adopt_problem_ok:"ok",adopt_problem_new_object:"(new)",adopt_problem_adopt:"Adopt selected",adopt_problem_done:"Adopted {tasks} problem sensor(s)",views_label:"Views",views_none:"\u2014 No view \u2014",views_manage:"Save / manage views",views_dialog_title:"Saved views",views_dialog_hint:"Save the current filters as a named view everyone can reuse.",views_name_placeholder:"View name",views_save_current:"Save current filters",views_none_yet:"No saved views yet.",close:"Close",trigger_hint_now:"The sensor reads {value} right now.",trigger_hint_above:"The task triggers once it rises above {target}.",trigger_hint_below:"It triggers once it falls below {target}.",trigger_hint_counter_delta:"Counts from the current reading ({value}): due at {due} (+{target}), and the count restarts after each completion.",trigger_hint_counter_delta_edit:"Counts usage since the last completion: due after +{target}; the count restarts after each completion.",trigger_hint_counter_abs:"The task becomes due once the sensor reaches {target}.",trigger_hint_runtime:"The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",trigger_hint_state_change:"The task becomes due after {count} state change(s).",trigger_hint_state_change_to:"The task becomes due after {count} change(s) to \u201C{state}\u201D.",trigger_hint_state_now:"Current state: {value}.",adopt_problem_part:"Uses part: {name}",label_filter:"Label",all_labels:"All labels",settings_notify_scope:"Notify only for view",settings_notify_scope_all:"All tasks",settings_notify_scope_hint:"Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",card_saved_view:"Saved view",card_saved_view_none:"None",card_saved_view_help:"Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",doc_part_none:"No documents linked to this part.",settings_templates_toggle_group:"Enable or disable all templates in this group",setups_button:"Suggested setups",setups_title:"Suggested setups (Beta)",setups_hint:"Devices of supported integrations whose consumable sensors can drive maintenance tasks. Adopting creates the object and wires each task to its sensor \u2014 it triggers when the consumable runs low and resolves itself after replacement.",setups_none:"No supported devices with unwired consumable sensors found.",setups_adopt:"Set up selected",setups_done:"{tasks} sensor-wired tasks created.",complete_parts_used:"Parts used this time",part_delete_confirm:"Delete part '{name}'? Its stock tracking, task links and any open buy reminder will be removed.",baseline_start_value:"Start reading (optional)",baseline_start_help:"Counting starts from this reading. Leave empty to count from the current value; enter the reading at the last service so usage since then already counts.",setups_baseline_hint:"reading at last service (optional)",baseline_start_help_edit:"Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",baseline_current_effective:"Currently effective start value: {value}",runtime_on_states:"Active states",runtime_on_states_help:"States that count as running \u2014 default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",setups_target_new:"Create new: {name}",schedule_preview_title:"Next dates",schedule_preview_ontime:"Assuming on-time completion.",schedule_preview_ends:"(series ends)",adopt_problem_responsible:"Responsible user for all adopted tasks (optional)",adopt_for_minutes_hint:"Only trigger once the problem has persisted this long \u2014 0 reacts to the first flicker.",adopt_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_add:"Add a battery",battery_fleet_add_hint:"Pick a battery sensor the automatic discovery missed \u2014 it joins the roster immediately.",battery_fleet_track_self:"Track self-charging batteries",battery_fleet_track_self_hint:"Phones, vacuums and other devices that recharge themselves appear as rechargeables \u2014 a low one asks for a charge, never for new cells.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",cal_editor_window:"Default window",cal_editor_window_week:"Week (7 days)",cal_editor_window_fortnight:"Fortnight (14 days)",cal_editor_window_month:"Month (30 days, default)",cal_editor_window_year:"Year (365 days, empty days collapsed)",cal_editor_show_chips:"Show window chips inside the card",cal_editor_chips_hint:"Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",cal_editor_show_user_filter:"Show user filter dropdown",cal_editor_default_user:"Default user filter",cal_editor_my_tasks:"My tasks (current user)",cal_editor_show_object_filter:"Show object filter dropdown",cal_editor_object_hint:'Pre-select one object via YAML: object_filter: "" \u2014 or a list of names to restrict the card to several objects.',object_history_section:"History (all tasks)",object_history_all_tasks:"All tasks",object_history_empty:"No entries in this range.",object_history_cap_note:"History keeps up to 500 entries per task \u2014 very old entries may be missing.",service_record_title:"Service record",service_record_print:"Service record (PDF)",date:"Date",service_record_entries:"entries",completed_by:"Completed by",date_from:"From",date_to:"To",phases_section:"Cycle phases (optional)",phases_hint:"Different work on one shared schedule \u2014 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 \u2014 showing recorder state history (typically the last ~10 days)"}});var ni,oi=w(()=>{"use strict";ni="2.65.0"});var Ce,li=w(()=>{"use strict";Ce={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)"}});function Es(r){oe.en=Object.assign({},r,oe.en??{})}function We(r){let s=(r||_t).toLowerCase();return s.startsWith("pt")&&s.endsWith("br")?"pt-br":s.substring(0,2)}function a(r,s){let e=We(s);return oe[e]?.[r]??oe.en[r]??r}function pi(r,s){s.has("hass")&&Cs(r.hass?.locale);let e=r.hass?.language;e&&!Re(e)&&qe(e).then(()=>r.requestUpdate())}function j(r){return r?.language||"en"}function Re(r){let s=We(r);return s===_t||s in oe}function qe(r){let s=We(r);return s===_t||s in oe||!Ss.has(s)?Promise.resolve():(s in Ie||(Ie[s]=fetch(`${As}/${s}.json?v=${ni}`).then(e=>e.ok?e.json():null).then(e=>{e?oe[s]=e:delete Ie[s]}).catch(()=>{delete Ie[s]})),Ie[s])}function Pe(r){let s=We(r);return{de:"de-DE",en:"en-US",nl:"nl-NL",fr:"fr-FR",it:"it-IT",es:"es-ES",pt:"pt-PT",ru:"ru-RU",uk:"uk-UA",zh:"zh-CN",da:"da-DK",fi:"fi-FI",nb:"nb-NO",ja:"ja-JP",hi:"hi-IN",pl:"pl-PL",cs:"cs-CZ",sv:"sv-SE","pt-br":"pt-BR",hu:"hu-HU",ko:"ko-KR",tr:"tr-TR"}[s]??"en-US"}function Cs(r){r&&(Ue.date=r.date_format,Ue.time=r.time_format)}function ui(r,s){let e=String(r.getDate()).padStart(2,"0"),t=String(r.getMonth()+1).padStart(2,"0"),i=String(r.getFullYear());switch(Ue.date){case"DMY":return`${e}/${t}/${i}`;case"MDY":return`${t}/${e}/${i}`;case"YMD":return`${i}-${t}-${e}`;case"system":return r.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return r.toLocaleDateString(Pe(s),{day:"2-digit",month:"2-digit",year:"numeric"})}}function Is(r,s){switch(Ue.time){case"12":return r.toLocaleTimeString(Pe(s),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return r.toLocaleTimeString(Pe(s),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return r.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return r.toLocaleTimeString(Pe(s),{hour:"2-digit",minute:"2-digit"})}}function Y(r,s){if(!r)return"\u2014";try{let e=r.includes("T")?r:r+"T00:00:00";return ui(new Date(e),s)}catch{return r}}function hi(r,s){if(!r)return"\u2014";try{let e=new Date(r);return ui(e,s)+" "+Is(e,s)}catch{return r}}function _i(r,s){if(r==null)return"\u2014";let e=s||"en";return r<0?`${Math.abs(r)} ${a("d_overdue",e)}`:r===0?a("today",e):`${r} ${a(r===1?"day":"days",e)}`}function Be(r,s,e){return r==null?"\u2014":`${r} ${a("unit_"+(s||"days"),e)}`}function Le(r,s,e="long"){return new Date(Date.UTC(2024,0,1+r)).toLocaleDateString(Pe(s),{weekday:e,timeZone:"UTC"})}function gi(r,s){let e=r.schedule,t=e?.offset?` ${e.offset>0?"+":"\u2212"}${Math.abs(e.offset)}d`:"";switch(e?.kind){case"weekdays":return((e.weekdays||[]).map(i=>Le(i,s,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?a("ord_last",s):a("ord_"+e.nth,s)} ${Le(e.weekday,s,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?a(e.business?"last_business_day_month":"last_day_month",s):`${a("day_word",s)} ${e.day}`)+t;case"one_time":return r.due_date?Y(r.due_date,s):a("one_time",s);case"manual":return a("manual",s);case"interval":return Be(e.every,e.unit,s)}return r.schedule_type==="one_time"?r.due_date?Y(r.due_date,s):a("one_time",s):r.schedule_type==="manual"?a("manual",s):r.schedule_type==="sensor_based"?a("sensor_based",s):r.interval_days!=null?Be(r.interval_days,r.interval_unit,s):"\u2014"}function mi(r,s){r.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:s},bubbles:!0,composed:!0}))}var ci,_t,di,oe,Ss,As,Ie,Ts,Ue,fi,Ve,R=w(()=>{"use strict";L();ri();oi();li();ci="\u20AC",_t="en",di=(()=>{let r=window;return r.__msLocales||(r.__msLocales={store:{},inflight:{}}),r.__msLocales})(),oe=di.store;Es(ai);Ss=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),As="/maintenance_supporter_locales",Ie=di.inflight;Ts=window,Ue=Ts.__msDateTimePrefs??={};fi=S` .field { display: flex; flex-direction: column; gap: 4px; } .field-label { font-size: 12px; color: var(--secondary-text-color); } .field-input { @@ -258,6 +258,21 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" color: var(--secondary-text-color); white-space: nowrap; } + /* #139 — names the cycle phase a completion recorded. Shares the auto-badge + look; carries the left-pinning margin itself unless the auto badge (which + already has it) follows. */ + .history-phase-badge { + margin-right: auto; + font-size: 11px; + padding: 1px 8px; + border-radius: 10px; + background: var(--secondary-background-color); + color: var(--secondary-text-color); + white-space: nowrap; + } + .history-phase-badge:has(+ .history-auto-badge) { + margin-right: 0; + } .history-edit-btn { background: transparent; color: var(--secondary-text-color); @@ -1129,25 +1144,25 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .stat-item .stat-label { font-size: 11px; white-space: normal; text-align: center; line-height: 1.2; } .stat-value { font-size: 20px; } } -`});function Cr(a,r){let e=Tr[a];if(!e)return a;let t=s(e,r);return t&&t!==e?t:a}function Ir(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 j(a,r,e){if(e=e??s("action_error",r),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=Ir(i),o=n.field?Cr(n.field,r):"",p=c=>s(c,r).replace("{field}",o).replace("{n}",n.param??"");switch(n.rule){case"too_long":return p("err_too_long");case"too_short":return p("err_too_short");case"value_too_high":return p("err_value_too_high");case"value_too_low":return p("err_value_too_low");case"required":return p("err_required");case"wrong_type":return p("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return p("err_invalid_choice");case"invalid_value":return p("err_invalid_value");default:return i||e}}var Tr,le=w(()=>{"use strict";R();Tr={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"}});var H,ht=w(()=>{"use strict";P();U();H=class extends A{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 l` +`});function Ls(r,s){let e=Ps[r];if(!e)return r;let t=a(e,s);return t&&t!==e?t:r}function Rs(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function q(r,s,e){if(e=e??a("action_error",s),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,i=t.message||t.error?.message||"";if(!i)return e;let n=Rs(i),o=n.field?Ls(n.field,s):"",p=d=>a(d,s).replace("{field}",o).replace("{n}",n.param??"");switch(n.rule){case"too_long":return p("err_too_long");case"too_short":return p("err_too_short");case"value_too_high":return p("err_value_too_high");case"value_too_low":return p("err_value_too_low");case"required":return p("err_required");case"wrong_type":return p("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return p("err_invalid_choice");case"invalid_value":return p("err_invalid_value");default:return i||e}}var Ps,le=w(()=>{"use strict";R();Ps={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"}});var N,gt=w(()=>{"use strict";L();U();N=class extends A{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 l` - `}};H.styles=S` + `}};N.styles=S` :host { display: block; } .field { display: flex; @@ -1181,52 +1196,52 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" color: var(--secondary-text-color); font-style: italic; } - `,d([x()],H.prototype,"label",2),d([x()],H.prototype,"value",2),d([x()],H.prototype,"placeholder",2),d([x()],H.prototype,"type",2),d([x({type:Boolean})],H.prototype,"required",2),d([x({type:Boolean})],H.prototype,"disabled",2),d([x()],H.prototype,"step",2),d([x()],H.prototype,"min",2),d([x()],H.prototype,"max",2),d([x()],H.prototype,"pattern",2),d([x()],H.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",H)});var T,fi=w(()=>{"use strict";P();U();R();le();ht();T=class extends A{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 N(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=j(e,this._lang,s("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 l``;let e=this._lang,t=this._entryId?s("edit_object",e):s("new_object",e);return l` + `,c([x()],N.prototype,"label",2),c([x()],N.prototype,"value",2),c([x()],N.prototype,"placeholder",2),c([x()],N.prototype,"type",2),c([x({type:Boolean})],N.prototype,"required",2),c([x({type:Boolean})],N.prototype,"disabled",2),c([x()],N.prototype,"step",2),c([x()],N.prototype,"min",2),c([x()],N.prototype,"max",2),c([x()],N.prototype,"pattern",2),c([x()],N.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",N)});var T,yi=w(()=>{"use strict";L();U();R();le();gt();T=class extends A{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=q(e,this._lang,a("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 l``;let e=this._lang,t=this._entryId?a("edit_object",e):a("new_object",e);return l`
${t}
- ${this._error?l`
${this._error}
`:h} + ${this._error?l`
${this._error}
`:_} this._name=i.target.value} > this._manufacturer=i.target.value} > this._model=i.target.value} > this._serialNumber=i.target.value} > this._documentationUrl=i.target.value} > this._areaId=i.detail.value||""} > this._installationDate=i.target.value} > this._warrantyExpiry=i.target.value} @@ -1235,27 +1250,27 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .hass=${this.hass} .data=${{device:this._haDeviceId||void 0}} .schema=${[{name:"device",selector:{device:{}}}]} - .computeLabel=${()=>s("link_device_optional",e)} + .computeLabel=${()=>a("link_device_optional",e)} @value-changed=${i=>this._haDeviceId=i.detail.value?.device||""} > ${this._parentChoices().length?l``:h} + `:_}
+ `)} + + ${a("phase_add",e)} + + ${this._phaseDefs.some(i=>i.name.trim())?l` +
${a("phase_sequence_label",e)}
+
+ ${this._phaseSeq.map((i,n)=>l` + + ${n+1}. ${t(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=`${a("adaptive_min_interval",this._lang)} > ${a("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):vt.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(p=>p.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 p={};for(let u of this._phaseDefs){if(!u.name.trim())continue;let m={...u.carry,name:u.name.trim()},f=u.checklistText.split(` +`).map(b=>b.trim()).filter(Boolean);f.length&&(m.checklist=f);let v=[];if(u.partId){let b=parseFloat(u.partQty);v.push({part_id:u.partId,quantity:Number.isFinite(b)&&b>0?b:1})}v.push(...u.extraParts),v.length&&(m.consumes_parts=v),p[u.id]=m}let d=this._phaseSeq.filter(u=>u in p);e.phases=Object.keys(p).length&&d.length?p:null,e.phase_sequence=e.phases?d:null}if((this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(p=>p.entry_id?{part_id:p.part_id,quantity:p.quantity,entry_id:p.entry_id}:{part_id:p.part_id,quantity:p.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 p=this._compoundConditions.map(Fs).filter(d=>d!==null);if(p.length>0){let d={type:"compound",compound_logic:this._compoundLogic,conditions:p};this._autoCompleteOnRecovery&&(d.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(d.trigger_combinator="all"),e.trigger_config=d}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let p=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],d={entity_id:p[0],entity_ids:p,type:this._triggerType};if(this._triggerAttribute&&(d.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(d.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(d.trigger_combinator="all"),p.length>1&&(d.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let u=parseFloat(this._triggerAbove);isNaN(u)||(d.trigger_above=u)}if(this._triggerBelow){let u=parseFloat(this._triggerBelow);isNaN(u)||(d.trigger_below=u)}if(this._triggerEquals){let u=parseFloat(this._triggerEquals);isNaN(u)||(d.trigger_equals=u)}if(this._triggerNotEquals){let u=parseFloat(this._triggerNotEquals);isNaN(u)||(d.trigger_not_equals=u)}if(this._triggerForMinutes){let u=parseInt(this._triggerForMinutes,10);isNaN(u)||(d.trigger_for_minutes=u)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let u=parseFloat(this._triggerTargetValue);isNaN(u)||(d.trigger_target_value=u)}if(d.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let u=parseFloat(this._triggerBaselineValue);!isNaN(u)&&u>=0&&(d.trigger_baseline_value=u)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(d.trigger_from_state=this._triggerFromState),this._triggerToState&&(d.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let u=parseInt(this._triggerTargetChanges,10);isNaN(u)||(d.trigger_target_changes=u)}if(this._triggerForMinutes){let u=parseInt(this._triggerForMinutes,10);isNaN(u)||(d.trigger_for_minutes=u)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let m=parseFloat(this._triggerRuntimeHours);isNaN(m)||(d.trigger_runtime_hours=m)}let u=this._triggerOnStates.split(",").map(m=>m.trim()).filter(Boolean);u.length>0&&(d.trigger_on_states=u)}e.trigger_config=d}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&this._scheduleType==="time_based"){let p=this._scheduleTime.trim();e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(p)?p:null}if(this.checklistsEnabled){let p=this._checklistText.split(` +`).map(d=>d.trim()).filter(Boolean).slice(0,100);e.checklist=p.length?p:null}if(this.completionActionsEnabled){let p=this._actionService.trim();if(p&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(p)){let f={service:p},v=this._actionTargetEntity.trim();v&&(f.target={entity_id:v});let b=this._buildActionData();Object.keys(b).length>0&&(f.data=b),e.on_complete_action=f}else e.on_complete_action=null;let d={};this._qcNotes.trim()&&(d.notes=this._qcNotes.trim());let u=parseFloat(this._qcCost);!isNaN(u)&&u>=0&&(d.cost=u);let m=parseInt(this._qcDuration,10);!isNaN(m)&&m>=0&&(d.duration=m),this._qcFeedback&&(d.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(d).length?d:null}let i=await this.hass.connection.sendMessagePromise(e),n=this._taskId||i?.task_id,o=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(n&&this._scheduleType==="sensor_based"&&o)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 p=parseFloat(this._adaptiveAlpha),d=parseInt(this._adaptiveMin,10),u=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,...p>=.1&&p<=.9?{ewa_alpha:p}:{},...!isNaN(d)&&d>=1?{min_interval_days:d}:{},...!isNaN(u)&&u>=1?{max_interval_days:u}:{},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=q(e,this._lang,a("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 _;let e=this._lang,t=this._triggerType==="compound";return l` +

${a("trigger_configuration",e)}

- +
${t?this._renderCompoundEditor():l` ${this._entityPickerFallback?l` 0?this._triggerEntityIds.join(", "):this._triggerEntityId} @input=${i=>{let o=i.target.value.split(",").map(p=>p.trim()).filter(Boolean);this._triggerEntityId=o[0]||"",this._triggerEntityIds=o,o[0]&&this._fetchEntityAttributes(o[0])}} > @@ -1442,24 +1521,24 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" 0?this._triggerEntityIds:this._triggerEntityId?[this._triggerEntityId]:[]}} - .computeLabel=${()=>s("entity_id",e)} + .computeLabel=${()=>a("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("")}} >`} ${this._triggerEntityIds.length>1?l`
- +
- `:h} - ${this._renderAttributeSelect({label:s("attribute_optional",e),value:this._triggerAttribute,suggested:this._suggestedAttributes,available:this._availableAttributes,onSelect:i=>this._triggerAttribute=i})} + `:_} + ${this._renderAttributeSelect({label:a("attribute_optional",e),value:this._triggerAttribute,suggested:this._suggestedAttributes,available:this._availableAttributes,onSelect:i=>this._triggerAttribute=i})} ${this._renderTriggerTypeFields()} ${this._renderTriggerLiveHint()} `} @@ -1469,57 +1548,57 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .checked=${this._autoCompleteOnRecovery} @change=${i=>this._autoCompleteOnRecovery=i.target.checked} /> - ${s("auto_complete_on_recovery",e)} + ${a("auto_complete_on_recovery",e)} -
${s("auto_complete_on_recovery_help",e)}
+
${a("auto_complete_on_recovery_help",e)}
this._intervalDays=i.target.value} > - ${this._intervalDays?this._renderUnitSelect():h} + ${this._intervalDays?this._renderUnitSelect():_} ${this._intervalDays?l`
- +
- `:h} - `}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Hr()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l` + `:_} + `}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Ms()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l`
- +
-
${s("compound_help",e)}
- ${this._compoundConditions.length===0?l`
${s("compound_no_conditions",e)}
`:this._compoundConditions.map((t,i)=>this._renderCondition(t,i))} +
${a("compound_help",e)}
+ ${this._compoundConditions.length===0?l`
${a("compound_no_conditions",e)}
`:this._compoundConditions.map((t,i)=>this._renderCondition(t,i))} `}_renderCondition(e,t){let i=this._lang,n=t+1;return l`
- ${s("compound_condition",i)} ${n} + ${a("compound_condition",i)} ${n}
${this._entityPickerFallback?l` this._patchCondition(t,{entityIds:o.target.value})} > @@ -1527,19 +1606,19 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" o.trim()).filter(Boolean)}} - .computeLabel=${()=>s("entity_id",i)} + .computeLabel=${()=>a("entity_id",i)} @value-changed=${o=>{let p=(o.detail.value.condition_entities||[]).filter(Boolean);this._patchCondition(t,{entityIds:p.join(", ")})}} >`} ${this._renderConditionAttribute(e,t)}
- +
${this._renderConditionTypeFields(e,t)} @@ -1561,7 +1640,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" > `}_renderOnStatesField(e){let t=this._lang;return this._entityPickerFallback||!e.entityId?l` e.onInput(i.target.value)} @@ -1572,37 +1651,37 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .hass=${this.hass} .schema=${[{name:"s",selector:{state:{entity_id:e.entityId,multiple:!0}}}]} .data=${{s:(e.value||"").split(",").map(i=>i.trim()).filter(Boolean)}} - .computeLabel=${()=>s("runtime_on_states",t)} + .computeLabel=${()=>a("runtime_on_states",t)} @value-changed=${i=>e.onInput((i.detail.value.s||[]).join(", "))} > - `}_renderAdaptiveSection(e){return this._scheduleType==="one_time"||this._scheduleType==="manual"?h:l` + `}_renderAdaptiveSection(e){return this._scheduleType==="one_time"||this._scheduleType==="manual"?_:l`
- ${s("adaptive_section_title",e)} + ${a("adaptive_section_title",e)} ${this._adaptiveEnabled?l` this._adaptiveMin=t.target.value} > this._adaptiveMax=t.target.value} > this._adaptiveSeasonal=t.target.checked} /> - ${s("adaptive_seasonal_enabled",e)} + ${a("adaptive_seasonal_enabled",e)} - `:h} + `:_}
`}_renderAttributeSelect(e){let t=this._lang;return e.available.length>0?l`
@@ -1635,7 +1714,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${e.value} @change=${i=>e.onSelect(i.target.value)} > - + ${e.suggested.map(i=>l``)} ${e.available.filter(i=>!e.suggested.includes(i.name)).map(i=>l``)} @@ -1646,51 +1725,51 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${e.value} @input=${i=>e.onSelect(i.target.value.trim())} > - `}_renderEnvironmentalAttribute(e){this._fetchConditionAttributes(this._environmentalEntity);let t=this._conditionAttrOptions[this._environmentalEntity];return this._renderAttributeSelect({label:s("environmental_attribute_optional",e),value:this._environmentalAttribute,suggested:t?.suggested??[],available:t?.available??[],onSelect:i=>this._environmentalAttribute=i})}_renderConditionAttribute(e,t){let i=e.entityIds.split(",")[0]?.trim()||"";i&&this._fetchConditionAttributes(i);let n=i?this._conditionAttrOptions[i]:void 0;return this._renderAttributeSelect({label:s("attribute_optional",this._lang),value:e.attribute,suggested:n?.suggested??[],available:n?.available??[],onSelect:o=>this._patchCondition(t,{attribute:o})})}_renderConditionTypeFields(e,t){let i=this._lang;if(e.type==="threshold")return l` - this._environmentalAttribute=i})}_renderConditionAttribute(e,t){let i=e.entityIds.split(",")[0]?.trim()||"";i&&this._fetchConditionAttributes(i);let n=i?this._conditionAttrOptions[i]:void 0;return this._renderAttributeSelect({label:a("attribute_optional",this._lang),value:e.attribute,suggested:n?.suggested??[],available:n?.available??[],onSelect:o=>this._patchCondition(t,{attribute:o})})}_renderConditionTypeFields(e,t){let i=this._lang;if(e.type==="threshold")return l` + this._patchCondition(t,{above:n.target.value})}> - this._patchCondition(t,{below:n.target.value})}> - this._patchCondition(t,{equals:n.target.value})}> - this._patchCondition(t,{notEquals:n.target.value})}> - this._patchCondition(t,{forMinutes:n.target.value})}> `;if(e.type==="counter")return l` - this._patchCondition(t,{targetValue:n.target.value})}> `;if(e.type==="state_change"){let n=e.entityIds.split(",")[0]?.trim()||"";return l` - ${this._renderStateField({label:s("from_state_optional",i),value:e.fromState,entityId:n,onInput:o=>this._patchCondition(t,{fromState:o})})} - ${this._renderStateField({label:s("to_state_optional",i),value:e.toState,entityId:n,onInput:o=>this._patchCondition(t,{toState:o})})} - this._patchCondition(t,{fromState:o})})} + ${this._renderStateField({label:a("to_state_optional",i),value:e.toState,entityId:n,onInput:o=>this._patchCondition(t,{toState:o})})} + this._patchCondition(t,{targetChanges:o.target.value})}> `}if(e.type==="runtime"){let n=e.entityIds.split(",")[0]?.trim()||"";return l` - this._patchCondition(t,{runtimeHours:o.target.value})}> ${this._renderOnStatesField({value:e.onStates,entityId:n,onInput:o=>this._patchCondition(t,{onStates:o})})} - `}return h}_renderUnitSelect(){let e=this._lang;return l` + `}return _}_renderUnitSelect(){let e=this._lang;return l`
- + -
`}_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(ft.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=(c,_,f=0)=>{if(!(!c||f>10)){(c.tagName?.toLowerCase()??"")==="ha-entity-picker"&&_.push(c);for(let m of[c.shadowRoot,c])if(m)for(let y of Array.from(m.children??[]))i(y,_,f+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let c of n)i(c,o);let p=o.length===0||o.some(c=>c.offsetHeight===0);if(e.offsetHeight===0||p){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 h;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,p)=>{let c=new Date(`${o}T12:00:00`).getDay();return`${Pe(c===0?6:c-1,e,"short")} ${Y(o,e)}${p===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`
${s("schedule_preview_ontime",e)}
`:h;return l` +
`}_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(vt.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=(d,u,m=0)=>{if(!(!d||m>10)){(d.tagName?.toLowerCase()??"")==="ha-entity-picker"&&u.push(d);for(let f of[d.shadowRoot,d])if(f)for(let v of Array.from(f.children??[]))i(v,u,m+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let d of n)i(d,o);let p=o.length===0||o.some(d=>d.offsetHeight===0);if(e.offsetHeight===0||p){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 _;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,p)=>{let d=new Date(`${o}T12:00:00`).getDay();return`${Le(d===0?6:d-1,e,"short")} ${Y(o,e)}${p===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`
${a("schedule_preview_ontime",e)}
`:_;return l`
- ${s("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?l` ${s("schedule_preview_ends",e)}`:h} + ${a("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?l` ${a("schedule_preview_ends",e)}`:_} ${n}
- `}_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"||ft.includes(this._scheduleType)))return h;let i=Fr(e);return l` - -
${s("season_window_hint",e)}
+ `}_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"||vt.includes(this._scheduleType)))return _;let i=Bs(e);return l` + +
${a("season_window_hint",e)}
${i.map((n,o)=>l` `)}
- +
${this._endsMode==="count"?l` this._endsCount=n.target.value} - >`:h} + >
`:_} ${this._endsMode==="until"?l` this._endsUntil=n.target.value} - >`:h} - `}_renderCalendarFields(){let e=this._lang,t=Dr(e);if(this._scheduleType==="weekdays")return l` - + >`:_} + `}_renderCalendarFields(){let e=this._lang,t=Us(e);if(this._scheduleType==="weekdays")return l` +
${t.map((i,n)=>l` `)}
- ${this._renderCalOffsetField()}`;if(this._scheduleType==="nth_weekday"){let i=[["1",s("ord_1",e)],["2",s("ord_2",e)],["3",s("ord_3",e)],["4",s("ord_4",e)],["5",s("ord_5",e)],["-1",s("ord_last",e)]];return l` + ${this._renderCalOffsetField()}`;if(this._scheduleType==="nth_weekday"){let i=[["1",a("ord_1",e)],["2",a("ord_2",e)],["3",a("ord_3",e)],["4",a("ord_4",e)],["5",a("ord_5",e)],["-1",a("ord_last",e)]];return l`
- +
- +
${this._renderCalOffsetField()}`}return this._scheduleType==="day_of_month"?l` - ${this._domLastDay?h:l` + ${this._domLastDay?_:l` this._domLastDay=i.target.checked} /> - ${s("recurrence_last_day",e)} + ${a("recurrence_last_day",e)} - ${this._renderCalOffsetField()}`:h}_renderCalOffsetField(){let e=this._lang;return l` + ${this._renderCalOffsetField()}`:_}_renderCalOffsetField(){let e=this._lang;return l` this._calOffset=t.target.value} - >`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return h;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return h;let t=this.hass.states[e];if(!t)return h;let i=this._lang,n=t.attributes?.unit_of_measurement,o=typeof n=="string"&&n?` ${n}`:"",p=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,c=typeof p=="number"?p:parseFloat(String(p)),_=p!=="unknown"&&p!=="unavailable"&&p!=null&&!isNaN(c),f=y=>Number.isInteger(y)?String(y):String(Math.round(y*10)/10),m=[];if(this._triggerType==="threshold"){let y=parseFloat(this._triggerAbove),b=parseFloat(this._triggerBelow);if(isNaN(y)&&isNaN(b))return h;_&&m.push(s("trigger_hint_now",i).replace("{value}",f(c)+o)),isNaN(y)||m.push(s("trigger_hint_above",i).replace("{target}",f(y)+o)),isNaN(b)||m.push(s("trigger_hint_below",i).replace("{target}",f(b)+o))}else if(this._triggerType==="counter"){let y=parseFloat(this._triggerTargetValue);if(isNaN(y))return h;this._triggerDeltaMode?this._taskId?m.push(s("trigger_hint_counter_delta_edit",i).replace("{target}",f(y)+o)):_?m.push(s("trigger_hint_counter_delta",i).replace("{value}",f(c)+o).replace("{due}",f(c+y)+o).replace("{target}",f(y)+o)):m.push(s("trigger_hint_counter_delta_edit",i).replace("{target}",f(y)+o)):(_&&m.push(s("trigger_hint_now",i).replace("{value}",f(c)+o)),m.push(s("trigger_hint_counter_abs",i).replace("{target}",f(y)+o)))}else if(this._triggerType==="runtime"){let y=parseFloat(this._triggerRuntimeHours);if(isNaN(y))return h;m.push(s("trigger_hint_runtime",i).replace("{hours}",f(y))),m.push(s("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let y=parseInt(this._triggerTargetChanges,10)||1,b=this._triggerToState.trim();m.push((b?s("trigger_hint_state_change_to",i).replace("{state}",b):s("trigger_hint_state_change",i)).replace("{count}",String(y))),m.push(s("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return m.length?l`
${m.join(" ")}
`:h}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l` + >
`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return _;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return _;let t=this.hass.states[e];if(!t)return _;let i=this._lang,n=t.attributes?.unit_of_measurement,o=typeof n=="string"&&n?` ${n}`:"",p=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,d=typeof p=="number"?p:parseFloat(String(p)),u=p!=="unknown"&&p!=="unavailable"&&p!=null&&!isNaN(d),m=v=>Number.isInteger(v)?String(v):String(Math.round(v*10)/10),f=[];if(this._triggerType==="threshold"){let v=parseFloat(this._triggerAbove),b=parseFloat(this._triggerBelow);if(isNaN(v)&&isNaN(b))return _;u&&f.push(a("trigger_hint_now",i).replace("{value}",m(d)+o)),isNaN(v)||f.push(a("trigger_hint_above",i).replace("{target}",m(v)+o)),isNaN(b)||f.push(a("trigger_hint_below",i).replace("{target}",m(b)+o))}else if(this._triggerType==="counter"){let v=parseFloat(this._triggerTargetValue);if(isNaN(v))return _;this._triggerDeltaMode?this._taskId?f.push(a("trigger_hint_counter_delta_edit",i).replace("{target}",m(v)+o)):u?f.push(a("trigger_hint_counter_delta",i).replace("{value}",m(d)+o).replace("{due}",m(d+v)+o).replace("{target}",m(v)+o)):f.push(a("trigger_hint_counter_delta_edit",i).replace("{target}",m(v)+o)):(u&&f.push(a("trigger_hint_now",i).replace("{value}",m(d)+o)),f.push(a("trigger_hint_counter_abs",i).replace("{target}",m(v)+o)))}else if(this._triggerType==="runtime"){let v=parseFloat(this._triggerRuntimeHours);if(isNaN(v))return _;f.push(a("trigger_hint_runtime",i).replace("{hours}",m(v))),f.push(a("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let v=parseInt(this._triggerTargetChanges,10)||1,b=this._triggerToState.trim();f.push((b?a("trigger_hint_state_change_to",i).replace("{state}",b):a("trigger_hint_state_change",i)).replace("{count}",String(v))),f.push(a("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return f.length?l`
${f.join(" ")}
`:_}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l` this._triggerAbove=t.target.value} > this._triggerBelow=t.target.value} > this._triggerEquals=t.target.value} > this._triggerNotEquals=t.target.value} > this._triggerForMinutes=t.target.value} > `:this._triggerType==="counter"?l` this._triggerDeltaMode=t.target.checked} /> - ${s("delta_mode",e)} + ${a("delta_mode",e)} ${this._triggerDeltaMode?l` this._triggerBaselineValue=t.target.value} >
- ${this._taskId?s("baseline_start_help_edit",e):s("baseline_start_help",e)} + ${this._taskId?a("baseline_start_help_edit",e):a("baseline_start_help",e)} ${this._taskId&&this._liveBaselineValue!=null?l`
- ${s("baseline_current_effective",e).replace("{value}",String(this._liveBaselineValue))} -
`:h} + ${a("baseline_current_effective",e).replace("{value}",String(this._liveBaselineValue))} +
`:_}
- `:h} + `:_} `:this._triggerType==="state_change"?l` - ${this._renderStateField({label:s("from_state_optional",e),value:this._triggerFromState,entityId:this._triggerEntityId,onInput:t=>this._triggerFromState=t})} -
${s("state_value_help",e)}
- ${this._renderStateField({label:s("to_state_optional",e),value:this._triggerToState,entityId:this._triggerEntityId,onInput:t=>this._triggerToState=t})} + ${this._renderStateField({label:a("from_state_optional",e),value:this._triggerFromState,entityId:this._triggerEntityId,onInput:t=>this._triggerFromState=t})} +
${a("state_value_help",e)}
+ ${this._renderStateField({label:a("to_state_optional",e),value:this._triggerToState,entityId:this._triggerEntityId,onInput:t=>this._triggerToState=t})} this._triggerTargetChanges=t.target.value} > -
${s("target_changes_help",e)}
+
${a("target_changes_help",e)}
this._triggerForMinutes=t.target.value} > -
${s("for_minutes_state_help",e)}
+
${a("for_minutes_state_help",e)}
`:this._triggerType==="runtime"?l` this._triggerRuntimeHours=t.target.value} > ${this._renderOnStatesField({value:this._triggerOnStates,entityId:this._triggerEntityId,onInput:t=>this._triggerOnStates=t})} -
${s("runtime_on_states_help",e)}
- `:h}render(){if(!this._open)return l``;let e=this._lang,t=this._taskId?s("edit_task",e):s("new_task",e);return l` +
${a("runtime_on_states_help",e)}
+ `:_}render(){if(!this._open)return l``;let e=this._lang,t=this._taskId?a("edit_task",e):a("new_task",e);return l`
${t}
- ${this._error?l`
${this._error}
`:h} + ${this._error?l`
${this._error}
`:_} ${this._objectChoices.length>0?l`
- +
- `:h} + `:_} this._name=i.target.value} >
- +
${this._type==="reading"?l` this._readingUnit=i.target.value} > -
${s("reading_unit_help",e)}
- `:h} - ${this._partsLoadFailed?l`
${s("parts_load_failed",e)}
`:h} +
${a("reading_unit_help",e)}
+ `:_} + ${this._partsLoadFailed?l`
${a("parts_load_failed",e)}
`:_} ${this.parts.length||this._foreignOwners.length?l`
- + ${this.parts.map(i=>this._renderConsumesRow(i))} ${this._foreignOwners.length?l`
- ${s("shared_parts_other_objects",e)} -
${s("shared_parts_help",e)}
+ ${a("shared_parts_other_objects",e)} +
${a("shared_parts_help",e)}
${this._foreignOwners.map(i=>l`
${i.name}
${i.parts.map(n=>this._renderConsumesRow(n,i.entry_id))} `)}
- `:h} + `:_}
- `:h} + `:_}
- +
- + this._labels=i.target.value} /> -
${s("labels_help",e)}
+
${a("labels_help",e)}
- +
${this._scheduleType==="time_based"?l` this._intervalDays=i.target.value} > ${this._renderUnitSelect()}
- +
${this.scheduleTimeEnabled?l` this._scheduleTime=i.target.value} > - `:h} - `:h} + `:_} + `:_} ${this._renderCalendarFields()} ${this._scheduleType==="one_time"?l` this._dueDate=i.target.value} > - `:h} + `:_} ${this._renderRecurrenceExtras()} ${this._renderSchedulePreview()} this._warningDays=i.target.value} > this._earliestCompletionDays=i.target.value} > ${this.checklistsEnabled?l` -

${s("checklist_steps_optional",e)}

+

${a("checklist_steps_optional",e)}

-
${s("checklist_help",e)}
- `:h} -

${s("require_on_completion",e)}

+
${a("checklist_help",e)}
+ `:_} + ${this._renderPhasesEditor(e)} +

${a("require_on_completion",e)}

- ${$i.map(i=>l` + ${Ei.map(i=>l` `)}
this._lastPerformed=i.target.value} >
- +
${this._availableUsers.length>=2?l`
- -
${s("shared_with_help",e)}
+ +
${a("shared_with_help",e)}
${this._availableUsers.map(i=>l`
${this._assigneePool.length>=2?l`
- + -
`:h} - `:h} +
`:_} + `:_} ${this._renderTriggerFields()} ${this._scheduleType==="sensor_based"?l` ${this._entityPickerFallback?l` this._environmentalEntity=i.target.value.trim()} > @@ -2089,56 +2169,56 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" s("environmental_entity_optional",e)} - .computeHelper=${()=>s("environmental_entity_helper",e)} + .computeLabel=${()=>a("environmental_entity_optional",e)} + .computeHelper=${()=>a("environmental_entity_helper",e)} @value-changed=${i=>{this._environmentalEntity=(i.detail.value.environmental_entity||"").trim()}} >`} - ${this._environmentalEntity?this._renderEnvironmentalAttribute(e):h} - `:h} + ${this._environmentalEntity?this._renderEnvironmentalAttribute(e):_} + `:_} ${this._renderAdaptiveSection(e)} this._notes=i.target.value} > this._documentationUrl=i.target.value} > this._customIcon=i.detail.value||""} > ${this._availableTags.length>0?l`
- + + title="${a("nfc_tags_refresh",e)}">↻
`:l` this._nfcTagId=i.target.value} >
- ${s("nfc_tags_empty_help",e)} - ${s("nfc_tags_open_settings",e)} + ${a("nfc_tags_empty_help",e)} + ${a("nfc_tags_open_settings",e)} ·
`} @@ -2148,17 +2228,17 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .checked=${this._enabled} @change=${i=>this._enabled=i.target.checked} /> - ${s("task_enabled",e)} + ${a("task_enabled",e)} ${this._renderCompletionActionsSection(e)}
- ${s("cancel",e)} + ${a("cancel",e)} - ${this._loading?s("saving",e):s("save",e)} + ${this._loading?a("saving",e):a("save",e)}
@@ -2276,6 +2356,82 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" gap: 8px; padding: 2px 0; } + .phase-def { + border: 1px solid var(--divider-color); + border-radius: 6px; + padding: 8px; + margin: 6px 0; + } + .phase-def-head { + display: flex; + align-items: center; + gap: 8px; + } + .phase-def-head ms-textfield { + flex: 1; + min-width: 0; + } + .phase-part { + max-width: 160px; + padding: 6px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + .phase-qty { + width: 64px; + padding: 6px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + .phase-checklist { + min-height: 56px; + margin-top: 6px; + } + .phase-remove { + --mdc-icon-button-size: 36px; + color: var(--secondary-text-color); + } + .phase-seq-label { + font-size: 12px; + color: var(--secondary-text-color); + margin-top: 8px; + } + .phase-seq { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 4px 0; + } + .phase-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 12px; + background: var(--secondary-background-color); + font-size: 13px; + } + .phase-chip-x { + border: none; + background: none; + color: var(--secondary-text-color); + cursor: pointer; + padding: 0 2px; + font-size: 12px; + } + .phase-seq-add { + padding: 4px 8px; + border: 1px dashed var(--divider-color); + border-radius: 12px; + background: transparent; + color: var(--secondary-text-color); + font-size: 13px; + } .consumes-check { display: flex; align-items: center; @@ -2453,18 +2609,19 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-size: 14px; cursor: pointer; } - `,d([x({attribute:!1})],g.prototype,"hass",2),d([x({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),d([x({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),d([x({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),d([x({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),d([u()],g.prototype,"parts",2),d([u()],g.prototype,"_foreignOwners",2),d([u()],g.prototype,"_open",2),d([u()],g.prototype,"_entityPickerFallback",2),d([u()],g.prototype,"_loading",2),d([u()],g.prototype,"_error",2),d([u()],g.prototype,"_entryId",2),d([u()],g.prototype,"_taskId",2),d([u()],g.prototype,"_objectChoices",2),d([u()],g.prototype,"_name",2),d([u()],g.prototype,"_type",2),d([u()],g.prototype,"_scheduleType",2),d([u()],g.prototype,"_intervalDays",2),d([u()],g.prototype,"_intervalUnit",2),d([u()],g.prototype,"_dueDate",2),d([u()],g.prototype,"_warningDays",2),d([u()],g.prototype,"_earliestCompletionDays",2),d([u()],g.prototype,"_intervalAnchor",2),d([u()],g.prototype,"_weekdays",2),d([u()],g.prototype,"_nth",2),d([u()],g.prototype,"_nthWeekday",2),d([u()],g.prototype,"_domDay",2),d([u()],g.prototype,"_domLastDay",2),d([u()],g.prototype,"_domBusiness",2),d([u()],g.prototype,"_calOffset",2),d([u()],g.prototype,"_seasonMonths",2),d([u()],g.prototype,"_endsMode",2),d([u()],g.prototype,"_endsCount",2),d([u()],g.prototype,"_endsUntil",2),d([u()],g.prototype,"_schedulePreview",2),d([u()],g.prototype,"_schedulePreviewEnded",2),d([u()],g.prototype,"_notes",2),d([u()],g.prototype,"_documentationUrl",2),d([u()],g.prototype,"_customIcon",2),d([u()],g.prototype,"_priority",2),d([u()],g.prototype,"_labels",2),d([u()],g.prototype,"_enabled",2),d([u()],g.prototype,"_triggerEntityId",2),d([u()],g.prototype,"_triggerEntityIds",2),d([u()],g.prototype,"_triggerEntityLogic",2),d([u()],g.prototype,"_triggerAttribute",2),d([u()],g.prototype,"_triggerType",2),d([u()],g.prototype,"_triggerAbove",2),d([u()],g.prototype,"_triggerBelow",2),d([u()],g.prototype,"_triggerEquals",2),d([u()],g.prototype,"_triggerNotEquals",2),d([u()],g.prototype,"_triggerForMinutes",2),d([u()],g.prototype,"_triggerCombinator",2),d([u()],g.prototype,"_triggerTargetValue",2),d([u()],g.prototype,"_triggerDeltaMode",2),d([u()],g.prototype,"_triggerBaselineValue",2),d([u()],g.prototype,"_liveBaselineValue",2),d([u()],g.prototype,"_autoCompleteOnRecovery",2),d([u()],g.prototype,"_triggerFromState",2),d([u()],g.prototype,"_triggerToState",2),d([u()],g.prototype,"_triggerTargetChanges",2),d([u()],g.prototype,"_triggerRuntimeHours",2),d([u()],g.prototype,"_triggerOnStates",2),d([u()],g.prototype,"_compoundLogic",2),d([u()],g.prototype,"_compoundConditions",2),d([u()],g.prototype,"_suggestedAttributes",2),d([u()],g.prototype,"_availableAttributes",2),d([u()],g.prototype,"_entityDomain",2),d([u()],g.prototype,"_lastPerformed",2),d([u()],g.prototype,"_nfcTagId",2),d([u()],g.prototype,"_readingUnit",2),d([u()],g.prototype,"_consumesParts",2),d([u()],g.prototype,"_partsLoadFailed",2),d([u()],g.prototype,"_availableTags",2),d([u()],g.prototype,"_responsibleUserId",2),d([u()],g.prototype,"_assigneePool",2),d([u()],g.prototype,"_rotationStrategy",2),d([u()],g.prototype,"_availableUsers",2),d([u()],g.prototype,"_checklistText",2),d([u()],g.prototype,"_requiredCompletion",2),d([u()],g.prototype,"_scheduleTime",2),d([u()],g.prototype,"_actionService",2),d([u()],g.prototype,"_actionTargetEntity",2),d([u()],g.prototype,"_actionData",2),d([u()],g.prototype,"_actionDataJsonFallback",2),d([u()],g.prototype,"_actionTesting",2),d([u()],g.prototype,"_actionTestResult",2),d([u()],g.prototype,"_actionTestError",2),d([u()],g.prototype,"_qcNotes",2),d([u()],g.prototype,"_qcCost",2),d([u()],g.prototype,"_qcDuration",2),d([u()],g.prototype,"_qcFeedback",2),d([u()],g.prototype,"_environmentalEntity",2),d([u()],g.prototype,"_environmentalAttribute",2),d([u()],g.prototype,"_adaptiveEnabled",2),d([u()],g.prototype,"_adaptiveAlpha",2),d([u()],g.prototype,"_adaptiveMin",2),d([u()],g.prototype,"_adaptiveMax",2),d([u()],g.prototype,"_adaptiveSeasonal",2),d([u()],g.prototype,"_adaptivePrediction",2),d([u()],g.prototype,"_conditionAttrOptions",2);vt=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",vt)});var $,Si=w(()=>{"use strict";P();U();R();le();Ge();mt();$=class extends A{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._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=>[B(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,i=t.files?.[0];if(t.value="",!!i){this._photoUploading=!0,this._error="";try{let n=new FormData;n.append("entry_id",this.entryId),n.append("tags","photo"),n.append("file",i,i.name);let o=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:n});if(!o.ok){this._error=o.status===413?s("doc_too_large",this.lang):s("doc_upload_failed",this.lang);return}let p=await o.json();p.id&&(this._photoDocId=p.id,this._photoPreview=URL.createObjectURL(i))}catch{this._error=s("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=s("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=j(e,this.lang,s("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)?l``:h}_partsCostSuggestion(){if(this.restockDefault!==null){let i=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(i)||i<=0?null:Math.round(this.restockUnitCost*i*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let i of Object.values(this._usedParts)){let n=this.parts.find(o=>B({part_id:o.id,entry_id:o.entry_id})===B(i));n?.cost!=null&&(e+=n.cost*(i.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return h;let t=this._partsCostSuggestion();if(t==null||t<=0)return h;let i=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return l``}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en";return l` + >${a("cost_from_parts",e).replace("{amount}",i)}`}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en";return l` -
${s("complete_title",e)}${this.taskName}
+
${a("complete_title",e)}${this.taskName}
+ ${this.phaseLabel?l`
${a("phase_current",e)}: ${this.phaseLabel}
`:_}
- ${this._error?l`
${this._error}
`:h} + ${this._error?l`
${this._error}
`:_} ${this.checklist.length>0?l`
- + ${this.checklist.map((t,i)=>l` `)}
- `:h} + `:_} ${this.taskType==="reading"?l` `:h} + `:_} ${this.parts.length?l`
- ${s("complete_parts_used",e)} + ${a("complete_parts_used",e)} ${this.parts.map(t=>{let i=B({part_id:t.id,entry_id:t.entry_id}),n=this._usedParts[i],o=n!==void 0,p=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return l`
${o?l`{let _=parseFloat(c.target.value);this._usedParts={...this._usedParts,[i]:{...p,quantity:Number.isFinite(_)&&_>=.01?_:1}}}} />`:h} + @input=${d=>{let u=parseFloat(d.target.value);this._usedParts={...this._usedParts,[i]:{...p,quantity:Number.isFinite(u)&&u>=.01?u:1}}}} />`:_}
`})}
`:this.consumesInfo.length?l`
${this.consumesInfo.map(t=>l`
${t}
`)} -
`:h} +
`:_} ${this.restockDefault!==null?l` `:h} + `:_}
- ${s("completion_photo_optional",e)}${this._req("photo")} + ${a("completion_photo_optional",e)}${this._req("photo")} ${this._photoPreview?l`
+ title="${a("remove",e)}">✕
`:l`
${this.adaptiveEnabled?l` - `:h} + `:_}
- ${s("cancel",e)} + ${a("cancel",e)} 0} - title=${this._missingRequired.length?this._missingRequired.map(t=>s("err_required",e).replace("{field}",s(Ye[t]??t,e))).join(" \xB7 "):""} + title=${this._missingRequired.length?this._missingRequired.map(t=>a("err_required",e).replace("{field}",a(Ye[t]??t,e))).join(" \xB7 "):""} > - ${this._loading?s("completing",e):s("complete",e)} + ${this._loading?a("completing",e):a("complete",e)}
- `}};$.styles=[gi,S` + `}};$.styles=[fi,S` .req-mark { color: var(--error-color, #f44336); margin-left: 2px; @@ -2609,6 +2766,12 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-weight: 500; padding-bottom: 12px; } + .phase-line { + margin-top: -8px; + padding-bottom: 12px; + font-size: 13px; + color: var(--secondary-text-color); + } .content { display: flex; flex-direction: column; @@ -2750,22 +2913,22 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" color: var(--text-primary-color, #fff); border-color: var(--primary-color); } - `],d([x({attribute:!1})],$.prototype,"hass",2),d([x()],$.prototype,"entryId",2),d([x()],$.prototype,"taskId",2),d([x()],$.prototype,"taskName",2),d([x()],$.prototype,"lang",2),d([x({type:Array})],$.prototype,"checklist",2),d([x({type:Boolean})],$.prototype,"adaptiveEnabled",2),d([x()],$.prototype,"taskType",2),d([x()],$.prototype,"readingUnit",2),d([x({attribute:!1})],$.prototype,"restockDefault",2),d([x({attribute:!1})],$.prototype,"restockUnitCost",2),d([x()],$.prototype,"currencySymbol",2),d([x({attribute:!1})],$.prototype,"parts",2),d([x({attribute:!1})],$.prototype,"consumesParts",2),d([x({type:Array})],$.prototype,"consumesInfo",2),d([x({type:Array})],$.prototype,"requiredFields",2),d([u()],$.prototype,"_open",2),d([u()],$.prototype,"_notes",2),d([u()],$.prototype,"_cost",2),d([u()],$.prototype,"_duration",2),d([u()],$.prototype,"_loading",2),d([u()],$.prototype,"_error",2),d([u()],$.prototype,"_checklistState",2),d([u()],$.prototype,"_feedback",2),d([u()],$.prototype,"_photoDocId",2),d([u()],$.prototype,"_photoPreview",2),d([u()],$.prototype,"_photoUploading",2),d([u()],$.prototype,"_readingValue",2),d([u()],$.prototype,"_restockQty",2),d([u()],$.prototype,"_completedAt",2),d([u()],$.prototype,"_usedParts",2),d([x({attribute:!1})],$.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",$)});var F,Ai=w(()=>{"use strict";P();U();R();le();F=class extends A{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 N(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 o of t.parts||[]){let p=o.entry_id===e.entry_id,c=o.consumers.some(_=>_.entry_id===e.entry_id&&_.task_id===e.task_id);!p&&!c||i.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!p,object_name:o.object_name})}for(let o of e.used_parts||[]){let p=o.entry_id||e.entry_id;i.some(c=>c.part_id===o.part_id&&c.entry_id===p)||i.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:p,foreign:p!==e.entry_id,object_name:null})}let n={};for(let o of e.used_parts||[])n[`${o.entry_id||e.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={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&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),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(e){this._error=j(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return h;let e=this._lang,t=this._draft;return l` + `],c([x({attribute:!1})],$.prototype,"hass",2),c([x()],$.prototype,"entryId",2),c([x()],$.prototype,"taskId",2),c([x()],$.prototype,"taskName",2),c([x()],$.prototype,"lang",2),c([x({type:Array})],$.prototype,"checklist",2),c([x({type:Boolean})],$.prototype,"adaptiveEnabled",2),c([x()],$.prototype,"taskType",2),c([x()],$.prototype,"readingUnit",2),c([x({attribute:!1})],$.prototype,"restockDefault",2),c([x({attribute:!1})],$.prototype,"restockUnitCost",2),c([x()],$.prototype,"currencySymbol",2),c([x({attribute:!1})],$.prototype,"parts",2),c([x({attribute:!1})],$.prototype,"consumesParts",2),c([x({type:Array})],$.prototype,"consumesInfo",2),c([x({type:Array})],$.prototype,"requiredFields",2),c([x()],$.prototype,"phaseLabel",2),c([h()],$.prototype,"_open",2),c([h()],$.prototype,"_notes",2),c([h()],$.prototype,"_cost",2),c([h()],$.prototype,"_duration",2),c([h()],$.prototype,"_loading",2),c([h()],$.prototype,"_error",2),c([h()],$.prototype,"_checklistState",2),c([h()],$.prototype,"_feedback",2),c([h()],$.prototype,"_photoDocId",2),c([h()],$.prototype,"_photoPreview",2),c([h()],$.prototype,"_photoUploading",2),c([h()],$.prototype,"_readingValue",2),c([h()],$.prototype,"_restockQty",2),c([h()],$.prototype,"_completedAt",2),c([h()],$.prototype,"_usedParts",2),c([x({attribute:!1})],$.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",$)});var z,Ci=w(()=>{"use strict";L();U();R();le();z=class extends A{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 o of t.parts||[]){let p=o.entry_id===e.entry_id,d=o.consumers.some(u=>u.entry_id===e.entry_id&&u.task_id===e.task_id);!p&&!d||i.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!p,object_name:o.object_name})}for(let o of e.used_parts||[]){let p=o.entry_id||e.entry_id;i.some(d=>d.part_id===o.part_id&&d.entry_id===p)||i.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:p,foreign:p!==e.entry_id,object_name:null})}let n={};for(let o of e.used_parts||[])n[`${o.entry_id||e.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={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&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),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(e){this._error=q(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return _;let e=this._lang,t=this._draft;return l`
+ `)} + + ${r("phase_add",e)} + + ${this._phaseDefs.some(i=>i.name.trim())?o` +
${r("phase_sequence_label",e)}
+
+ ${this._phaseSeq.map((i,n)=>o` + + ${n+1}. ${t(i)} + + + `)} + +
+ `: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`

${r("trigger_configuration",e)}

@@ -1725,7 +1811,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${this._triggerType} @change=${i=>this._triggerType=i.target.value} > - ${Ms.map(i=>o``)} + ${Bs.map(i=>o``)}
${t?this._renderCompoundEditor():o` @@ -1739,7 +1825,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" 0?this._triggerEntityIds:this._triggerEntityId?[this._triggerEntityId]:[]}} .computeLabel=${()=>r("entity_id",e)} @value-changed=${i=>{let n=(i.detail.value.trigger_entities||[]).filter(Boolean);this._triggerEntityId=n[0]||"",this._triggerEntityIds=n,n[0]?this._fetchEntityAttributes(n[0]):this._fetchEntityAttributes("")}} @@ -1787,7 +1873,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" `: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`
${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``)} - `}_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`
${r("schedule_preview_ontime",e)}
`:u;return o` + `}_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`
${r("schedule_preview_ontime",e)}
`:u;return o`
${r("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?o` ${r("schedule_preview_ends",e)}`:u} ${n}
- `}_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`
${r("season_window_hint",e)}
@@ -2020,7 +2106,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${this._endsUntil} @input=${n=>this._endsUntil=n.target.value} >`: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`
${t.map((i,n)=>o` @@ -2072,7 +2158,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" max="15" .value=${this._calOffset} @input=${t=>this._calOffset=t.target.value} - >`}_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`
${m.join(" ")}
`:u}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?o` + >`}_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`
${m.join(" ")}
`:u}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?o` this._type=i.target.value} > - ${Hs.map(i=>o``)} + ${zs.map(i=>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} @change=${i=>this._priority=i.target.value} > - ${qs.map(i=>o``)} + ${Fs.map(i=>o``)}
@@ -2249,7 +2335,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .value=${this._scheduleType} @change=${i=>this._scheduleType=i.target.value} > - ${Os.map(i=>o``)} + ${Us.map(i=>o``)}
${this._scheduleType==="time_based"?o` @@ -2318,16 +2404,17 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" >
${r("checklist_help",e)}
`:u} + ${this._renderPhasesEditor(e)}

${r("require_on_completion",e)}

- ${fi.map(i=>o` + ${yi.map(i=>o` `)}
@@ -2386,7 +2473,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" r("environmental_entity_optional",e)} .computeHelper=${()=>r("environmental_entity_helper",e)} @@ -2573,6 +2660,82 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" gap: 8px; padding: 2px 0; } + .phase-def { + border: 1px solid var(--divider-color); + border-radius: 6px; + padding: 8px; + margin: 6px 0; + } + .phase-def-head { + display: flex; + align-items: center; + gap: 8px; + } + .phase-def-head ms-textfield { + flex: 1; + min-width: 0; + } + .phase-part { + max-width: 160px; + padding: 6px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + .phase-qty { + width: 64px; + padding: 6px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + .phase-checklist { + min-height: 56px; + margin-top: 6px; + } + .phase-remove { + --mdc-icon-button-size: 36px; + color: var(--secondary-text-color); + } + .phase-seq-label { + font-size: 12px; + color: var(--secondary-text-color); + margin-top: 8px; + } + .phase-seq { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 6px; + padding: 4px 0; + } + .phase-chip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 12px; + background: var(--secondary-background-color); + font-size: 13px; + } + .phase-chip-x { + border: none; + background: none; + color: var(--secondary-text-color); + cursor: pointer; + padding: 0 2px; + font-size: 12px; + } + .phase-seq-add { + padding: 4px 8px; + border: 1px dashed var(--divider-color); + border-radius: 12px; + background: transparent; + color: var(--secondary-text-color); + font-size: 13px; + } .consumes-check { display: flex; align-items: center; @@ -2750,7 +2913,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-size: 14px; cursor: pointer; } - `,c([b({attribute:!1})],g.prototype,"hass",2),c([b({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),c([b({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),c([b({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),c([b({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),c([h()],g.prototype,"parts",2),c([h()],g.prototype,"_foreignOwners",2),c([h()],g.prototype,"_open",2),c([h()],g.prototype,"_entityPickerFallback",2),c([h()],g.prototype,"_loading",2),c([h()],g.prototype,"_error",2),c([h()],g.prototype,"_entryId",2),c([h()],g.prototype,"_taskId",2),c([h()],g.prototype,"_objectChoices",2),c([h()],g.prototype,"_name",2),c([h()],g.prototype,"_type",2),c([h()],g.prototype,"_scheduleType",2),c([h()],g.prototype,"_intervalDays",2),c([h()],g.prototype,"_intervalUnit",2),c([h()],g.prototype,"_dueDate",2),c([h()],g.prototype,"_warningDays",2),c([h()],g.prototype,"_earliestCompletionDays",2),c([h()],g.prototype,"_intervalAnchor",2),c([h()],g.prototype,"_weekdays",2),c([h()],g.prototype,"_nth",2),c([h()],g.prototype,"_nthWeekday",2),c([h()],g.prototype,"_domDay",2),c([h()],g.prototype,"_domLastDay",2),c([h()],g.prototype,"_domBusiness",2),c([h()],g.prototype,"_calOffset",2),c([h()],g.prototype,"_seasonMonths",2),c([h()],g.prototype,"_endsMode",2),c([h()],g.prototype,"_endsCount",2),c([h()],g.prototype,"_endsUntil",2),c([h()],g.prototype,"_schedulePreview",2),c([h()],g.prototype,"_schedulePreviewEnded",2),c([h()],g.prototype,"_notes",2),c([h()],g.prototype,"_documentationUrl",2),c([h()],g.prototype,"_customIcon",2),c([h()],g.prototype,"_priority",2),c([h()],g.prototype,"_labels",2),c([h()],g.prototype,"_enabled",2),c([h()],g.prototype,"_triggerEntityId",2),c([h()],g.prototype,"_triggerEntityIds",2),c([h()],g.prototype,"_triggerEntityLogic",2),c([h()],g.prototype,"_triggerAttribute",2),c([h()],g.prototype,"_triggerType",2),c([h()],g.prototype,"_triggerAbove",2),c([h()],g.prototype,"_triggerBelow",2),c([h()],g.prototype,"_triggerEquals",2),c([h()],g.prototype,"_triggerNotEquals",2),c([h()],g.prototype,"_triggerForMinutes",2),c([h()],g.prototype,"_triggerCombinator",2),c([h()],g.prototype,"_triggerTargetValue",2),c([h()],g.prototype,"_triggerDeltaMode",2),c([h()],g.prototype,"_triggerBaselineValue",2),c([h()],g.prototype,"_liveBaselineValue",2),c([h()],g.prototype,"_autoCompleteOnRecovery",2),c([h()],g.prototype,"_triggerFromState",2),c([h()],g.prototype,"_triggerToState",2),c([h()],g.prototype,"_triggerTargetChanges",2),c([h()],g.prototype,"_triggerRuntimeHours",2),c([h()],g.prototype,"_triggerOnStates",2),c([h()],g.prototype,"_compoundLogic",2),c([h()],g.prototype,"_compoundConditions",2),c([h()],g.prototype,"_suggestedAttributes",2),c([h()],g.prototype,"_availableAttributes",2),c([h()],g.prototype,"_entityDomain",2),c([h()],g.prototype,"_lastPerformed",2),c([h()],g.prototype,"_nfcTagId",2),c([h()],g.prototype,"_readingUnit",2),c([h()],g.prototype,"_consumesParts",2),c([h()],g.prototype,"_partsLoadFailed",2),c([h()],g.prototype,"_availableTags",2),c([h()],g.prototype,"_responsibleUserId",2),c([h()],g.prototype,"_assigneePool",2),c([h()],g.prototype,"_rotationStrategy",2),c([h()],g.prototype,"_availableUsers",2),c([h()],g.prototype,"_checklistText",2),c([h()],g.prototype,"_requiredCompletion",2),c([h()],g.prototype,"_scheduleTime",2),c([h()],g.prototype,"_actionService",2),c([h()],g.prototype,"_actionTargetEntity",2),c([h()],g.prototype,"_actionData",2),c([h()],g.prototype,"_actionDataJsonFallback",2),c([h()],g.prototype,"_actionTesting",2),c([h()],g.prototype,"_actionTestResult",2),c([h()],g.prototype,"_actionTestError",2),c([h()],g.prototype,"_qcNotes",2),c([h()],g.prototype,"_qcCost",2),c([h()],g.prototype,"_qcDuration",2),c([h()],g.prototype,"_qcFeedback",2),c([h()],g.prototype,"_environmentalEntity",2),c([h()],g.prototype,"_environmentalAttribute",2),c([h()],g.prototype,"_adaptiveEnabled",2),c([h()],g.prototype,"_adaptiveAlpha",2),c([h()],g.prototype,"_adaptiveMin",2),c([h()],g.prototype,"_adaptiveMax",2),c([h()],g.prototype,"_adaptiveSeasonal",2),c([h()],g.prototype,"_adaptivePrediction",2),c([h()],g.prototype,"_conditionAttrOptions",2);$t=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",$t)});var U,Si=w(()=>{"use strict";P();O();R();ie();U=class extends S{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return j(this.hass)}openEdit(e){this._draft={...e},this._originalSnapshot={...e},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let e=this._draft;if(e)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),i=[];for(let l of t.parts||[]){let d=l.entry_id===e.entry_id,p=l.consumers.some(_=>_.entry_id===e.entry_id&&_.task_id===e.task_id);!d&&!p||i.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 e.used_parts||[]){let d=l.entry_id||e.entry_id;i.some(p=>p.part_id===l.part_id&&p.entry_id===d)||i.push({part_id:l.part_id,name:l.name||l.part_id,entry_id:d,foreign:d!==e.entry_id,object_name:null})}let n={};for(let l of e.used_parts||[])n[`${l.entry_id||e.entry_id}:${l.part_id}`]=l.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={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&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),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(e){this._error=T(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return u;let e=this._lang,t=this._draft;return o` + `,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({type:"maintenance_supporter/parts/overview"}),i=[];for(let l of t.parts||[]){let d=l.entry_id===e.entry_id,p=l.consumers.some(_=>_.entry_id===e.entry_id&&_.task_id===e.task_id);!d&&!p||i.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 e.used_parts||[]){let d=l.entry_id||e.entry_id;i.some(p=>p.part_id===l.part_id&&p.entry_id===d)||i.push({part_id:l.part_id,name:l.name||l.part_id,entry_id:d,foreign:d!==e.entry_id,object_name:null})}let n={};for(let l of e.used_parts||[])n[`${l.entry_id||e.entry_id}:${l.part_id}`]=l.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={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&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),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(e){this._error=T(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return u;let e=this._lang,t=this._draft;return o`