diff --git a/custom_components/maintenance_supporter/__init__.py b/custom_components/maintenance_supporter/__init__.py index f2ab4825..48d37425 100644 --- a/custom_components/maintenance_supporter/__init__.py +++ b/custom_components/maintenance_supporter/__init__.py @@ -133,6 +133,9 @@ SERVICE_COMPLETE_SCHEMA = vol.Schema( vol.Optional("duration"): vol.All(vol.Coerce(int), vol.Range(min=0, max=MAX_DURATION_MINUTES)), # Meter readings (v2.20, #83): recorded value for `reading` tasks. vol.Optional("reading_value"): vol.All(vol.Coerce(float), vol.Range(min=-1e12, max=1e12)), + # #128: who did it — a person ENTITY (validated picker, no free text); + # resolved to the linked HA user id. Omitted -> the calling user. + vol.Optional("completed_by"): cv.entity_id, } ) @@ -215,6 +218,10 @@ SERVICE_UPDATE_TASK_SCHEMA = vol.Schema( vol.Optional("notes"): vol.All(cv.string, vol.Length(max=MAX_TEXT_LENGTH)), vol.Optional("priority"): vol.In(TASK_PRIORITIES), vol.Optional("labels"): vol.All([vol.All(cv.string, vol.Length(max=MAX_LABEL_LENGTH))], vol.Length(max=MAX_LABELS)), + # #128: (re)assign via automations — a person ENTITY resolved to the + # linked HA user; the boolean clears the assignment instead. + vol.Optional("responsible_user"): cv.entity_id, + vol.Optional("clear_responsible_user"): cv.boolean, } ) @@ -425,6 +432,25 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: async_register_document_views(hass) + def _resolve_person_user_id(person_entity_id: str) -> str: + """Resolve a person ENTITY to its linked HA user id (#128). + + Person entities are the validated way to reference a user in a + service form (HA has no user selector) — the picker rules out typos, + and the entity's ``user_id`` attribute carries the account link. + """ + state = hass.states.get(person_entity_id) + if state is None or not person_entity_id.startswith("person."): + raise ServiceValidationError( + f"{person_entity_id!r} is not a known person entity" + ) + user_id = state.attributes.get("user_id") + if not user_id: + raise ServiceValidationError( + f"Person {state.name!r} is not linked to a Home Assistant user account" + ) + return str(user_id) + async def _handle_complete(call: ServiceCall) -> None: """Handle the complete service call.""" entity_id = call.data[ATTR_ENTITY_ID] @@ -442,12 +468,19 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: translation_key="no_task_for_entity", translation_placeholders={"entity_id": entity_id}, ) + # #128: explicit person beats the call context; the context covers the + # common case for free (a dashboard tap propagates the tapping user). + if call.data.get("completed_by"): + completed_by: str | None = _resolve_person_user_id(call.data["completed_by"]) + else: + completed_by = call.context.user_id if call.context else None await coordinator.complete_maintenance( task_id=task_id, notes=call.data.get("notes"), cost=call.data.get("cost"), duration=call.data.get("duration"), reading_value=call.data.get("reading_value"), + completed_by=completed_by, ) async def _handle_reset(call: ServiceCall) -> None: @@ -599,6 +632,16 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: "priority": call.data.get("priority"), "labels": call.data.get("labels"), } + # #128: assignment via automations. Person entity -> HA user id; the + # clear flag maps to "" (async_update_task_simple pops the key). + if call.data.get("responsible_user") and call.data.get("clear_responsible_user"): + raise ServiceValidationError( + "Provide either responsible_user or clear_responsible_user, not both" + ) + if call.data.get("responsible_user"): + updates["responsible_user_id"] = _resolve_person_user_id(call.data["responsible_user"]) + elif call.data.get("clear_responsible_user"): + updates["responsible_user_id"] = "" try: await async_update_task_simple( hass, diff --git a/custom_components/maintenance_supporter/config_flow_options_task_adaptive.py b/custom_components/maintenance_supporter/config_flow_options_task_adaptive.py index e28451d1..e4c71435 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_adaptive.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_adaptive.py @@ -29,10 +29,9 @@ from .helpers.task_fields import INTERVAL_DAYS_RANGE if TYPE_CHECKING: from homeassistant.config_entries import ConfigEntry -# UI-only cap for the adaptive MINIMUM interval (one year). Deliberately not a -# shared const: the adaptive min has no WS/schema twin to drift from — the -# engine clamps recommendations to the per-task min/max pair itself. -_ADAPTIVE_MIN_INTERVAL_CAP_DAYS = 365 +# Cap for the adaptive MINIMUM interval (one year). Shared with the +# task/set_adaptive WS schema since the parity round — same field, two UIs. +from .const import ADAPTIVE_MIN_INTERVAL_CAP_DAYS as _ADAPTIVE_MIN_INTERVAL_CAP_DAYS class AdaptiveMixin: diff --git a/custom_components/maintenance_supporter/config_flow_trigger.py b/custom_components/maintenance_supporter/config_flow_trigger.py index 626888d0..e5e03254 100644 --- a/custom_components/maintenance_supporter/config_flow_trigger.py +++ b/custom_components/maintenance_supporter/config_flow_trigger.py @@ -88,6 +88,46 @@ TRIGGER_ENTITY_DOMAINS = [ ] +def _apply_recovery_flag(tc: dict[str, Any], user_input: dict[str, Any]) -> None: + """Write the #53 auto-complete-on-recovery flag from a form submission. + + Editable since the dialog/flow parity round — before that the flow only + carried a previously stored value. Absence in trigger_config means off. + """ + if user_input.get("auto_complete_on_recovery"): + tc["auto_complete_on_recovery"] = True + else: + tc.pop("auto_complete_on_recovery", None) + + +def _recovery_default(tc: dict[str, Any] | None) -> bool: + return bool((tc or {}).get("auto_complete_on_recovery")) + + +def _state_selector(entity_id: str | None, *, multiple: bool = False) -> Any: + """State field bound to the trigger entity (#129 follow-up). + + Suggests the entity's known states instead of free text — typo'd states + were a real failure mode. Falls back to a plain text field when no entity + is available (defensive; the entity step always runs first). + """ + if entity_id: + return selector.StateSelector( + selector.StateSelectorConfig(entity_id=entity_id, multiple=multiple) + ) + return selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)) + + +def _parse_states(raw: Any) -> list[str]: + """Normalize an on-states submission — list from the state selector, + comma string from the legacy text fallback.""" + if isinstance(raw, list): + return [s.strip().lower() for s in raw if isinstance(s, str) and s.strip()] + if isinstance(raw, str): + return [s.strip().lower() for s in raw.split(",") if s.strip()] + return [] + + class TriggerConfigMixin: """Shared sensor trigger configuration logic for ConfigFlow and OptionsFlow. @@ -381,6 +421,7 @@ class TriggerConfigMixin: if below is not None: tc[CONF_TRIGGER_BELOW] = below tc[CONF_TRIGGER_FOR_MINUTES] = user_input.get(CONF_TRIGGER_FOR_MINUTES, 0) + _apply_recovery_flag(tc, user_input) # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -419,6 +460,10 @@ class TriggerConfigMixin: vol.Optional(CONF_TRIGGER_FOR_MINUTES, default=0): selector.NumberSelector( selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) ), + vol.Optional( + "auto_complete_on_recovery", + default=_recovery_default(self._current_task.get("trigger_config")), + ): selector.BooleanSelector(), } # Add entity_logic selector when multiple entities are selected @@ -480,6 +525,13 @@ class TriggerConfigMixin: tc = self._current_task["trigger_config"] tc[CONF_TRIGGER_TARGET_VALUE] = user_input[CONF_TRIGGER_TARGET_VALUE] tc[CONF_TRIGGER_DELTA_MODE] = user_input.get(CONF_TRIGGER_DELTA_MODE, False) + _apply_recovery_flag(tc, user_input) + # Counting start value (#102/#103): editable here since the parity + # round — an omitted field keeps the value the attribute step + # carried over; the backend clears stale Store state on change. + baseline = user_input.get("trigger_baseline_value") + if baseline is not None and baseline >= 0: + tc["trigger_baseline_value"] = baseline # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -510,6 +562,12 @@ class TriggerConfigMixin: else: current_value = state.state + prev_tc = self._current_task.get("trigger_config") or {} + baseline_key = ( + vol.Optional("trigger_baseline_value", default=prev_tc["trigger_baseline_value"]) + if "trigger_baseline_value" in prev_tc + else vol.Optional("trigger_baseline_value") + ) schema_fields: dict[Any, Any] = { vol.Required(CONF_TRIGGER_TARGET_VALUE): selector.NumberSelector( selector.NumberSelectorConfig( @@ -518,6 +576,17 @@ class TriggerConfigMixin: ) ), vol.Optional(CONF_TRIGGER_DELTA_MODE, default=False): selector.BooleanSelector(), + baseline_key: selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + mode=selector.NumberSelectorMode.BOX, + step="any", + ) + ), + vol.Optional( + "auto_complete_on_recovery", + default=_recovery_default(prev_tc), + ): selector.BooleanSelector(), } # Add entity_logic selector when multiple entities are selected @@ -590,6 +659,7 @@ class TriggerConfigMixin: if to_state: tc[CONF_TRIGGER_TO_STATE] = to_state tc[CONF_TRIGGER_TARGET_CHANGES] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1) + _apply_recovery_flag(tc, user_input) # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -608,12 +678,8 @@ class TriggerConfigMixin: return on_complete() schema_fields: dict[Any, Any] = { - vol.Optional(CONF_TRIGGER_FROM_STATE): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) - ), - vol.Optional(CONF_TRIGGER_TO_STATE): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) - ), + vol.Optional(CONF_TRIGGER_FROM_STATE): _state_selector(self._trigger_entity_id), + vol.Optional(CONF_TRIGGER_TO_STATE): _state_selector(self._trigger_entity_id), vol.Required(CONF_TRIGGER_TARGET_CHANGES, default=1): selector.NumberSelector( selector.NumberSelectorConfig( min=1, @@ -622,6 +688,10 @@ class TriggerConfigMixin: mode=selector.NumberSelectorMode.BOX, ) ), + vol.Optional( + "auto_complete_on_recovery", + default=_recovery_default(self._current_task.get("trigger_config")), + ): selector.BooleanSelector(), } # Add entity_logic selector when multiple entities are selected @@ -684,12 +754,12 @@ class TriggerConfigMixin: tc = self._current_task["trigger_config"] tc[CONF_TRIGGER_RUNTIME_HOURS] = user_input[CONF_TRIGGER_RUNTIME_HOURS] - # Parse comma-separated ON states - raw_states = user_input.get(CONF_TRIGGER_ON_STATES, "") - if raw_states and raw_states.strip(): - tc[CONF_TRIGGER_ON_STATES] = [s.strip().lower() for s in raw_states.split(",") if s.strip()] + states = _parse_states(user_input.get(CONF_TRIGGER_ON_STATES)) + if states: + tc[CONF_TRIGGER_ON_STATES] = states else: tc.pop(CONF_TRIGGER_ON_STATES, None) + _apply_recovery_flag(tc, user_input) # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -709,8 +779,9 @@ class TriggerConfigMixin: # Pre-fill existing custom states for editing current_tc = self._current_task.get("trigger_config", {}) - existing_states = current_tc.get(CONF_TRIGGER_ON_STATES) - default_states = ", ".join(existing_states) if existing_states else "" + existing_states = current_tc.get(CONF_TRIGGER_ON_STATES) or [] + # The state selector takes/returns a LIST; the text fallback a comma string. + default_states: Any = list(existing_states) if self._trigger_entity_id else ", ".join(existing_states) schema_fields: dict[Any, Any] = { vol.Required(CONF_TRIGGER_RUNTIME_HOURS): selector.NumberSelector( @@ -722,11 +793,13 @@ class TriggerConfigMixin: unit_of_measurement="h", ) ), - vol.Optional(CONF_TRIGGER_ON_STATES, default=default_states): selector.TextSelector( - selector.TextSelectorConfig( - type=selector.TextSelectorType.TEXT, - ) + vol.Optional(CONF_TRIGGER_ON_STATES, default=default_states): _state_selector( + self._trigger_entity_id, multiple=True ), + vol.Optional( + "auto_complete_on_recovery", + default=_recovery_default(current_tc), + ): selector.BooleanSelector(), } # Add entity_logic selector when multiple entities are selected @@ -791,15 +864,12 @@ class TriggerConfigMixin: return cancel logic = user_input.get(CONF_COMPOUND_LOGIC, "AND").upper() - # Carry over the panel-managed recovery flag (#53) across the - # compound rebuild, mirroring the flat-trigger path above. - prev_tc = self._current_task.get("trigger_config") or {} self._current_task["trigger_config"] = { "type": TriggerType.COMPOUND, CONF_COMPOUND_LOGIC: logic, CONF_COMPOUND_CONDITIONS: [], - **({"auto_complete_on_recovery": True} if prev_tc.get("auto_complete_on_recovery") else {}), } + _apply_recovery_flag(self._current_task["trigger_config"], user_input) if not hasattr(self, "_compound_conditions"): self._compound_conditions: list[dict[str, Any]] = [] self._compound_conditions = [] @@ -823,6 +893,10 @@ class TriggerConfigMixin: translation_key="compound_logic", ) ), + vol.Optional( + "auto_complete_on_recovery", + default=_recovery_default(self._current_task.get("trigger_config")), + ): selector.BooleanSelector(), } return self.async_show_form( step_id=step_id, @@ -961,9 +1035,9 @@ class TriggerConfigMixin: cond["trigger_target_changes"] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1) elif condition_type == TriggerType.RUNTIME: cond["trigger_runtime_hours"] = user_input[CONF_TRIGGER_RUNTIME_HOURS] - raw_states = user_input.get(CONF_TRIGGER_ON_STATES, "") - if raw_states and raw_states.strip(): - cond["trigger_on_states"] = [s.strip().lower() for s in raw_states.split(",") if s.strip()] + states = _parse_states(user_input.get(CONF_TRIGGER_ON_STATES)) + if states: + cond["trigger_on_states"] = states entity_ids = cond.get("entity_ids", []) if len(entity_ids) > 1 and user_input.get(CONF_TRIGGER_ENTITY_LOGIC): @@ -1000,13 +1074,10 @@ class TriggerConfigMixin: vol.Optional(CONF_TRIGGER_DELTA_MODE, default=False): selector.BooleanSelector(), } elif condition_type == TriggerType.STATE_CHANGE: + cond_entity = cond.get("entity_id") schema_fields = { - vol.Optional(CONF_TRIGGER_FROM_STATE): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) - ), - vol.Optional(CONF_TRIGGER_TO_STATE): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) - ), + vol.Optional(CONF_TRIGGER_FROM_STATE): _state_selector(cond_entity), + vol.Optional(CONF_TRIGGER_TO_STATE): _state_selector(cond_entity), vol.Required(CONF_TRIGGER_TARGET_CHANGES, default=1): selector.NumberSelector( selector.NumberSelectorConfig( min=1, @@ -1027,9 +1098,10 @@ class TriggerConfigMixin: unit_of_measurement="h", ) ), - vol.Optional(CONF_TRIGGER_ON_STATES, default=""): selector.TextSelector( - selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT) - ), + vol.Optional( + CONF_TRIGGER_ON_STATES, + default=[] if cond.get("entity_id") else "", + ): _state_selector(cond.get("entity_id"), multiple=True), } entity_ids = cond.get("entity_ids", []) diff --git a/custom_components/maintenance_supporter/const.py b/custom_components/maintenance_supporter/const.py index 9181722f..ef75fc32 100644 --- a/custom_components/maintenance_supporter/const.py +++ b/custom_components/maintenance_supporter/const.py @@ -403,6 +403,9 @@ CONF_ADAPTIVE_ENABLED = "adaptive_enabled" CONF_ADAPTIVE_EWA_ALPHA = "ewa_alpha" CONF_ADAPTIVE_MIN_INTERVAL = "min_interval_days" CONF_ADAPTIVE_MAX_INTERVAL = "max_interval_days" +# Deliberate cap: a MINIMUM interval above a year defeats adaptive learning. +# Shared by the options flow's adaptive step and the task/set_adaptive WS. +ADAPTIVE_MIN_INTERVAL_CAP_DAYS = 365 # --- Config Keys: Seasonal Scheduling --- CONF_SEASONAL_ENABLED = "seasonal_enabled" diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts index 61595b2c..5dc5e564 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-deeplink.test.ts @@ -33,9 +33,11 @@ function completeDialog(el: HTMLElement): MaintenanceCompleteDialog | null { } /** The dialogs are lazy code-split chunks — the open lands whenever the - * whole lazy-UI group has loaded, so poll instead of guessing a delay. */ + * whole lazy-UI group has loaded, so poll instead of guessing a delay. + * Generous window: under full-suite concurrency (70+ files) the 2 s the + * poll originally allowed was load-dependent flaky. */ async function waitForOpenCompleteDialog(el: HTMLElement): Promise { - for (let i = 0; i < 100; i++) { + for (let i = 0; i < 400; i++) { if (completeDialog(el)?.shadowRoot?.querySelector("ha-dialog")) return; await new Promise((r) => setTimeout(r, 20)); } 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 a150fc78..504d40cd 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts @@ -6,6 +6,11 @@ import type { AdaptiveConfig, HomeAssistant, MaintenanceTask, TaskPartLink, Trig import { formatDate, t, weekdayName } from "../styles"; import { UserService } from "../user-service"; import { partLinkKey } from "../helpers/shared-parts"; +import { + ENVIRONMENTAL_PICKER_DEVICE_CLASSES, + ENVIRONMENTAL_PICKER_DOMAINS, + TRIGGER_PICKER_DOMAINS, +} from "../helpers/trigger-domains"; import { describeWsError } from "../ws-errors"; import { REQUIRED_COMPLETION_KEYS, REQUIRED_COMPLETION_LABELS } from "./required-completion-labels"; @@ -25,6 +30,7 @@ const TRIGGER_TYPE_KEYS_WITH_COMPOUND = [...TRIGGER_TYPE_KEYS, "compound"]; interface CompoundConditionDraft { entityIds: string; // comma-separated raw input type: string; // threshold | counter | state_change | runtime + attribute: string; // "" = use the entity state above: string; below: string; forMinutes: string; @@ -42,7 +48,7 @@ interface CompoundConditionDraft { function emptyCondition(): CompoundConditionDraft { return { - entityIds: "", type: "threshold", above: "", below: "", forMinutes: "0", + entityIds: "", type: "threshold", attribute: "", above: "", below: "", forMinutes: "0", targetValue: "", deltaMode: false, fromState: "", toState: "", targetChanges: "", runtimeHours: "", onStates: "", carry: {}, }; @@ -51,7 +57,7 @@ function emptyCondition(): CompoundConditionDraft { /** Keys the compound editor owns via its own form fields — everything else * travels through `carry` untouched. */ const MANAGED_CONDITION_KEYS = new Set([ - "entity_id", "entity_ids", "type", + "entity_id", "entity_ids", "type", "attribute", "trigger_above", "trigger_below", "trigger_for_minutes", "trigger_target_value", "trigger_delta_mode", "trigger_from_state", "trigger_to_state", "trigger_target_changes", @@ -64,6 +70,7 @@ function conditionToDraft(c: TriggerConfig): CompoundConditionDraft { return { entityIds: ids.join(", "), type: c.type || "threshold", + attribute: c.attribute || "", above: c.trigger_above?.toString() ?? "", below: c.trigger_below?.toString() ?? "", forMinutes: c.trigger_for_minutes?.toString() ?? "0", @@ -86,6 +93,7 @@ function draftToCondition(d: CompoundConditionDraft): TriggerConfig | null { const ids = d.entityIds.split(",").map((s) => s.trim()).filter(Boolean); if (ids.length === 0) return null; const c: TriggerConfig = { ...(d.carry || {}), entity_id: ids[0], entity_ids: ids, type: d.type }; + if (d.attribute) c.attribute = d.attribute; if (d.type === "threshold") { const a = parseFloat(d.above); if (!isNaN(a)) c.trigger_above = a; const b = parseFloat(d.below); if (!isNaN(b)) c.trigger_below = b; @@ -133,6 +141,11 @@ export class MaintenanceTaskDialog extends LitElement { parts: Array<{ id: string; name: string; unit?: string }>; }> = []; @state() private _open = false; + // #129: flips the trigger entity pickers back to comma text fields when the + // HA picker fails to lay out in this mount context (see _probeEntityPickers). + @state() private _entityPickerFallback = false; + private _pickerProbeTimer: ReturnType | undefined; + private _pickerProbeStrikes = 0; @state() private _loading = false; @state() private _error = ""; @state() private _entryId = ""; @@ -263,6 +276,22 @@ export class MaintenanceTaskDialog extends LitElement { @state() private _environmentalAttribute = ""; private _environmentalInitial = ""; // for change detection on save private _environmentalAttributeInitial = ""; + // Adaptive tuning (parity with the options flow's adaptive step) — Store- + // managed like the environmental binding, saved through task/set_adaptive. + @state() private _adaptiveEnabled = false; + @state() private _adaptiveAlpha = "0.3"; + @state() private _adaptiveMin = "7"; + @state() private _adaptiveMax = "365"; + @state() private _adaptiveSeasonal = true; + @state() private _adaptivePrediction = true; + private _adaptiveInitial = ""; + + private _adaptiveSnapshot(): string { + return JSON.stringify([ + this._adaptiveEnabled, this._adaptiveAlpha, this._adaptiveMin, + this._adaptiveMax, this._adaptiveSeasonal, this._adaptivePrediction, + ]); + } private _userService: UserService | null = null; private get _lang(): string { @@ -374,6 +403,13 @@ export class MaintenanceTaskDialog extends LitElement { this._environmentalAttribute = ac.environmental_attribute || ""; this._environmentalInitial = this._environmentalEntity; this._environmentalAttributeInitial = this._environmentalAttribute; + this._adaptiveEnabled = !!ac.enabled; + this._adaptiveAlpha = (ac.ewa_alpha ?? 0.3).toString(); + this._adaptiveMin = (ac.min_interval_days ?? 7).toString(); + this._adaptiveMax = (ac.max_interval_days ?? 365).toString(); + this._adaptiveSeasonal = ac.seasonal_enabled !== false; + this._adaptivePrediction = ac.sensor_prediction_enabled !== false; + this._adaptiveInitial = this._adaptiveSnapshot(); if (task.trigger_config) { const tc = task.trigger_config; @@ -460,6 +496,13 @@ export class MaintenanceTaskDialog extends LitElement { this._environmentalAttribute = ""; this._environmentalInitial = ""; this._environmentalAttributeInitial = ""; + this._adaptiveEnabled = false; + this._adaptiveAlpha = "0.3"; + this._adaptiveMin = "7"; + this._adaptiveMax = "365"; + this._adaptiveSeasonal = true; + this._adaptivePrediction = true; + this._adaptiveInitial = this._adaptiveSnapshot(); // v1.3.0 this._actionService = ""; this._actionTargetEntity = ""; @@ -802,6 +845,46 @@ export class MaintenanceTaskDialog extends LitElement { } } + /** Per-entity attribute options — generic entity_id-keyed cache, fetched + * lazily; serves the compound condition rows AND the environmental + * attribute dropdown. (Parity round: the flow's compound path always had + * an attribute step; the dialog only carried it without an editor.) */ + @state() private _conditionAttrOptions: Record< + string, + { suggested: string[]; available: Array<{ name: string; numeric: boolean }> } + > = {}; + private _conditionAttrPending = new Set(); + + private _fetchConditionAttributes(entityId: string): void { + if (!entityId || !this.hass) return; + if (this._conditionAttrOptions[entityId] || this._conditionAttrPending.has(entityId)) return; + this._conditionAttrPending.add(entityId); + void this.hass.connection + .sendMessagePromise({ + type: "maintenance_supporter/entity/attributes", + entity_id: entityId, + }) + .then((result) => { + const r = result as { + suggested_attributes: string[]; + available_attributes: Array<{ name: string; numeric: boolean }>; + }; + this._conditionAttrOptions = { + ...this._conditionAttrOptions, + [entityId]: { + suggested: r.suggested_attributes || [], + available: r.available_attributes || [], + }, + }; + }) + .catch(() => { + this._conditionAttrOptions = { + ...this._conditionAttrOptions, + [entityId]: { suggested: [], available: [] }, + }; + }); + } + private async _fetchEntityAttributes(entityId: string): Promise { if (!entityId || !this.hass) { this._suggestedAttributes = []; @@ -894,6 +977,14 @@ export class MaintenanceTaskDialog extends LitElement { private async _save(): Promise { if (this._loading) return; // synchronous re-entry guard (double-click) if (!this._name.trim()) return; + if (this._adaptiveSnapshot() !== this._adaptiveInitial) { + const minIv = parseInt(this._adaptiveMin, 10); + const maxIv = parseInt(this._adaptiveMax, 10); + if (!isNaN(minIv) && !isNaN(maxIv) && minIv > maxIv) { + this._error = `${t("adaptive_min_interval", this._lang)} > ${t("adaptive_max_interval", this._lang)}`; + return; + } + } this._loading = true; this._error = ""; try { @@ -1116,6 +1207,30 @@ export class MaintenanceTaskDialog extends LitElement { } } + // Adaptive tuning is Store-managed like the environmental binding — + // dedicated endpoint, only called when something actually changed. + if (savedTaskId && this._adaptiveSnapshot() !== this._adaptiveInitial) { + const alpha = parseFloat(this._adaptiveAlpha); + const minIv = parseInt(this._adaptiveMin, 10); + const maxIv = parseInt(this._adaptiveMax, 10); + try { + await this.hass.connection.sendMessagePromise({ + type: "maintenance_supporter/task/set_adaptive", + entry_id: this._entryId, + task_id: savedTaskId, + enabled: this._adaptiveEnabled, + ...(alpha >= 0.1 && alpha <= 0.9 ? { ewa_alpha: alpha } : {}), + ...(!isNaN(minIv) && minIv >= 1 ? { min_interval_days: minIv } : {}), + ...(!isNaN(maxIv) && maxIv >= 1 ? { max_interval_days: maxIv } : {}), + seasonal_enabled: this._adaptiveSeasonal, + sensor_prediction_enabled: this._adaptivePrediction, + }); + this._adaptiveInitial = this._adaptiveSnapshot(); + } catch { + /* non-fatal — task itself saved */ + } + } + this._open = false; this.dispatchEvent(new CustomEvent("task-saved")); } catch (e) { @@ -1127,6 +1242,11 @@ export class MaintenanceTaskDialog extends LitElement { private _close(): void { this._open = false; + if (this._pickerProbeTimer !== undefined) { + clearTimeout(this._pickerProbeTimer); + this._pickerProbeTimer = undefined; + } + this._pickerProbeStrikes = 0; } private _renderTriggerFields() { @@ -1148,17 +1268,40 @@ export class MaintenanceTaskDialog extends LitElement { ${isCompound ? this._renderCompoundEditor() : html` - 0 ? this._triggerEntityIds.join(", ") : this._triggerEntityId} - @input=${(e: Event) => { - const raw = (e.target as HTMLInputElement).value; - const ids = raw.split(",").map((s: string) => s.trim()).filter(Boolean); + ${this._entityPickerFallback ? html` + 0 ? this._triggerEntityIds.join(", ") : this._triggerEntityId} + @input=${(e: Event) => { + const raw = (e.target as HTMLInputElement).value; + const ids = raw.split(",").map((s: string) => s.trim()).filter(Boolean); + this._triggerEntityId = ids[0] || ""; + this._triggerEntityIds = ids; + if (ids[0]) this._fetchEntityAttributes(ids[0]); + }} + > + ` : html` + 0 + ? this._triggerEntityIds + : this._triggerEntityId ? [this._triggerEntityId] : [], + }} + .computeLabel=${() => t("entity_id", L)} + @value-changed=${(e: CustomEvent) => { + const ids = ((e.detail.value as { trigger_entities?: string[] }).trigger_entities || []).filter(Boolean); this._triggerEntityId = ids[0] || ""; this._triggerEntityIds = ids; if (ids[0]) this._fetchEntityAttributes(ids[0]); + else this._fetchEntityAttributes(""); }} - > + >`} ${this._triggerEntityIds.length > 1 ? html`
@@ -1275,11 +1418,28 @@ export class MaintenanceTaskDialog extends LitElement { @click=${() => this._removeCondition(i)} >✕
- this._patchCondition(i, { entityIds: (e.target as HTMLInputElement).value })} - > + ${this._entityPickerFallback ? html` + this._patchCondition(i, { entityIds: (e.target as HTMLInputElement).value })} + > + ` : html` + s.trim()).filter(Boolean) }} + .computeLabel=${() => t("entity_id", L)} + @value-changed=${(e: CustomEvent) => { + const ids = ((e.detail.value as { condition_entities?: string[] }).condition_entities || []).filter(Boolean); + this._patchCondition(i, { entityIds: ids.join(", ") }); + }} + >`} + ${this._renderConditionAttribute(c, i)}
(this._adaptiveEnabled = (e.target as HTMLInputElement).checked)} + /> + ${t("adaptive_enabled", L)} + + ${this._adaptiveEnabled ? html` + (this._adaptiveMin = (e.target as HTMLInputElement).value)} + > + (this._adaptiveMax = (e.target as HTMLInputElement).value)} + > + (this._adaptiveAlpha = (e.target as HTMLInputElement).value)} + > + + + ` : nothing} + + `; + } + + /** Environmental attribute — the same live-fetched dropdown the flat and + * compound attribute fields use, keyed by the environmental entity. */ + private _renderEnvironmentalAttribute(L: string) { + this._fetchConditionAttributes(this._environmentalEntity); + const opts = this._conditionAttrOptions[this._environmentalEntity]; + if (opts && opts.available.length > 0) { + return html` +
+ + +
+ `; + } + return html` + (this._environmentalAttribute = (e.target as HTMLInputElement).value.trim())} + > + `; + } + + /** Attribute selector for one compound condition — the same live-fetched + * dropdown the flat editor has, keyed by the condition's first entity. */ + private _renderConditionAttribute(c: CompoundConditionDraft, i: number) { + const L = this._lang; + const firstId = c.entityIds.split(",")[0]?.trim() || ""; + if (firstId) this._fetchConditionAttributes(firstId); + const opts = firstId ? this._conditionAttrOptions[firstId] : undefined; + if (opts && opts.available.length > 0) { + return html` +
+ + +
+ `; + } + return html` + this._patchCondition(i, { attribute: (e.target as HTMLInputElement).value.trim() })} + > + `; + } + /** Type-specific inputs for a single compound condition (mirrors the flat * per-type fields, bound to the condition draft). */ private _renderConditionTypeFields(c: CompoundConditionDraft, i: number) { @@ -1322,21 +1674,34 @@ export class MaintenanceTaskDialog extends LitElement { `; } if (c.type === "state_change") { + const condEntity = c.entityIds.split(",")[0]?.trim() || ""; return html` - this._patchCondition(i, { fromState: (e.target as HTMLInputElement).value })}> - this._patchCondition(i, { toState: (e.target as HTMLInputElement).value })}> + ${this._renderStateField({ + label: t("from_state_optional", L), + value: c.fromState, + entityId: condEntity, + onInput: (v) => this._patchCondition(i, { fromState: v }), + })} + ${this._renderStateField({ + label: t("to_state_optional", L), + value: c.toState, + entityId: condEntity, + onInput: (v) => this._patchCondition(i, { toState: v }), + })} this._patchCondition(i, { targetChanges: (e.target as HTMLInputElement).value })}> `; } if (c.type === "runtime") { + const condEntity = c.entityIds.split(",")[0]?.trim() || ""; return html` this._patchCondition(i, { runtimeHours: (e.target as HTMLInputElement).value })}> - this._patchCondition(i, { onStates: (e.target as HTMLInputElement).value })}> + ${this._renderOnStatesField({ + value: c.onStates, + entityId: condEntity, + onInput: (v) => this._patchCondition(i, { onStates: v }), + })} `; } return nothing; @@ -1396,6 +1761,7 @@ export class MaintenanceTaskDialog extends LitElement { protected updated(changed: Map): void { super.updated?.(changed); + this._scheduleEntityPickerProbe(); for (const key of changed.keys()) { if (MaintenanceTaskDialog._PREVIEW_RELEVANT.has(String(key))) { this._schedulePreviewRefresh(); @@ -1404,6 +1770,62 @@ export class MaintenanceTaskDialog extends LitElement { } } + /** #129 SAFETY NET (not the primary fix): HA's modern pickers resolve data + * via Lit context — events that must bubble up to providers on the + * element. A dialog mounted outside that tree gets + * pickers that upgrade to an EMPTY shadow root. The root cause is solved + * by mounting dialogs inside 's shadow root + * (dialog-mount.ts); this probe remains as defense in depth for unknown + * contexts: two consecutive zero-height measurements of the leaf pickers + * inside a visible dialog flip the trigger fields back to the + * comma-separated text inputs. */ + private _scheduleEntityPickerProbe(): void { + if ( + this._entityPickerFallback + || this._pickerProbeTimer !== undefined + || !this._open + || this._scheduleType !== "sensor_based" + ) return; + this._pickerProbeTimer = setTimeout(() => this._probeEntityPickers(), 1500); + } + + private _probeEntityPickers(): void { + this._pickerProbeTimer = undefined; + if (this._entityPickerFallback || !this._open) return; + const form = this.shadowRoot?.querySelector("ha-form.entity-picker-form"); + const dialogVisible = (this.shadowRoot?.querySelector(".content")?.offsetHeight ?? 0) > 0; + if (!form || !dialogVisible) { + this._pickerProbeStrikes = 0; + return; + } + // The broken-context signature is subtle: the form (and even a + // ha-entities-picker wrapper) may keep its label height while the + // ha-entity-picker LEAVES upgrade to empty shadow roots — so collect the + // leaf pickers across ALL picker forms and require every one to lay out. + const collectLeaves = (el: Element | null, out: HTMLElement[], depth = 0): void => { + if (!el || depth > 10) return; + if ((el.tagName?.toLowerCase() ?? "") === "ha-entity-picker") out.push(el as HTMLElement); + for (const root of [el.shadowRoot, el]) { + if (!root) continue; + for (const child of Array.from(root.children ?? [])) collectLeaves(child, out, depth + 1); + } + }; + const forms = [...(this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form") ?? [])]; + const leaves: HTMLElement[] = []; + for (const f of forms) collectLeaves(f, leaves); + const broken = leaves.length === 0 || leaves.some((leaf) => leaf.offsetHeight === 0); + if (form.offsetHeight === 0 || broken) { + this._pickerProbeStrikes += 1; + if (this._pickerProbeStrikes >= 2) { + this._entityPickerFallback = true; + return; + } + this._pickerProbeTimer = setTimeout(() => this._probeEntityPickers(), 700); + } else { + this._pickerProbeStrikes = 0; + } + } + private _schedulePreviewRefresh(): void { if (this._previewTimer) clearTimeout(this._previewTimer); this._previewTimer = setTimeout(() => void this._fetchSchedulePreview(), 300); @@ -1773,17 +2195,19 @@ export class MaintenanceTaskDialog extends LitElement { } if (this._triggerType === "state_change") { return html` - (this._triggerFromState = (e.target as HTMLInputElement).value)} - > + ${this._renderStateField({ + label: t("from_state_optional", L), + value: this._triggerFromState, + entityId: this._triggerEntityId, + onInput: (v) => (this._triggerFromState = v), + })}
${t("state_value_help", L)}
- (this._triggerToState = (e.target as HTMLInputElement).value)} - > + ${this._renderStateField({ + label: t("to_state_optional", L), + value: this._triggerToState, + entityId: this._triggerEntityId, + onInput: (v) => (this._triggerToState = v), + })} (this._triggerRuntimeHours = (e.target as HTMLInputElement).value)} > - (this._triggerOnStates = (e.target as HTMLInputElement).value)} - > + ${this._renderOnStatesField({ + value: this._triggerOnStates, + entityId: this._triggerEntityId, + onInput: (v) => (this._triggerOnStates = v), + })}
${t("runtime_on_states_help", L)}
`; } @@ -2064,20 +2487,34 @@ export class MaintenanceTaskDialog extends LitElement { ` : nothing} ${this._renderTriggerFields()} ${this._scheduleType === "sensor_based" ? html` - (this._environmentalEntity = (e.target as HTMLInputElement).value.trim())} - > - ${this._environmentalEntity ? html` + ${this._entityPickerFallback ? html` (this._environmentalAttribute = (e.target as HTMLInputElement).value.trim())} + label="${t("environmental_entity_optional", L)}" + helper="${t("environmental_entity_helper", L)}" + .value=${this._environmentalEntity} + @input=${(e: Event) => (this._environmentalEntity = (e.target as HTMLInputElement).value.trim())} > - ` : nothing} + ` : html` + t("environmental_entity_optional", L)} + .computeHelper=${() => t("environmental_entity_helper", L)} + @value-changed=${(e: CustomEvent) => { + this._environmentalEntity = ((e.detail.value as { environmental_entity?: string }).environmental_entity || "").trim(); + }} + >`} + ${this._environmentalEntity ? this._renderEnvironmentalAttribute(L) : nothing} ` : nothing} + ${this._renderAdaptiveSection(L)} summary { + .ca-section > summary, + .adaptive-section > summary { cursor: pointer; font-weight: 500; } + .adaptive-section ms-textfield { + width: 100%; + margin-top: 8px; + display: block; + } + .adaptive-section label { + display: block; + margin-top: 8px; + } .ca-section ms-textfield, .ca-section ha-entity-picker, .ca-section ha-service-picker, diff --git a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts index de1c37f1..d0d99288 100644 --- a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts +++ b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts @@ -1,8 +1,17 @@ /** Standalone dialog mounting helper. * - * Mounts the existing MaintenanceObjectDialog / MaintenanceTaskDialog onto - * document.body so they can be opened from any Lovelace context — without - * the user navigating to the panel first. + * Mounts the existing MaintenanceObjectDialog / MaintenanceTaskDialog into + * 's shadow root — the same place HA's own dialogs live — + * so they can be opened from any Lovelace context without the user + * navigating to the panel first. + * + * The mount point matters (#129): HA's modern components (ha-entity-picker + * and friends) resolve data through Lit context — `context-request` events + * that bubble UP the DOM to providers on the element. A + * dialog on document.body is a SIBLING tree of , the events + * never reach the providers, and such components upgrade to an empty shadow + * root. Mounting inside 's shadow root keeps the provider + * chain intact; document.body remains only as a last-resort fallback. * * Usage from a strategy or card click handler: * @@ -54,11 +63,21 @@ function getHass(): HomeAssistant | undefined { return root?.hass; } +/** Where dialogs live: 's shadow root (context providers + * reachable), falling back to document.body if HA's root ever goes away. */ +function dialogHost(): ShadowRoot | HTMLElement { + return document.querySelector("home-assistant")?.shadowRoot ?? document.body; +} + function getOrCreate(tag: string): T { - let el = document.body.querySelector(tag); + const host = dialogHost(); + let el = host.querySelector(tag) ?? document.body.querySelector(tag); if (!el) { el = document.createElement(tag) as T; - document.body.appendChild(el); + host.appendChild(el); + } else if (el.parentNode !== host) { + // Adopt a dialog mounted by an older bundle onto document.body. + host.appendChild(el); } return el; } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/cs.json b/custom_components/maintenance_supporter/frontend-src/locales/cs.json index c7fa18c6..9f79d1b1 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/cs.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Obnovit", "environmental_entity_optional": "Senzor prostředí (volitelný)", "environmental_entity_helper": "např. sensor.outdoor_temperature — upravuje interval podle podmínek prostředí", + "adaptive_prediction_enabled": "Povolit predikce řízené senzory", + "adaptive_seasonal_enabled": "Povolit sezónní povědomí", + "adaptive_max_interval": "Maximální interval (dny)", + "adaptive_min_interval": "Minimální interval (dny)", + "adaptive_ewa_alpha": "Rychlost učení (alpha)", + "adaptive_enabled": "Povolit adaptivní plánování", + "adaptive_section_title": "Adaptivní plánování", "environmental_attribute_optional": "Atribut prostředí (volitelný)", "nfc_tag_id": "ID NFC tagu", "nfc_linked": "NFC tag propojen", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/da.json b/custom_components/maintenance_supporter/frontend-src/locales/da.json index 74624168..b3950ce1 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/da.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/da.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Opdater", "environmental_entity_optional": "Miljøsensor (valgfrit)", "environmental_entity_helper": "f.eks. sensor.outdoor_temperature — justerer intervallet baseret på miljøforhold", + "adaptive_prediction_enabled": "Aktivér sensordrevne forudsigelser", + "adaptive_seasonal_enabled": "Aktivér sæsonbevidsthed", + "adaptive_max_interval": "Maksimumsinterval (dage)", + "adaptive_min_interval": "Minimumsinterval (dage)", + "adaptive_ewa_alpha": "Læringsrate (alfa)", + "adaptive_enabled": "Aktivér adaptiv planlægning", + "adaptive_section_title": "Adaptiv planlægning", "environmental_attribute_optional": "Miljøattribut (valgfrit)", "nfc_tag_id": "NFC-tag-ID", "nfc_linked": "NFC-tag tilknyttet", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/de.json b/custom_components/maintenance_supporter/frontend-src/locales/de.json index aaccc572..7db29172 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/de.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/de.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Aktualisieren", "environmental_entity_optional": "Umgebungs-Sensor (optional)", "environmental_entity_helper": "z.B. sensor.aussentemperatur — passt das Intervall an Umgebungswerte an", + "adaptive_prediction_enabled": "Sensorbasierte Vorhersagen aktivieren", + "adaptive_seasonal_enabled": "Saisonale Anpassung aktivieren", + "adaptive_max_interval": "Maximales Intervall (Tage)", + "adaptive_min_interval": "Minimales Intervall (Tage)", + "adaptive_ewa_alpha": "Lernrate (Alpha)", + "adaptive_enabled": "Adaptive Planung aktivieren", + "adaptive_section_title": "Adaptive Planung", "environmental_attribute_optional": "Umgebungs-Attribut (optional)", "nfc_tag_id": "NFC-Tag-ID", "nfc_linked": "NFC-Tag verknüpft", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/en.json b/custom_components/maintenance_supporter/frontend-src/locales/en.json index a2f0ce44..b9ccbe9f 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/en.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/en.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Refresh", "environmental_entity_optional": "Environmental sensor (optional)", "environmental_entity_helper": "e.g. sensor.outdoor_temperature — 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", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/es.json b/custom_components/maintenance_supporter/frontend-src/locales/es.json index cf5b3eaa..50161744 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/es.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/es.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Actualizar", "environmental_entity_optional": "Sensor ambiental (opcional)", "environmental_entity_helper": "p.ej. sensor.temperatura_exterior — ajusta el intervalo según las condiciones ambientales", + "adaptive_prediction_enabled": "Activar predicciones de sensor", + "adaptive_seasonal_enabled": "Activar conciencia estacional", + "adaptive_max_interval": "Intervalo máximo (días)", + "adaptive_min_interval": "Intervalo mínimo (días)", + "adaptive_ewa_alpha": "Tasa de aprendizaje (alpha)", + "adaptive_enabled": "Activar programación adaptativa", + "adaptive_section_title": "Programación adaptativa", "environmental_attribute_optional": "Atributo ambiental (opcional)", "nfc_tag_id": "ID de etiqueta NFC", "nfc_linked": "Etiqueta NFC vinculada", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fi.json b/custom_components/maintenance_supporter/frontend-src/locales/fi.json index b638d02b..df30c7aa 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fi.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Päivitä", "environmental_entity_optional": "Ympäristöanturi (valinnainen)", "environmental_entity_helper": "esim. sensor.outdoor_temperature — säätää väliä ympäristöolosuhteiden mukaan", + "adaptive_prediction_enabled": "Ota anturiperusteiset ennusteet käyttöön", + "adaptive_seasonal_enabled": "Ota kausitietoisuus käyttöön", + "adaptive_max_interval": "Enimmäisaikaväli (päivää)", + "adaptive_min_interval": "Vähimmäisaikaväli (päivää)", + "adaptive_ewa_alpha": "Oppimisnopeus (alfa)", + "adaptive_enabled": "Ota mukautuva aikataulutus käyttöön", + "adaptive_section_title": "Mukautuva aikataulutus", "environmental_attribute_optional": "Ympäristöattribuutti (valinnainen)", "nfc_tag_id": "NFC-tunnisteen tunnus", "nfc_linked": "NFC-tunniste linkitetty", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fr.json b/custom_components/maintenance_supporter/frontend-src/locales/fr.json index 43ea0ce6..06a43a8a 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fr.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Actualiser", "environmental_entity_optional": "Capteur d'environnement (optionnel)", "environmental_entity_helper": "ex. sensor.temperature_exterieure — ajuste l'intervalle selon les conditions environnementales", + "adaptive_prediction_enabled": "Activer les prédictions capteur", + "adaptive_seasonal_enabled": "Activer la sensibilité saisonnière", + "adaptive_max_interval": "Intervalle maximum (jours)", + "adaptive_min_interval": "Intervalle minimum (jours)", + "adaptive_ewa_alpha": "Taux d'apprentissage (alpha)", + "adaptive_enabled": "Activer la planification adaptative", + "adaptive_section_title": "Planification adaptative", "environmental_attribute_optional": "Attribut d'environnement (optionnel)", "nfc_tag_id": "ID tag NFC", "nfc_linked": "Tag NFC lié", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hi.json b/custom_components/maintenance_supporter/frontend-src/locales/hi.json index fd28005d..2b63f6f0 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hi.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "रिफ़्रेश करें", "environmental_entity_optional": "पर्यावरण सेंसर (वैकल्पिक)", "environmental_entity_helper": "उदा. sensor.outdoor_temperature — पर्यावरणीय स्थितियों के आधार पर अंतराल समायोजित करता है", + "adaptive_prediction_enabled": "सेंसर-संचालित पूर्वानुमान सक्षम करें", + "adaptive_seasonal_enabled": "मौसमी जागरूकता सक्षम करें", + "adaptive_max_interval": "अधिकतम अंतराल (दिन)", + "adaptive_min_interval": "न्यूनतम अंतराल (दिन)", + "adaptive_ewa_alpha": "अधिगम दर (अल्फा)", + "adaptive_enabled": "अनुकूली अनुसूचन सक्षम करें", + "adaptive_section_title": "अनुकूली अनुसूचन", "environmental_attribute_optional": "पर्यावरण विशेषता (वैकल्पिक)", "nfc_tag_id": "NFC टैग ID", "nfc_linked": "NFC टैग लिंक किया गया", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hu.json b/custom_components/maintenance_supporter/frontend-src/locales/hu.json index 283f422f..8e6a1758 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hu.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Frissítés", "environmental_entity_optional": "Környezeti érzékelő (opcionális)", "environmental_entity_helper": "pl. sensor.outdoor_temperature — a környezeti feltételek alapján igazítja az intervallumot", + "adaptive_prediction_enabled": "Érzékelővezérelt előrejelzések engedélyezése", + "adaptive_seasonal_enabled": "Szezonális igazodás engedélyezése", + "adaptive_max_interval": "Maximális intervallum (nap)", + "adaptive_min_interval": "Minimális intervallum (nap)", + "adaptive_ewa_alpha": "Tanulási ráta (alfa)", + "adaptive_enabled": "Adaptív ütemezés engedélyezése", + "adaptive_section_title": "Adaptív ütemezés", "environmental_attribute_optional": "Környezeti attribútum (opcionális)", "nfc_tag_id": "NFC címke azonosító", "nfc_linked": "NFC címke hozzárendelve", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/it.json b/custom_components/maintenance_supporter/frontend-src/locales/it.json index 860b1a05..b2605821 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/it.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/it.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Aggiorna", "environmental_entity_optional": "Sensore ambientale (opzionale)", "environmental_entity_helper": "es. sensor.temperatura_esterna — regola l'intervallo in base alle condizioni ambientali", + "adaptive_prediction_enabled": "Abilita previsioni sensore", + "adaptive_seasonal_enabled": "Abilita consapevolezza stagionale", + "adaptive_max_interval": "Intervallo massimo (giorni)", + "adaptive_min_interval": "Intervallo minimo (giorni)", + "adaptive_ewa_alpha": "Tasso di apprendimento (alpha)", + "adaptive_enabled": "Abilita pianificazione adattiva", + "adaptive_section_title": "Pianificazione adattiva", "environmental_attribute_optional": "Attributo ambientale (opzionale)", "nfc_tag_id": "ID tag NFC", "nfc_linked": "Tag NFC collegato", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ja.json b/custom_components/maintenance_supporter/frontend-src/locales/ja.json index 7c2b9368..546f9df7 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ja.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "更新", "environmental_entity_optional": "環境センサー(任意)", "environmental_entity_helper": "例: sensor.outdoor_temperature — 環境条件に応じて間隔を調整します", + "adaptive_prediction_enabled": "センサー駆動の予測を有効にする", + "adaptive_seasonal_enabled": "季節認識を有効にする", + "adaptive_max_interval": "最大間隔 (日)", + "adaptive_min_interval": "最小間隔 (日)", + "adaptive_ewa_alpha": "学習率 (アルファ)", + "adaptive_enabled": "適応スケジューリングを有効にする", + "adaptive_section_title": "適応スケジューリング", "environmental_attribute_optional": "環境属性(任意)", "nfc_tag_id": "NFCタグID", "nfc_linked": "NFCタグ連携済み", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ko.json b/custom_components/maintenance_supporter/frontend-src/locales/ko.json index 5093e8df..3e11fc06 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ko.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "새로 고침", "environmental_entity_optional": "환경 센서 (선택)", "environmental_entity_helper": "예: sensor.outdoor_temperature — 환경 조건에 따라 주기를 조정합니다", + "adaptive_prediction_enabled": "센서 기반 예측 사용", + "adaptive_seasonal_enabled": "계절 인식 사용", + "adaptive_max_interval": "최대 주기(일)", + "adaptive_min_interval": "최소 주기(일)", + "adaptive_ewa_alpha": "학습률(alpha)", + "adaptive_enabled": "적응형 일정 사용", + "adaptive_section_title": "적응형 일정", "environmental_attribute_optional": "환경 속성 (선택)", "nfc_tag_id": "NFC 태그 ID", "nfc_linked": "NFC 태그 연결됨", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nb.json b/custom_components/maintenance_supporter/frontend-src/locales/nb.json index a4ff61fc..79bc0cd6 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nb.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Oppdater", "environmental_entity_optional": "Miljøsensor (valgfritt)", "environmental_entity_helper": "f.eks. sensor.outdoor_temperature — justerer intervallet basert på miljøforhold", + "adaptive_prediction_enabled": "Aktiver sensordrevne prognoser", + "adaptive_seasonal_enabled": "Aktiver sesongbevissthet", + "adaptive_max_interval": "Største intervall (dager)", + "adaptive_min_interval": "Minste intervall (dager)", + "adaptive_ewa_alpha": "Læringsrate (alfa)", + "adaptive_enabled": "Aktiver adaptiv planlegging", + "adaptive_section_title": "Adaptiv planlegging", "environmental_attribute_optional": "Miljøattributt (valgfritt)", "nfc_tag_id": "NFC-brikke-ID", "nfc_linked": "NFC-brikke koblet", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nl.json b/custom_components/maintenance_supporter/frontend-src/locales/nl.json index 5c9d1a06..dbff7b52 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nl.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Vernieuwen", "environmental_entity_optional": "Omgevingssensor (optioneel)", "environmental_entity_helper": "bv. sensor.buitentemperatuur — past het interval aan op basis van omgevingswaarden", + "adaptive_prediction_enabled": "Sensorgestuurde voorspellingen inschakelen", + "adaptive_seasonal_enabled": "Seizoensbewustzijn inschakelen", + "adaptive_max_interval": "Maximaal interval (dagen)", + "adaptive_min_interval": "Minimaal interval (dagen)", + "adaptive_ewa_alpha": "Leersnelheid (alpha)", + "adaptive_enabled": "Adaptieve planning inschakelen", + "adaptive_section_title": "Adaptieve planning", "environmental_attribute_optional": "Omgevingsattribuut (optioneel)", "nfc_tag_id": "NFC-tag-ID", "nfc_linked": "NFC-tag gekoppeld", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pl.json b/custom_components/maintenance_supporter/frontend-src/locales/pl.json index 5fdedeb2..0b8c8dd8 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pl.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Odśwież", "environmental_entity_optional": "Czujnik środowiskowy (opcjonalne)", "environmental_entity_helper": "np. sensor.outdoor_temperature — dostosowuje interwał na podstawie warunków środowiskowych", + "adaptive_prediction_enabled": "Włącz predykcje sterowane czujnikami", + "adaptive_seasonal_enabled": "Włącz świadomość sezonową", + "adaptive_max_interval": "Maksymalny interwał (dni)", + "adaptive_min_interval": "Minimalny interwał (dni)", + "adaptive_ewa_alpha": "Tempo uczenia (alpha)", + "adaptive_enabled": "Włącz adaptacyjne planowanie", + "adaptive_section_title": "Adaptacyjne planowanie", "environmental_attribute_optional": "Atrybut środowiskowy (opcjonalne)", "nfc_tag_id": "ID tagu NFC", "nfc_linked": "Tag NFC powiązany", 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 5ad1d7f2..a6b3f107 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Atualizar", "environmental_entity_optional": "Sensor ambiental (opcional)", "environmental_entity_helper": "ex.: sensor.outdoor_temperature — ajusta o intervalo com base nas condições ambientais", + "adaptive_prediction_enabled": "Ativar previsões baseadas em sensor", + "adaptive_seasonal_enabled": "Ativar sazonalidade", + "adaptive_max_interval": "Intervalo máximo (dias)", + "adaptive_min_interval": "Intervalo mínimo (dias)", + "adaptive_ewa_alpha": "Taxa de aprendizado (alpha)", + "adaptive_enabled": "Ativar agendamento adaptativo", + "adaptive_section_title": "Agendamento adaptativo", "environmental_attribute_optional": "Atributo ambiental (opcional)", "nfc_tag_id": "ID da tag NFC", "nfc_linked": "Tag NFC vinculada", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt.json b/custom_components/maintenance_supporter/frontend-src/locales/pt.json index 7cfd6638..7b3341c9 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Atualizar", "environmental_entity_optional": "Sensor ambiental (opcional)", "environmental_entity_helper": "ex. sensor.temperatura_exterior — ajusta o intervalo segundo as condições ambientais", + "adaptive_prediction_enabled": "Ativar previsões baseadas em sensores", + "adaptive_seasonal_enabled": "Ativar consciência sazonal", + "adaptive_max_interval": "Intervalo máximo (dias)", + "adaptive_min_interval": "Intervalo mínimo (dias)", + "adaptive_ewa_alpha": "Taxa de aprendizagem (alfa)", + "adaptive_enabled": "Ativar agendamento adaptativo", + "adaptive_section_title": "Agendamento Adaptativo", "environmental_attribute_optional": "Atributo ambiental (opcional)", "nfc_tag_id": "ID da etiqueta NFC", "nfc_linked": "Etiqueta NFC associada", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ru.json b/custom_components/maintenance_supporter/frontend-src/locales/ru.json index ef11715b..195f93e1 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ru.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Обновить", "environmental_entity_optional": "Датчик окружающей среды (опционально)", "environmental_entity_helper": "напр. sensor.outdoor_temperature — корректирует интервал в зависимости от условий", + "adaptive_prediction_enabled": "Включить прогнозы на основе датчиков", + "adaptive_seasonal_enabled": "Учитывать сезонность", + "adaptive_max_interval": "Максимальный интервал (дни)", + "adaptive_min_interval": "Минимальный интервал (дни)", + "adaptive_ewa_alpha": "Скорость обучения (альфа)", + "adaptive_enabled": "Включить адаптивное планирование", + "adaptive_section_title": "Адаптивное планирование", "environmental_attribute_optional": "Атрибут среды (опционально)", "nfc_tag_id": "ID NFC-метки", "nfc_linked": "NFC-метка привязана", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/sv.json b/custom_components/maintenance_supporter/frontend-src/locales/sv.json index 590bdc1e..7699d221 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/sv.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Uppdatera", "environmental_entity_optional": "Miljösensor (valfritt)", "environmental_entity_helper": "t.ex. sensor.outdoor_temperature — justerar intervallet baserat på miljöförhållanden", + "adaptive_prediction_enabled": "Aktivera sensorstyrda prediktioner", + "adaptive_seasonal_enabled": "Aktivera säsongsmedvetenhet", + "adaptive_max_interval": "Största intervall (dagar)", + "adaptive_min_interval": "Minsta intervall (dagar)", + "adaptive_ewa_alpha": "Inlärningshastighet (alpha)", + "adaptive_enabled": "Aktivera adaptiv schemaläggning", + "adaptive_section_title": "Adaptiv schemaläggning", "environmental_attribute_optional": "Miljöattribut (valfritt)", "nfc_tag_id": "NFC-tagg-ID", "nfc_linked": "NFC-tagg länkad", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/tr.json b/custom_components/maintenance_supporter/frontend-src/locales/tr.json index c69d10ef..81c1b809 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/tr.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Yenile", "environmental_entity_optional": "Çevresel sensör (isteğe bağlı)", "environmental_entity_helper": "örn. sensor.outdoor_temperature — aralığı çevresel koşullara göre ayarlar", + "adaptive_prediction_enabled": "Sensör tabanlı tahminleri etkinleştir", + "adaptive_seasonal_enabled": "Mevsimsel farkındalığı etkinleştir", + "adaptive_max_interval": "Maksimum aralık (gün)", + "adaptive_min_interval": "Minimum aralık (gün)", + "adaptive_ewa_alpha": "Öğrenme hızı (alfa)", + "adaptive_enabled": "Uyarlanabilir zamanlamayı etkinleştir", + "adaptive_section_title": "Uyarlanabilir Zamanlama", "environmental_attribute_optional": "Çevresel öznitelik (isteğe bağlı)", "nfc_tag_id": "NFC Etiket Kimliği", "nfc_linked": "NFC etiketi bağlandı", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/uk.json b/custom_components/maintenance_supporter/frontend-src/locales/uk.json index 85afa69c..bac0d713 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/uk.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Оновити", "environmental_entity_optional": "Датчик навколишнього середовища (необов'язково)", "environmental_entity_helper": "напр. sensor.outdoor_temperature — коригує інтервал відповідно до умов навколишнього середовища", + "adaptive_prediction_enabled": "Увімкнути прогнози за сенсорами", + "adaptive_seasonal_enabled": "Увімкнути сезонну корекцію", + "adaptive_max_interval": "Максимальний інтервал (дні)", + "adaptive_min_interval": "Мінімальний інтервал (дні)", + "adaptive_ewa_alpha": "Швидкість навчання (alpha)", + "adaptive_enabled": "Увімкнути адаптивне планування", + "adaptive_section_title": "Адаптивне планування", "environmental_attribute_optional": "Атрибут середовища (необов'язково)", "nfc_tag_id": "ID NFC-тега", "nfc_linked": "NFC-тег прив'язано", diff --git a/custom_components/maintenance_supporter/frontend-src/locales/zh.json b/custom_components/maintenance_supporter/frontend-src/locales/zh.json index dd8a0462..99c9db7e 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/zh.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "刷新", "environmental_entity_optional": "环境传感器 (可选)", "environmental_entity_helper": "例如:sensor.outdoor_temperature — 根据环境条件自动调整间隔", + "adaptive_prediction_enabled": "启用基于传感器的预测", + "adaptive_seasonal_enabled": "启用季节性感知", + "adaptive_max_interval": "最大间隔 (天)", + "adaptive_min_interval": "最小间隔 (天)", + "adaptive_ewa_alpha": "学习率 (alpha)", + "adaptive_enabled": "启用自适应计划", + "adaptive_section_title": "自适应计划", "environmental_attribute_optional": "环境属性 (可选)", "nfc_tag_id": "NFC 标签 ID", "nfc_linked": "NFC 标签已链接", diff --git a/custom_components/maintenance_supporter/frontend/locales/cs.json b/custom_components/maintenance_supporter/frontend/locales/cs.json index c7fa18c6..9f79d1b1 100644 --- a/custom_components/maintenance_supporter/frontend/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend/locales/cs.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Obnovit", "environmental_entity_optional": "Senzor prostředí (volitelný)", "environmental_entity_helper": "např. sensor.outdoor_temperature — upravuje interval podle podmínek prostředí", + "adaptive_prediction_enabled": "Povolit predikce řízené senzory", + "adaptive_seasonal_enabled": "Povolit sezónní povědomí", + "adaptive_max_interval": "Maximální interval (dny)", + "adaptive_min_interval": "Minimální interval (dny)", + "adaptive_ewa_alpha": "Rychlost učení (alpha)", + "adaptive_enabled": "Povolit adaptivní plánování", + "adaptive_section_title": "Adaptivní plánování", "environmental_attribute_optional": "Atribut prostředí (volitelný)", "nfc_tag_id": "ID NFC tagu", "nfc_linked": "NFC tag propojen", diff --git a/custom_components/maintenance_supporter/frontend/locales/da.json b/custom_components/maintenance_supporter/frontend/locales/da.json index 74624168..b3950ce1 100644 --- a/custom_components/maintenance_supporter/frontend/locales/da.json +++ b/custom_components/maintenance_supporter/frontend/locales/da.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Opdater", "environmental_entity_optional": "Miljøsensor (valgfrit)", "environmental_entity_helper": "f.eks. sensor.outdoor_temperature — justerer intervallet baseret på miljøforhold", + "adaptive_prediction_enabled": "Aktivér sensordrevne forudsigelser", + "adaptive_seasonal_enabled": "Aktivér sæsonbevidsthed", + "adaptive_max_interval": "Maksimumsinterval (dage)", + "adaptive_min_interval": "Minimumsinterval (dage)", + "adaptive_ewa_alpha": "Læringsrate (alfa)", + "adaptive_enabled": "Aktivér adaptiv planlægning", + "adaptive_section_title": "Adaptiv planlægning", "environmental_attribute_optional": "Miljøattribut (valgfrit)", "nfc_tag_id": "NFC-tag-ID", "nfc_linked": "NFC-tag tilknyttet", diff --git a/custom_components/maintenance_supporter/frontend/locales/de.json b/custom_components/maintenance_supporter/frontend/locales/de.json index aaccc572..7db29172 100644 --- a/custom_components/maintenance_supporter/frontend/locales/de.json +++ b/custom_components/maintenance_supporter/frontend/locales/de.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Aktualisieren", "environmental_entity_optional": "Umgebungs-Sensor (optional)", "environmental_entity_helper": "z.B. sensor.aussentemperatur — passt das Intervall an Umgebungswerte an", + "adaptive_prediction_enabled": "Sensorbasierte Vorhersagen aktivieren", + "adaptive_seasonal_enabled": "Saisonale Anpassung aktivieren", + "adaptive_max_interval": "Maximales Intervall (Tage)", + "adaptive_min_interval": "Minimales Intervall (Tage)", + "adaptive_ewa_alpha": "Lernrate (Alpha)", + "adaptive_enabled": "Adaptive Planung aktivieren", + "adaptive_section_title": "Adaptive Planung", "environmental_attribute_optional": "Umgebungs-Attribut (optional)", "nfc_tag_id": "NFC-Tag-ID", "nfc_linked": "NFC-Tag verknüpft", diff --git a/custom_components/maintenance_supporter/frontend/locales/en.json b/custom_components/maintenance_supporter/frontend/locales/en.json index a2f0ce44..b9ccbe9f 100644 --- a/custom_components/maintenance_supporter/frontend/locales/en.json +++ b/custom_components/maintenance_supporter/frontend/locales/en.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Refresh", "environmental_entity_optional": "Environmental sensor (optional)", "environmental_entity_helper": "e.g. sensor.outdoor_temperature — 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", diff --git a/custom_components/maintenance_supporter/frontend/locales/es.json b/custom_components/maintenance_supporter/frontend/locales/es.json index cf5b3eaa..50161744 100644 --- a/custom_components/maintenance_supporter/frontend/locales/es.json +++ b/custom_components/maintenance_supporter/frontend/locales/es.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Actualizar", "environmental_entity_optional": "Sensor ambiental (opcional)", "environmental_entity_helper": "p.ej. sensor.temperatura_exterior — ajusta el intervalo según las condiciones ambientales", + "adaptive_prediction_enabled": "Activar predicciones de sensor", + "adaptive_seasonal_enabled": "Activar conciencia estacional", + "adaptive_max_interval": "Intervalo máximo (días)", + "adaptive_min_interval": "Intervalo mínimo (días)", + "adaptive_ewa_alpha": "Tasa de aprendizaje (alpha)", + "adaptive_enabled": "Activar programación adaptativa", + "adaptive_section_title": "Programación adaptativa", "environmental_attribute_optional": "Atributo ambiental (opcional)", "nfc_tag_id": "ID de etiqueta NFC", "nfc_linked": "Etiqueta NFC vinculada", diff --git a/custom_components/maintenance_supporter/frontend/locales/fi.json b/custom_components/maintenance_supporter/frontend/locales/fi.json index b638d02b..df30c7aa 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend/locales/fi.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Päivitä", "environmental_entity_optional": "Ympäristöanturi (valinnainen)", "environmental_entity_helper": "esim. sensor.outdoor_temperature — säätää väliä ympäristöolosuhteiden mukaan", + "adaptive_prediction_enabled": "Ota anturiperusteiset ennusteet käyttöön", + "adaptive_seasonal_enabled": "Ota kausitietoisuus käyttöön", + "adaptive_max_interval": "Enimmäisaikaväli (päivää)", + "adaptive_min_interval": "Vähimmäisaikaväli (päivää)", + "adaptive_ewa_alpha": "Oppimisnopeus (alfa)", + "adaptive_enabled": "Ota mukautuva aikataulutus käyttöön", + "adaptive_section_title": "Mukautuva aikataulutus", "environmental_attribute_optional": "Ympäristöattribuutti (valinnainen)", "nfc_tag_id": "NFC-tunnisteen tunnus", "nfc_linked": "NFC-tunniste linkitetty", diff --git a/custom_components/maintenance_supporter/frontend/locales/fr.json b/custom_components/maintenance_supporter/frontend/locales/fr.json index 43ea0ce6..06a43a8a 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend/locales/fr.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Actualiser", "environmental_entity_optional": "Capteur d'environnement (optionnel)", "environmental_entity_helper": "ex. sensor.temperature_exterieure — ajuste l'intervalle selon les conditions environnementales", + "adaptive_prediction_enabled": "Activer les prédictions capteur", + "adaptive_seasonal_enabled": "Activer la sensibilité saisonnière", + "adaptive_max_interval": "Intervalle maximum (jours)", + "adaptive_min_interval": "Intervalle minimum (jours)", + "adaptive_ewa_alpha": "Taux d'apprentissage (alpha)", + "adaptive_enabled": "Activer la planification adaptative", + "adaptive_section_title": "Planification adaptative", "environmental_attribute_optional": "Attribut d'environnement (optionnel)", "nfc_tag_id": "ID tag NFC", "nfc_linked": "Tag NFC lié", diff --git a/custom_components/maintenance_supporter/frontend/locales/hi.json b/custom_components/maintenance_supporter/frontend/locales/hi.json index fd28005d..2b63f6f0 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend/locales/hi.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "रिफ़्रेश करें", "environmental_entity_optional": "पर्यावरण सेंसर (वैकल्पिक)", "environmental_entity_helper": "उदा. sensor.outdoor_temperature — पर्यावरणीय स्थितियों के आधार पर अंतराल समायोजित करता है", + "adaptive_prediction_enabled": "सेंसर-संचालित पूर्वानुमान सक्षम करें", + "adaptive_seasonal_enabled": "मौसमी जागरूकता सक्षम करें", + "adaptive_max_interval": "अधिकतम अंतराल (दिन)", + "adaptive_min_interval": "न्यूनतम अंतराल (दिन)", + "adaptive_ewa_alpha": "अधिगम दर (अल्फा)", + "adaptive_enabled": "अनुकूली अनुसूचन सक्षम करें", + "adaptive_section_title": "अनुकूली अनुसूचन", "environmental_attribute_optional": "पर्यावरण विशेषता (वैकल्पिक)", "nfc_tag_id": "NFC टैग ID", "nfc_linked": "NFC टैग लिंक किया गया", diff --git a/custom_components/maintenance_supporter/frontend/locales/hu.json b/custom_components/maintenance_supporter/frontend/locales/hu.json index 283f422f..8e6a1758 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend/locales/hu.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Frissítés", "environmental_entity_optional": "Környezeti érzékelő (opcionális)", "environmental_entity_helper": "pl. sensor.outdoor_temperature — a környezeti feltételek alapján igazítja az intervallumot", + "adaptive_prediction_enabled": "Érzékelővezérelt előrejelzések engedélyezése", + "adaptive_seasonal_enabled": "Szezonális igazodás engedélyezése", + "adaptive_max_interval": "Maximális intervallum (nap)", + "adaptive_min_interval": "Minimális intervallum (nap)", + "adaptive_ewa_alpha": "Tanulási ráta (alfa)", + "adaptive_enabled": "Adaptív ütemezés engedélyezése", + "adaptive_section_title": "Adaptív ütemezés", "environmental_attribute_optional": "Környezeti attribútum (opcionális)", "nfc_tag_id": "NFC címke azonosító", "nfc_linked": "NFC címke hozzárendelve", diff --git a/custom_components/maintenance_supporter/frontend/locales/it.json b/custom_components/maintenance_supporter/frontend/locales/it.json index 860b1a05..b2605821 100644 --- a/custom_components/maintenance_supporter/frontend/locales/it.json +++ b/custom_components/maintenance_supporter/frontend/locales/it.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Aggiorna", "environmental_entity_optional": "Sensore ambientale (opzionale)", "environmental_entity_helper": "es. sensor.temperatura_esterna — regola l'intervallo in base alle condizioni ambientali", + "adaptive_prediction_enabled": "Abilita previsioni sensore", + "adaptive_seasonal_enabled": "Abilita consapevolezza stagionale", + "adaptive_max_interval": "Intervallo massimo (giorni)", + "adaptive_min_interval": "Intervallo minimo (giorni)", + "adaptive_ewa_alpha": "Tasso di apprendimento (alpha)", + "adaptive_enabled": "Abilita pianificazione adattiva", + "adaptive_section_title": "Pianificazione adattiva", "environmental_attribute_optional": "Attributo ambientale (opzionale)", "nfc_tag_id": "ID tag NFC", "nfc_linked": "Tag NFC collegato", diff --git a/custom_components/maintenance_supporter/frontend/locales/ja.json b/custom_components/maintenance_supporter/frontend/locales/ja.json index 7c2b9368..546f9df7 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend/locales/ja.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "更新", "environmental_entity_optional": "環境センサー(任意)", "environmental_entity_helper": "例: sensor.outdoor_temperature — 環境条件に応じて間隔を調整します", + "adaptive_prediction_enabled": "センサー駆動の予測を有効にする", + "adaptive_seasonal_enabled": "季節認識を有効にする", + "adaptive_max_interval": "最大間隔 (日)", + "adaptive_min_interval": "最小間隔 (日)", + "adaptive_ewa_alpha": "学習率 (アルファ)", + "adaptive_enabled": "適応スケジューリングを有効にする", + "adaptive_section_title": "適応スケジューリング", "environmental_attribute_optional": "環境属性(任意)", "nfc_tag_id": "NFCタグID", "nfc_linked": "NFCタグ連携済み", diff --git a/custom_components/maintenance_supporter/frontend/locales/ko.json b/custom_components/maintenance_supporter/frontend/locales/ko.json index 5093e8df..3e11fc06 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend/locales/ko.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "새로 고침", "environmental_entity_optional": "환경 센서 (선택)", "environmental_entity_helper": "예: sensor.outdoor_temperature — 환경 조건에 따라 주기를 조정합니다", + "adaptive_prediction_enabled": "센서 기반 예측 사용", + "adaptive_seasonal_enabled": "계절 인식 사용", + "adaptive_max_interval": "최대 주기(일)", + "adaptive_min_interval": "최소 주기(일)", + "adaptive_ewa_alpha": "학습률(alpha)", + "adaptive_enabled": "적응형 일정 사용", + "adaptive_section_title": "적응형 일정", "environmental_attribute_optional": "환경 속성 (선택)", "nfc_tag_id": "NFC 태그 ID", "nfc_linked": "NFC 태그 연결됨", diff --git a/custom_components/maintenance_supporter/frontend/locales/nb.json b/custom_components/maintenance_supporter/frontend/locales/nb.json index a4ff61fc..79bc0cd6 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend/locales/nb.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Oppdater", "environmental_entity_optional": "Miljøsensor (valgfritt)", "environmental_entity_helper": "f.eks. sensor.outdoor_temperature — justerer intervallet basert på miljøforhold", + "adaptive_prediction_enabled": "Aktiver sensordrevne prognoser", + "adaptive_seasonal_enabled": "Aktiver sesongbevissthet", + "adaptive_max_interval": "Største intervall (dager)", + "adaptive_min_interval": "Minste intervall (dager)", + "adaptive_ewa_alpha": "Læringsrate (alfa)", + "adaptive_enabled": "Aktiver adaptiv planlegging", + "adaptive_section_title": "Adaptiv planlegging", "environmental_attribute_optional": "Miljøattributt (valgfritt)", "nfc_tag_id": "NFC-brikke-ID", "nfc_linked": "NFC-brikke koblet", diff --git a/custom_components/maintenance_supporter/frontend/locales/nl.json b/custom_components/maintenance_supporter/frontend/locales/nl.json index 5c9d1a06..dbff7b52 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend/locales/nl.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Vernieuwen", "environmental_entity_optional": "Omgevingssensor (optioneel)", "environmental_entity_helper": "bv. sensor.buitentemperatuur — past het interval aan op basis van omgevingswaarden", + "adaptive_prediction_enabled": "Sensorgestuurde voorspellingen inschakelen", + "adaptive_seasonal_enabled": "Seizoensbewustzijn inschakelen", + "adaptive_max_interval": "Maximaal interval (dagen)", + "adaptive_min_interval": "Minimaal interval (dagen)", + "adaptive_ewa_alpha": "Leersnelheid (alpha)", + "adaptive_enabled": "Adaptieve planning inschakelen", + "adaptive_section_title": "Adaptieve planning", "environmental_attribute_optional": "Omgevingsattribuut (optioneel)", "nfc_tag_id": "NFC-tag-ID", "nfc_linked": "NFC-tag gekoppeld", diff --git a/custom_components/maintenance_supporter/frontend/locales/pl.json b/custom_components/maintenance_supporter/frontend/locales/pl.json index 5fdedeb2..0b8c8dd8 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend/locales/pl.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Odśwież", "environmental_entity_optional": "Czujnik środowiskowy (opcjonalne)", "environmental_entity_helper": "np. sensor.outdoor_temperature — dostosowuje interwał na podstawie warunków środowiskowych", + "adaptive_prediction_enabled": "Włącz predykcje sterowane czujnikami", + "adaptive_seasonal_enabled": "Włącz świadomość sezonową", + "adaptive_max_interval": "Maksymalny interwał (dni)", + "adaptive_min_interval": "Minimalny interwał (dni)", + "adaptive_ewa_alpha": "Tempo uczenia (alpha)", + "adaptive_enabled": "Włącz adaptacyjne planowanie", + "adaptive_section_title": "Adaptacyjne planowanie", "environmental_attribute_optional": "Atrybut środowiskowy (opcjonalne)", "nfc_tag_id": "ID tagu NFC", "nfc_linked": "Tag NFC powiązany", diff --git a/custom_components/maintenance_supporter/frontend/locales/pt-br.json b/custom_components/maintenance_supporter/frontend/locales/pt-br.json index 5ad1d7f2..a6b3f107 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt-br.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Atualizar", "environmental_entity_optional": "Sensor ambiental (opcional)", "environmental_entity_helper": "ex.: sensor.outdoor_temperature — ajusta o intervalo com base nas condições ambientais", + "adaptive_prediction_enabled": "Ativar previsões baseadas em sensor", + "adaptive_seasonal_enabled": "Ativar sazonalidade", + "adaptive_max_interval": "Intervalo máximo (dias)", + "adaptive_min_interval": "Intervalo mínimo (dias)", + "adaptive_ewa_alpha": "Taxa de aprendizado (alpha)", + "adaptive_enabled": "Ativar agendamento adaptativo", + "adaptive_section_title": "Agendamento adaptativo", "environmental_attribute_optional": "Atributo ambiental (opcional)", "nfc_tag_id": "ID da tag NFC", "nfc_linked": "Tag NFC vinculada", diff --git a/custom_components/maintenance_supporter/frontend/locales/pt.json b/custom_components/maintenance_supporter/frontend/locales/pt.json index 7cfd6638..7b3341c9 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Atualizar", "environmental_entity_optional": "Sensor ambiental (opcional)", "environmental_entity_helper": "ex. sensor.temperatura_exterior — ajusta o intervalo segundo as condições ambientais", + "adaptive_prediction_enabled": "Ativar previsões baseadas em sensores", + "adaptive_seasonal_enabled": "Ativar consciência sazonal", + "adaptive_max_interval": "Intervalo máximo (dias)", + "adaptive_min_interval": "Intervalo mínimo (dias)", + "adaptive_ewa_alpha": "Taxa de aprendizagem (alfa)", + "adaptive_enabled": "Ativar agendamento adaptativo", + "adaptive_section_title": "Agendamento Adaptativo", "environmental_attribute_optional": "Atributo ambiental (opcional)", "nfc_tag_id": "ID da etiqueta NFC", "nfc_linked": "Etiqueta NFC associada", diff --git a/custom_components/maintenance_supporter/frontend/locales/ru.json b/custom_components/maintenance_supporter/frontend/locales/ru.json index ef11715b..195f93e1 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend/locales/ru.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Обновить", "environmental_entity_optional": "Датчик окружающей среды (опционально)", "environmental_entity_helper": "напр. sensor.outdoor_temperature — корректирует интервал в зависимости от условий", + "adaptive_prediction_enabled": "Включить прогнозы на основе датчиков", + "adaptive_seasonal_enabled": "Учитывать сезонность", + "adaptive_max_interval": "Максимальный интервал (дни)", + "adaptive_min_interval": "Минимальный интервал (дни)", + "adaptive_ewa_alpha": "Скорость обучения (альфа)", + "adaptive_enabled": "Включить адаптивное планирование", + "adaptive_section_title": "Адаптивное планирование", "environmental_attribute_optional": "Атрибут среды (опционально)", "nfc_tag_id": "ID NFC-метки", "nfc_linked": "NFC-метка привязана", diff --git a/custom_components/maintenance_supporter/frontend/locales/sv.json b/custom_components/maintenance_supporter/frontend/locales/sv.json index 590bdc1e..7699d221 100644 --- a/custom_components/maintenance_supporter/frontend/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend/locales/sv.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Uppdatera", "environmental_entity_optional": "Miljösensor (valfritt)", "environmental_entity_helper": "t.ex. sensor.outdoor_temperature — justerar intervallet baserat på miljöförhållanden", + "adaptive_prediction_enabled": "Aktivera sensorstyrda prediktioner", + "adaptive_seasonal_enabled": "Aktivera säsongsmedvetenhet", + "adaptive_max_interval": "Största intervall (dagar)", + "adaptive_min_interval": "Minsta intervall (dagar)", + "adaptive_ewa_alpha": "Inlärningshastighet (alpha)", + "adaptive_enabled": "Aktivera adaptiv schemaläggning", + "adaptive_section_title": "Adaptiv schemaläggning", "environmental_attribute_optional": "Miljöattribut (valfritt)", "nfc_tag_id": "NFC-tagg-ID", "nfc_linked": "NFC-tagg länkad", diff --git a/custom_components/maintenance_supporter/frontend/locales/tr.json b/custom_components/maintenance_supporter/frontend/locales/tr.json index c69d10ef..81c1b809 100644 --- a/custom_components/maintenance_supporter/frontend/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend/locales/tr.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "Yenile", "environmental_entity_optional": "Çevresel sensör (isteğe bağlı)", "environmental_entity_helper": "örn. sensor.outdoor_temperature — aralığı çevresel koşullara göre ayarlar", + "adaptive_prediction_enabled": "Sensör tabanlı tahminleri etkinleştir", + "adaptive_seasonal_enabled": "Mevsimsel farkındalığı etkinleştir", + "adaptive_max_interval": "Maksimum aralık (gün)", + "adaptive_min_interval": "Minimum aralık (gün)", + "adaptive_ewa_alpha": "Öğrenme hızı (alfa)", + "adaptive_enabled": "Uyarlanabilir zamanlamayı etkinleştir", + "adaptive_section_title": "Uyarlanabilir Zamanlama", "environmental_attribute_optional": "Çevresel öznitelik (isteğe bağlı)", "nfc_tag_id": "NFC Etiket Kimliği", "nfc_linked": "NFC etiketi bağlandı", diff --git a/custom_components/maintenance_supporter/frontend/locales/uk.json b/custom_components/maintenance_supporter/frontend/locales/uk.json index 85afa69c..bac0d713 100644 --- a/custom_components/maintenance_supporter/frontend/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend/locales/uk.json @@ -205,6 +205,13 @@ "nfc_tags_refresh": "Оновити", "environmental_entity_optional": "Датчик навколишнього середовища (необов'язково)", "environmental_entity_helper": "напр. sensor.outdoor_temperature — коригує інтервал відповідно до умов навколишнього середовища", + "adaptive_prediction_enabled": "Увімкнути прогнози за сенсорами", + "adaptive_seasonal_enabled": "Увімкнути сезонну корекцію", + "adaptive_max_interval": "Максимальний інтервал (дні)", + "adaptive_min_interval": "Мінімальний інтервал (дні)", + "adaptive_ewa_alpha": "Швидкість навчання (alpha)", + "adaptive_enabled": "Увімкнути адаптивне планування", + "adaptive_section_title": "Адаптивне планування", "environmental_attribute_optional": "Атрибут середовища (необов'язково)", "nfc_tag_id": "ID NFC-тега", "nfc_linked": "NFC-тег прив'язано", diff --git a/custom_components/maintenance_supporter/frontend/locales/zh.json b/custom_components/maintenance_supporter/frontend/locales/zh.json index dd8a0462..99c9db7e 100644 --- a/custom_components/maintenance_supporter/frontend/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend/locales/zh.json @@ -206,6 +206,13 @@ "nfc_tags_refresh": "刷新", "environmental_entity_optional": "环境传感器 (可选)", "environmental_entity_helper": "例如:sensor.outdoor_temperature — 根据环境条件自动调整间隔", + "adaptive_prediction_enabled": "启用基于传感器的预测", + "adaptive_seasonal_enabled": "启用季节性感知", + "adaptive_max_interval": "最大间隔 (天)", + "adaptive_min_interval": "最小间隔 (天)", + "adaptive_ewa_alpha": "学习率 (alpha)", + "adaptive_enabled": "启用自适应计划", + "adaptive_section_title": "自适应计划", "environmental_attribute_optional": "环境属性 (可选)", "nfc_tag_id": "NFC 标签 ID", "nfc_linked": "NFC 标签已链接", diff --git a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js index 7c216d81..0fe70c34 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js @@ -1,7 +1,7 @@ -/*! maintenance_supporter frontend 2.55.0 */ -var it=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var x=(a,e,t,o)=>{for(var r=o>1?void 0:o?lt(e,t):e,n=a.length-1,s;n>=0;n--)(s=a[n])&&(r=(o?s(e,t,r):s(r))||r);return o&&r&&it(e,t,r),r};var ee=globalThis,te=ee.ShadowRoot&&(ee.ShadyCSS===void 0||ee.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,de=Symbol(),Se=new WeakMap,q=class{constructor(e,t,o){if(this._$cssResult$=!0,o!==de)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o,t=this.t;if(te&&e===void 0){let o=t!==void 0&&t.length===1;o&&(e=Se.get(t)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),o&&Se.set(t,e))}return e}toString(){return this.cssText}},$e=a=>new q(typeof a=="string"?a:a+"",void 0,de),k=(a,...e)=>{let t=a.length===1?a[0]:e.reduce((o,r,n)=>o+(s=>{if(s._$cssResult$===!0)return s.cssText;if(typeof s=="number")return s;throw Error("Value passed to 'css' function must be a 'css' function result: "+s+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+a[n+1],a[0]);return new q(t,a,de)},Ae=(a,e)=>{if(te)a.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of e){let o=document.createElement("style"),r=ee.litNonce;r!==void 0&&o.setAttribute("nonce",r),o.textContent=t.cssText,a.appendChild(o)}},pe=te?a=>a:a=>a instanceof CSSStyleSheet?(e=>{let t="";for(let o of e.cssRules)t+=o.cssText;return $e(t)})(a):a;var{is:ct,defineProperty:dt,getOwnPropertyDescriptor:pt,getOwnPropertyNames:ut,getOwnPropertySymbols:_t,getPrototypeOf:ht}=Object,oe=globalThis,je=oe.trustedTypes,gt=je?je.emptyScript:"",ft=oe.reactiveElementPolyfillSupport,F=(a,e)=>a,B={toAttribute(a,e){switch(e){case Boolean:a=a?gt:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,e){let t=a;switch(e){case Boolean:t=a!==null;break;case Number:t=a===null?null:Number(a);break;case Object:case Array:try{t=JSON.parse(a)}catch{t=null}}return t}},re=(a,e)=>!ct(a,e),Ce={attribute:!0,type:String,converter:B,reflect:!1,useDefault:!1,hasChanged:re};Symbol.metadata??=Symbol("metadata"),oe.litPropertyMetadata??=new WeakMap;var S=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=Ce){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){let o=Symbol(),r=this.getPropertyDescriptor(e,o,t);r!==void 0&&dt(this.prototype,e,r)}}static getPropertyDescriptor(e,t,o){let{get:r,set:n}=pt(this.prototype,e)??{get(){return this[t]},set(s){this[t]=s}};return{get:r,set(s){let l=r?.call(this);n?.call(this,s),this.requestUpdate(e,l,o)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??Ce}static _$Ei(){if(this.hasOwnProperty(F("elementProperties")))return;let e=ht(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(F("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(F("properties"))){let t=this.properties,o=[...ut(t),..._t(t)];for(let r of o)this.createProperty(r,t[r])}let e=this[Symbol.metadata];if(e!==null){let t=litPropertyMetadata.get(e);if(t!==void 0)for(let[o,r]of t)this.elementProperties.set(o,r)}this._$Eh=new Map;for(let[t,o]of this.elementProperties){let r=this._$Eu(t,o);r!==void 0&&this._$Eh.set(r,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let t=[];if(Array.isArray(e)){let o=new Set(e.flat(1/0).reverse());for(let r of o)t.unshift(pe(r))}else e!==void 0&&t.push(pe(e));return t}static _$Eu(e,t){let o=t.attribute;return o===!1?void 0:typeof o=="string"?o:typeof e=="string"?e.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(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,t=this.constructor.elementProperties;for(let o of t.keys())this.hasOwnProperty(o)&&(e.set(o,this[o]),delete this[o]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Ae(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,o){this._$AK(e,o)}_$ET(e,t){let o=this.constructor.elementProperties.get(e),r=this.constructor._$Eu(e,o);if(r!==void 0&&o.reflect===!0){let n=(o.converter?.toAttribute!==void 0?o.converter:B).toAttribute(t,o.type);this._$Em=e,n==null?this.removeAttribute(r):this.setAttribute(r,n),this._$Em=null}}_$AK(e,t){let o=this.constructor,r=o._$Eh.get(e);if(r!==void 0&&this._$Em!==r){let n=o.getPropertyOptions(r),s=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:B;this._$Em=r;let l=s.fromAttribute(t,n.type);this[r]=l??this._$Ej?.get(r)??l,this._$Em=null}}requestUpdate(e,t,o,r=!1,n){if(e!==void 0){let s=this.constructor;if(r===!1&&(n=this[e]),o??=s.getPropertyOptions(e),!((o.hasChanged??re)(n,t)||o.useDefault&&o.reflect&&n===this._$Ej?.get(e)&&!this.hasAttribute(s._$Eu(e,o))))return;this.C(e,t,o)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(e,t,{useDefault:o,reflect:r,wrapped:n},s){o&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,s??t??this[e]),n!==!0||s!==void 0)||(this._$AL.has(e)||(this.hasUpdated||o||(t=void 0),this._$AL.set(e,t)),r===!0&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}let e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[r,n]of this._$Ep)this[r]=n;this._$Ep=void 0}let o=this.constructor.elementProperties;if(o.size>0)for(let[r,n]of o){let{wrapped:s}=n,l=this[r];s!==!0||this._$AL.has(r)||l===void 0||this.C(r,void 0,n,l)}}let e=!1,t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(o=>o.hostUpdate?.()),this.update(t)):this._$EM()}catch(o){throw e=!1,this._$EM(),o}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(e){}firstUpdated(e){}};S.elementStyles=[],S.shadowRootOptions={mode:"open"},S[F("elementProperties")]=new Map,S[F("finalized")]=new Map,ft?.({ReactiveElement:S}),(oe.reactiveElementVersions??=[]).push("2.1.2");var be=globalThis,Ee=a=>a,ae=be.trustedTypes,Te=ae?ae.createPolicy("lit-html",{createHTML:a=>a}):void 0,Oe="$lit$",A=`lit$${Math.random().toFixed(9).slice(2)}$`,Le="?"+A,mt=`<${Le}>`,T=document,Y=()=>T.createComment(""),G=a=>a===null||typeof a!="object"&&typeof a!="function",ye=Array.isArray,bt=a=>ye(a)||typeof a?.[Symbol.iterator]=="function",ue=`[ -\f\r]`,W=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,De=/-->/g,Ne=/>/g,C=RegExp(`>|${ue}(?:([^\\s"'>=/]+)(${ue}*=${ue}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),Re=/'/g,Pe=/"/g,Me=/^(?:script|style|textarea|title)$/i,ve=a=>(e,...t)=>({_$litType$:a,strings:e,values:t}),m=ve(1),It=ve(2),qt=ve(3),D=Symbol.for("lit-noChange"),g=Symbol.for("lit-nothing"),ze=new WeakMap,E=T.createTreeWalker(T,129);function Ue(a,e){if(!ye(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return Te!==void 0?Te.createHTML(e):e}var yt=(a,e)=>{let t=a.length-1,o=[],r,n=e===2?"":e===3?"":"",s=W;for(let l=0;l"?(s=r??W,c=-1):u[1]===void 0?c=-2:(c=s.lastIndex-u[2].length,p=u[1],s=u[3]===void 0?C:u[3]==='"'?Pe:Re):s===Pe||s===Re?s=C:s===De||s===Ne?s=W:(s=C,r=void 0);let h=s===C&&a[l+1].startsWith("/>")?" ":"";n+=s===W?d+mt:c>=0?(o.push(p),d.slice(0,c)+Oe+d.slice(c)+A+h):d+A+(c===-2?l:h)}return[Ue(a,n+(a[t]||"")+(e===2?"":e===3?"":"")),o]},V=class a{constructor({strings:e,_$litType$:t},o){let r;this.parts=[];let n=0,s=0,l=e.length-1,d=this.parts,[p,u]=yt(e,t);if(this.el=a.createElement(p,o),E.currentNode=this.el.content,t===2||t===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(r=E.nextNode())!==null&&d.length0){r.textContent=ae?ae.emptyScript:"";for(let h=0;h<_;h++)r.append(c[h],Y()),E.nextNode(),d.push({type:2,index:++n});r.append(c[_],Y())}}}else if(r.nodeType===8)if(r.data===Le)d.push({type:2,index:n});else{let c=-1;for(;(c=r.data.indexOf(A,c+1))!==-1;)d.push({type:7,index:n}),c+=A.length-1}n++}}static createElement(e,t){let o=T.createElement("template");return o.innerHTML=e,o}};function z(a,e,t=a,o){if(e===D)return e;let r=o!==void 0?t._$Co?.[o]:t._$Cl,n=G(e)?void 0:e._$litDirective$;return r?.constructor!==n&&(r?._$AO?.(!1),n===void 0?r=void 0:(r=new n(a),r._$AT(a,t,o)),o!==void 0?(t._$Co??=[])[o]=r:t._$Cl=r),r!==void 0&&(e=z(a,r._$AS(a,e.values),r,o)),e}var _e=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:o}=this._$AD,r=(e?.creationScope??T).importNode(t,!0);E.currentNode=r;let n=E.nextNode(),s=0,l=0,d=o[0];for(;d!==void 0;){if(s===d.index){let p;d.type===2?p=new K(n,n.nextSibling,this,e):d.type===1?p=new d.ctor(n,d.name,d.strings,this,e):d.type===6&&(p=new me(n,this,e)),this._$AV.push(p),d=o[++l]}s!==d?.index&&(n=E.nextNode(),s++)}return E.currentNode=T,r}p(e){let t=0;for(let o of this._$AV)o!==void 0&&(o.strings!==void 0?(o._$AI(e,o,t),t+=o.strings.length-2):o._$AI(e[t])),t++}},K=class a{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,o,r){this.type=2,this._$AH=g,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=o,this.options=r,this._$Cv=r?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=z(this,e,t),G(e)?e===g||e==null||e===""?(this._$AH!==g&&this._$AR(),this._$AH=g):e!==this._$AH&&e!==D&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):bt(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==g&&G(this._$AH)?this._$AA.nextSibling.data=e:this.T(T.createTextNode(e)),this._$AH=e}$(e){let{values:t,_$litType$:o}=e,r=typeof o=="number"?this._$AC(e):(o.el===void 0&&(o.el=V.createElement(Ue(o.h,o.h[0]),this.options)),o);if(this._$AH?._$AD===r)this._$AH.p(t);else{let n=new _e(r,this),s=n.u(this.options);n.p(t),this.T(s),this._$AH=n}}_$AC(e){let t=ze.get(e.strings);return t===void 0&&ze.set(e.strings,t=new V(e)),t}k(e){ye(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,o,r=0;for(let n of e)r===t.length?t.push(o=new a(this.O(Y()),this.O(Y()),this,this.options)):o=t[r],o._$AI(n),r++;r2||o[0]!==""||o[1]!==""?(this._$AH=Array(o.length-1).fill(new String),this.strings=o):this._$AH=g}_$AI(e,t=this,o,r){let n=this.strings,s=!1;if(n===void 0)e=z(this,e,t,0),s=!G(e)||e!==this._$AH&&e!==D,s&&(this._$AH=e);else{let l=e,d,p;for(e=n[0],d=0;d{let o=t?.renderBefore??e,r=o._$litPart$;if(r===void 0){let n=t?.renderBefore??null;o._$litPart$=r=new K(e.insertBefore(Y(),n),n,void 0,t??{})}return r._$AI(a),r};var xe=globalThis,$=class extends S{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=He(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return D}};$._$litElement$=!0,$.finalized=!0,xe.litElementHydrateSupport?.({LitElement:$});var xt=xe.litElementPolyfillSupport;xt?.({LitElement:$});(xe.litElementVersions??=[]).push("4.2.2");var wt={attribute:!0,type:String,converter:B,reflect:!1,hasChanged:re},kt=(a=wt,e,t)=>{let{kind:o,metadata:r}=t,n=globalThis.litPropertyMetadata.get(r);if(n===void 0&&globalThis.litPropertyMetadata.set(r,n=new Map),o==="setter"&&((a=Object.create(a)).wrapped=!0),n.set(t.name,a),o==="accessor"){let{name:s}=t;return{set(l){let d=e.get.call(this);e.set.call(this,l),this.requestUpdate(s,d,a,!0,l)},init(l){return l!==void 0&&this.C(s,void 0,a,l),l}}}if(o==="setter"){let{name:s}=t;return function(l){let d=this[s];e.call(this,l),this.requestUpdate(s,d,a,!0,l)}}throw Error("Unsupported decorator location: "+o)};function Q(a){return(e,t)=>typeof t=="object"?kt(a,e,t):((o,r,n)=>{let s=r.hasOwnProperty(n);return r.constructor.createProperty(n,o),s?Object.getOwnPropertyDescriptor(r,n):void 0})(a,e,t)}function w(a){return Q({...a,state:!0,attribute:!1})}var St={days:1,weeks:7,months:30.4368,years:365.25};function Ie(a,e){return!a||a<=0?0:a*(St[e||"days"]??1)}var qe=5;function J(a){let e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),o=String(a.getDate()).padStart(2,"0");return`${e}-${t}-${o}`}function $t(a,e){let t=[];for(let o=0;ot.cost).filter(t=>typeof t=="number");return e.length===0?null:e.reduce((t,o)=>t+o,0)/e.length}function jt(a){let{windowStart:e,windowEnd:t,task:o,entryId:r,objectName:n}=a,s=[],l=(c,_)=>({date:c,entry_id:r,task_id:o.id,task_name:o.name,object_name:n,status:_&&(o.status==="overdue"||o.status==="triggered")?"ok":o.status,days_until_due:_?null:o.days_until_due??null,projected:_,schedule_type:o.schedule_type,interval_days:o.interval_days??null,interval_unit:o.interval_unit??null,responsible_user_id:o.responsible_user_id??null,avg_cost:At(o.history),adaptive_enabled:!!o.adaptive_config?.enabled,prediction_confidence:o.threshold_prediction_confidence??null}),d=Math.max(1,Math.round(Ie(o.interval_days,o.interval_unit)));if(o.status==="overdue"||o.status==="triggered"){if(s.push(l(e,!1)),o.schedule_type==="time_based"&&o.interval_days&&o.interval_days>0){let c=se(e,d),_=1;for(;c<=t&&_=e&&u<=t)s.push(l(u,!1));else if(u>t)return s;if(o.schedule_type==="time_based"&&o.interval_days&&o.interval_days>0){let c=se(u,d),_=s.length;for(;c<=t&&_=e&&(s.push(l(c,!0)),_++),c=se(c,d)}return s}var Fe={overdue:0,triggered:1,due_soon:2,ok:3};function Be(a,e,t,o=null){let r=$t(e,t),n=r[0],s=r[r.length-1],l=[];for(let p of a){let u=p.object?.name||"",c=p.entry_id,_=p.tasks||[];for(let h of _){if(o&&h.responsible_user_id!==o||h.enabled===!1)continue;let b=jt({windowStart:n,windowEnd:s,task:h,entryId:c,objectName:u});l.push(...b)}}let d=new Map;for(let p of r)d.set(p,[]);for(let p of l){let u=d.get(p.date);u&&u.push(p)}for(let[,p]of d)p.sort((u,c)=>{let _=Fe[u.status]??99,h=Fe[c.status]??99;if(_!==h)return _-h;if(u.projected!==c.projected)return u.projected?1:-1;let b=u.object_name.localeCompare(c.object_name);return b!==0?b:u.task_name.localeCompare(c.task_name)});return r.map(p=>({date:p,events:d.get(p)??[]}))}var Ct={completed:"ok",reset:"ok",skipped:"due_soon",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"};function Et(a,e){let t=[];for(let o=e-1;o>=0;o--){let r=new Date(a);r.setDate(r.getDate()-o),r.setHours(0,0,0,0),t.push(J(r))}return t}function We(a,e,t,o=null){let r=Et(e,t),n=r[0],s=r[r.length-1],l=new Map;for(let p of r)l.set(p,[]);for(let p of a){let u=p.object?.name||"",c=p.entry_id,_=p.tasks||[];for(let h of _){if(o&&h.responsible_user_id!==o)continue;let b=h.history||[];for(let y of b){if(typeof y?.timestamp!="string")continue;let R=y.timestamp.slice(0,10);if(Rs)continue;let M=l.get(R);if(!M)continue;let U=y.type??"completed";M.push({date:R,entry_id:c,task_id:h.id,task_name:h.name,object_name:u,status:Ct[U]??"ok",days_until_due:null,projected:!1,schedule_type:h.schedule_type,interval_days:h.interval_days??null,responsible_user_id:h.responsible_user_id??null,avg_cost:typeof y.cost=="number"?y.cost:null,adaptive_enabled:!!h.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:y.timestamp,history_type:U,history_cost:typeof y.cost=="number"?y.cost:null,history_notes:typeof y.notes=="string"?y.notes:null,history_duration:typeof y.duration=="number"?y.duration:null})}}}let d={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,p]of l)p.sort((u,c)=>{let _=d[u.history_type??""]??99,h=d[c.history_type??""]??99;if(_!==h)return _-h;let b=u.object_name.localeCompare(c.object_name);return b!==0?b:u.task_name.localeCompare(c.task_name)});return r.map(p=>({date:p,events:l.get(p)??[]}))}var Ye=k` +/*! maintenance_supporter frontend 2.56.0 */ +var it=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var x=(a,e,t,o)=>{for(var r=o>1?void 0:o?lt(e,t):e,n=a.length-1,s;n>=0;n--)(s=a[n])&&(r=(o?s(e,t,r):s(r))||r);return o&&r&&it(e,t,r),r};var ee=globalThis,te=ee.ShadowRoot&&(ee.ShadyCSS===void 0||ee.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,de=Symbol(),Se=new WeakMap,q=class{constructor(e,t,o){if(this._$cssResult$=!0,o!==de)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o,t=this.t;if(te&&e===void 0){let o=t!==void 0&&t.length===1;o&&(e=Se.get(t)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),o&&Se.set(t,e))}return e}toString(){return this.cssText}},$e=a=>new q(typeof a=="string"?a:a+"",void 0,de),k=(a,...e)=>{let t=a.length===1?a[0]:e.reduce((o,r,n)=>o+(s=>{if(s._$cssResult$===!0)return s.cssText;if(typeof s=="number")return s;throw Error("Value passed to 'css' function must be a 'css' function result: "+s+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+a[n+1],a[0]);return new q(t,a,de)},Ae=(a,e)=>{if(te)a.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of e){let o=document.createElement("style"),r=ee.litNonce;r!==void 0&&o.setAttribute("nonce",r),o.textContent=t.cssText,a.appendChild(o)}},pe=te?a=>a:a=>a instanceof CSSStyleSheet?(e=>{let t="";for(let o of e.cssRules)t+=o.cssText;return $e(t)})(a):a;var{is:ct,defineProperty:dt,getOwnPropertyDescriptor:pt,getOwnPropertyNames:ut,getOwnPropertySymbols:_t,getPrototypeOf:ht}=Object,oe=globalThis,je=oe.trustedTypes,gt=je?je.emptyScript:"",ft=oe.reactiveElementPolyfillSupport,F=(a,e)=>a,B={toAttribute(a,e){switch(e){case Boolean:a=a?gt:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,e){let t=a;switch(e){case Boolean:t=a!==null;break;case Number:t=a===null?null:Number(a);break;case Object:case Array:try{t=JSON.parse(a)}catch{t=null}}return t}},re=(a,e)=>!ct(a,e),Ee={attribute:!0,type:String,converter:B,reflect:!1,useDefault:!1,hasChanged:re};Symbol.metadata??=Symbol("metadata"),oe.litPropertyMetadata??=new WeakMap;var S=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=Ee){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){let o=Symbol(),r=this.getPropertyDescriptor(e,o,t);r!==void 0&&dt(this.prototype,e,r)}}static getPropertyDescriptor(e,t,o){let{get:r,set:n}=pt(this.prototype,e)??{get(){return this[t]},set(s){this[t]=s}};return{get:r,set(s){let l=r?.call(this);n?.call(this,s),this.requestUpdate(e,l,o)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??Ee}static _$Ei(){if(this.hasOwnProperty(F("elementProperties")))return;let e=ht(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(F("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(F("properties"))){let t=this.properties,o=[...ut(t),..._t(t)];for(let r of o)this.createProperty(r,t[r])}let e=this[Symbol.metadata];if(e!==null){let t=litPropertyMetadata.get(e);if(t!==void 0)for(let[o,r]of t)this.elementProperties.set(o,r)}this._$Eh=new Map;for(let[t,o]of this.elementProperties){let r=this._$Eu(t,o);r!==void 0&&this._$Eh.set(r,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let t=[];if(Array.isArray(e)){let o=new Set(e.flat(1/0).reverse());for(let r of o)t.unshift(pe(r))}else e!==void 0&&t.push(pe(e));return t}static _$Eu(e,t){let o=t.attribute;return o===!1?void 0:typeof o=="string"?o:typeof e=="string"?e.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(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,t=this.constructor.elementProperties;for(let o of t.keys())this.hasOwnProperty(o)&&(e.set(o,this[o]),delete this[o]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return Ae(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,o){this._$AK(e,o)}_$ET(e,t){let o=this.constructor.elementProperties.get(e),r=this.constructor._$Eu(e,o);if(r!==void 0&&o.reflect===!0){let n=(o.converter?.toAttribute!==void 0?o.converter:B).toAttribute(t,o.type);this._$Em=e,n==null?this.removeAttribute(r):this.setAttribute(r,n),this._$Em=null}}_$AK(e,t){let o=this.constructor,r=o._$Eh.get(e);if(r!==void 0&&this._$Em!==r){let n=o.getPropertyOptions(r),s=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:B;this._$Em=r;let l=s.fromAttribute(t,n.type);this[r]=l??this._$Ej?.get(r)??l,this._$Em=null}}requestUpdate(e,t,o,r=!1,n){if(e!==void 0){let s=this.constructor;if(r===!1&&(n=this[e]),o??=s.getPropertyOptions(e),!((o.hasChanged??re)(n,t)||o.useDefault&&o.reflect&&n===this._$Ej?.get(e)&&!this.hasAttribute(s._$Eu(e,o))))return;this.C(e,t,o)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(e,t,{useDefault:o,reflect:r,wrapped:n},s){o&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,s??t??this[e]),n!==!0||s!==void 0)||(this._$AL.has(e)||(this.hasUpdated||o||(t=void 0),this._$AL.set(e,t)),r===!0&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}let e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[r,n]of this._$Ep)this[r]=n;this._$Ep=void 0}let o=this.constructor.elementProperties;if(o.size>0)for(let[r,n]of o){let{wrapped:s}=n,l=this[r];s!==!0||this._$AL.has(r)||l===void 0||this.C(r,void 0,n,l)}}let e=!1,t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(o=>o.hostUpdate?.()),this.update(t)):this._$EM()}catch(o){throw e=!1,this._$EM(),o}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(e){}firstUpdated(e){}};S.elementStyles=[],S.shadowRootOptions={mode:"open"},S[F("elementProperties")]=new Map,S[F("finalized")]=new Map,ft?.({ReactiveElement:S}),(oe.reactiveElementVersions??=[]).push("2.1.2");var be=globalThis,Ce=a=>a,ae=be.trustedTypes,Te=ae?ae.createPolicy("lit-html",{createHTML:a=>a}):void 0,Oe="$lit$",A=`lit$${Math.random().toFixed(9).slice(2)}$`,Le="?"+A,mt=`<${Le}>`,T=document,Y=()=>T.createComment(""),G=a=>a===null||typeof a!="object"&&typeof a!="function",ye=Array.isArray,bt=a=>ye(a)||typeof a?.[Symbol.iterator]=="function",ue=`[ +\f\r]`,W=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,De=/-->/g,Ne=/>/g,E=RegExp(`>|${ue}(?:([^\\s"'>=/]+)(${ue}*=${ue}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),Re=/'/g,Pe=/"/g,Me=/^(?:script|style|textarea|title)$/i,ve=a=>(e,...t)=>({_$litType$:a,strings:e,values:t}),m=ve(1),It=ve(2),qt=ve(3),D=Symbol.for("lit-noChange"),g=Symbol.for("lit-nothing"),ze=new WeakMap,C=T.createTreeWalker(T,129);function Ue(a,e){if(!ye(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return Te!==void 0?Te.createHTML(e):e}var yt=(a,e)=>{let t=a.length-1,o=[],r,n=e===2?"":e===3?"":"",s=W;for(let l=0;l"?(s=r??W,c=-1):u[1]===void 0?c=-2:(c=s.lastIndex-u[2].length,p=u[1],s=u[3]===void 0?E:u[3]==='"'?Pe:Re):s===Pe||s===Re?s=E:s===De||s===Ne?s=W:(s=E,r=void 0);let h=s===E&&a[l+1].startsWith("/>")?" ":"";n+=s===W?d+mt:c>=0?(o.push(p),d.slice(0,c)+Oe+d.slice(c)+A+h):d+A+(c===-2?l:h)}return[Ue(a,n+(a[t]||"")+(e===2?"":e===3?"":"")),o]},V=class a{constructor({strings:e,_$litType$:t},o){let r;this.parts=[];let n=0,s=0,l=e.length-1,d=this.parts,[p,u]=yt(e,t);if(this.el=a.createElement(p,o),C.currentNode=this.el.content,t===2||t===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(r=C.nextNode())!==null&&d.length0){r.textContent=ae?ae.emptyScript:"";for(let h=0;h<_;h++)r.append(c[h],Y()),C.nextNode(),d.push({type:2,index:++n});r.append(c[_],Y())}}}else if(r.nodeType===8)if(r.data===Le)d.push({type:2,index:n});else{let c=-1;for(;(c=r.data.indexOf(A,c+1))!==-1;)d.push({type:7,index:n}),c+=A.length-1}n++}}static createElement(e,t){let o=T.createElement("template");return o.innerHTML=e,o}};function z(a,e,t=a,o){if(e===D)return e;let r=o!==void 0?t._$Co?.[o]:t._$Cl,n=G(e)?void 0:e._$litDirective$;return r?.constructor!==n&&(r?._$AO?.(!1),n===void 0?r=void 0:(r=new n(a),r._$AT(a,t,o)),o!==void 0?(t._$Co??=[])[o]=r:t._$Cl=r),r!==void 0&&(e=z(a,r._$AS(a,e.values),r,o)),e}var _e=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:o}=this._$AD,r=(e?.creationScope??T).importNode(t,!0);C.currentNode=r;let n=C.nextNode(),s=0,l=0,d=o[0];for(;d!==void 0;){if(s===d.index){let p;d.type===2?p=new K(n,n.nextSibling,this,e):d.type===1?p=new d.ctor(n,d.name,d.strings,this,e):d.type===6&&(p=new me(n,this,e)),this._$AV.push(p),d=o[++l]}s!==d?.index&&(n=C.nextNode(),s++)}return C.currentNode=T,r}p(e){let t=0;for(let o of this._$AV)o!==void 0&&(o.strings!==void 0?(o._$AI(e,o,t),t+=o.strings.length-2):o._$AI(e[t])),t++}},K=class a{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,o,r){this.type=2,this._$AH=g,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=o,this.options=r,this._$Cv=r?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=z(this,e,t),G(e)?e===g||e==null||e===""?(this._$AH!==g&&this._$AR(),this._$AH=g):e!==this._$AH&&e!==D&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):bt(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==g&&G(this._$AH)?this._$AA.nextSibling.data=e:this.T(T.createTextNode(e)),this._$AH=e}$(e){let{values:t,_$litType$:o}=e,r=typeof o=="number"?this._$AC(e):(o.el===void 0&&(o.el=V.createElement(Ue(o.h,o.h[0]),this.options)),o);if(this._$AH?._$AD===r)this._$AH.p(t);else{let n=new _e(r,this),s=n.u(this.options);n.p(t),this.T(s),this._$AH=n}}_$AC(e){let t=ze.get(e.strings);return t===void 0&&ze.set(e.strings,t=new V(e)),t}k(e){ye(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,o,r=0;for(let n of e)r===t.length?t.push(o=new a(this.O(Y()),this.O(Y()),this,this.options)):o=t[r],o._$AI(n),r++;r2||o[0]!==""||o[1]!==""?(this._$AH=Array(o.length-1).fill(new String),this.strings=o):this._$AH=g}_$AI(e,t=this,o,r){let n=this.strings,s=!1;if(n===void 0)e=z(this,e,t,0),s=!G(e)||e!==this._$AH&&e!==D,s&&(this._$AH=e);else{let l=e,d,p;for(e=n[0],d=0;d{let o=t?.renderBefore??e,r=o._$litPart$;if(r===void 0){let n=t?.renderBefore??null;o._$litPart$=r=new K(e.insertBefore(Y(),n),n,void 0,t??{})}return r._$AI(a),r};var xe=globalThis,$=class extends S{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=He(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return D}};$._$litElement$=!0,$.finalized=!0,xe.litElementHydrateSupport?.({LitElement:$});var xt=xe.litElementPolyfillSupport;xt?.({LitElement:$});(xe.litElementVersions??=[]).push("4.2.2");var wt={attribute:!0,type:String,converter:B,reflect:!1,hasChanged:re},kt=(a=wt,e,t)=>{let{kind:o,metadata:r}=t,n=globalThis.litPropertyMetadata.get(r);if(n===void 0&&globalThis.litPropertyMetadata.set(r,n=new Map),o==="setter"&&((a=Object.create(a)).wrapped=!0),n.set(t.name,a),o==="accessor"){let{name:s}=t;return{set(l){let d=e.get.call(this);e.set.call(this,l),this.requestUpdate(s,d,a,!0,l)},init(l){return l!==void 0&&this.C(s,void 0,a,l),l}}}if(o==="setter"){let{name:s}=t;return function(l){let d=this[s];e.call(this,l),this.requestUpdate(s,d,a,!0,l)}}throw Error("Unsupported decorator location: "+o)};function Q(a){return(e,t)=>typeof t=="object"?kt(a,e,t):((o,r,n)=>{let s=r.hasOwnProperty(n);return r.constructor.createProperty(n,o),s?Object.getOwnPropertyDescriptor(r,n):void 0})(a,e,t)}function w(a){return Q({...a,state:!0,attribute:!1})}var St={days:1,weeks:7,months:30.4368,years:365.25};function Ie(a,e){return!a||a<=0?0:a*(St[e||"days"]??1)}var qe=5;function J(a){let e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),o=String(a.getDate()).padStart(2,"0");return`${e}-${t}-${o}`}function $t(a,e){let t=[];for(let o=0;ot.cost).filter(t=>typeof t=="number");return e.length===0?null:e.reduce((t,o)=>t+o,0)/e.length}function jt(a){let{windowStart:e,windowEnd:t,task:o,entryId:r,objectName:n}=a,s=[],l=(c,_)=>({date:c,entry_id:r,task_id:o.id,task_name:o.name,object_name:n,status:_&&(o.status==="overdue"||o.status==="triggered")?"ok":o.status,days_until_due:_?null:o.days_until_due??null,projected:_,schedule_type:o.schedule_type,interval_days:o.interval_days??null,interval_unit:o.interval_unit??null,responsible_user_id:o.responsible_user_id??null,avg_cost:At(o.history),adaptive_enabled:!!o.adaptive_config?.enabled,prediction_confidence:o.threshold_prediction_confidence??null}),d=Math.max(1,Math.round(Ie(o.interval_days,o.interval_unit)));if(o.status==="overdue"||o.status==="triggered"){if(s.push(l(e,!1)),o.schedule_type==="time_based"&&o.interval_days&&o.interval_days>0){let c=se(e,d),_=1;for(;c<=t&&_=e&&u<=t)s.push(l(u,!1));else if(u>t)return s;if(o.schedule_type==="time_based"&&o.interval_days&&o.interval_days>0){let c=se(u,d),_=s.length;for(;c<=t&&_=e&&(s.push(l(c,!0)),_++),c=se(c,d)}return s}var Fe={overdue:0,triggered:1,due_soon:2,ok:3};function Be(a,e,t,o=null){let r=$t(e,t),n=r[0],s=r[r.length-1],l=[];for(let p of a){let u=p.object?.name||"",c=p.entry_id,_=p.tasks||[];for(let h of _){if(o&&h.responsible_user_id!==o||h.enabled===!1)continue;let b=jt({windowStart:n,windowEnd:s,task:h,entryId:c,objectName:u});l.push(...b)}}let d=new Map;for(let p of r)d.set(p,[]);for(let p of l){let u=d.get(p.date);u&&u.push(p)}for(let[,p]of d)p.sort((u,c)=>{let _=Fe[u.status]??99,h=Fe[c.status]??99;if(_!==h)return _-h;if(u.projected!==c.projected)return u.projected?1:-1;let b=u.object_name.localeCompare(c.object_name);return b!==0?b:u.task_name.localeCompare(c.task_name)});return r.map(p=>({date:p,events:d.get(p)??[]}))}var Et={completed:"ok",reset:"ok",skipped:"due_soon",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"};function Ct(a,e){let t=[];for(let o=e-1;o>=0;o--){let r=new Date(a);r.setDate(r.getDate()-o),r.setHours(0,0,0,0),t.push(J(r))}return t}function We(a,e,t,o=null){let r=Ct(e,t),n=r[0],s=r[r.length-1],l=new Map;for(let p of r)l.set(p,[]);for(let p of a){let u=p.object?.name||"",c=p.entry_id,_=p.tasks||[];for(let h of _){if(o&&h.responsible_user_id!==o)continue;let b=h.history||[];for(let y of b){if(typeof y?.timestamp!="string")continue;let R=y.timestamp.slice(0,10);if(Rs)continue;let M=l.get(R);if(!M)continue;let U=y.type??"completed";M.push({date:R,entry_id:c,task_id:h.id,task_name:h.name,object_name:u,status:Et[U]??"ok",days_until_due:null,projected:!1,schedule_type:h.schedule_type,interval_days:h.interval_days??null,responsible_user_id:h.responsible_user_id??null,avg_cost:typeof y.cost=="number"?y.cost:null,adaptive_enabled:!!h.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:y.timestamp,history_type:U,history_cost:typeof y.cost=="number"?y.cost:null,history_notes:typeof y.notes=="string"?y.notes:null,history_duration:typeof y.duration=="number"?y.duration:null})}}}let d={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,p]of l)p.sort((u,c)=>{let _=d[u.history_type??""]??99,h=d[c.history_type??""]??99;if(_!==h)return _-h;let b=u.object_name.localeCompare(c.object_name);return b!==0?b:u.task_name.localeCompare(c.task_name)});return r.map(p=>({date:p,events:l.get(p)??[]}))}var Ye=k` .cal-controls { display: flex; gap: 12px; @@ -200,7 +200,7 @@ var it=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var x=(a,e,t } `;var Ge={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",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)",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",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",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",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",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",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_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_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).",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",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 (comma-separated)",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_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_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"};var Ke="\u20AC",we="en",Qe=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),N=Qe.store;N.en||(N.en=Ge);var Dt=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),Nt="/maintenance_supporter_locales",Z=Qe.inflight;function ke(a){let e=(a||we).toLowerCase();return e.startsWith("pt")&&e.endsWith("br")?"pt-br":e.substring(0,2)}function f(a,e){let t=ke(e);return N[t]?.[a]??N.en[a]??a}function Je(a){let e=ke(a);return e===we||e in N}function Ze(a){let e=ke(a);return e===we||e in N||!Dt.has(e)?Promise.resolve():(e in Z||(Z[e]=fetch(`${Nt}/${e}.json`).then(t=>t.ok?t.json():null).then(t=>{t?N[e]=t:delete Z[e]}).catch(()=>{delete Z[e]})),Z[e])}var Rt=window,Ve=Rt.__msDateTimePrefs??={};function Xe(a){a&&(Ve.date=a.date_format,Ve.time=a.time_format)}function et(a,e){if(a==null)return"\u2014";let t=e||"en";return a<0?`${Math.abs(a)} ${f("d_overdue",t)}`:a===0?f("today",t):`${a} ${f(a===1?"day":"days",t)}`}var Ho=k` +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)",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",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",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",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",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_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_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).",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",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 (comma-separated)",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_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_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"};var Ke="\u20AC",we="en",Qe=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),N=Qe.store;N.en||(N.en=Ge);var Dt=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),Nt="/maintenance_supporter_locales",Z=Qe.inflight;function ke(a){let e=(a||we).toLowerCase();return e.startsWith("pt")&&e.endsWith("br")?"pt-br":e.substring(0,2)}function f(a,e){let t=ke(e);return N[t]?.[a]??N.en[a]??a}function Je(a){let e=ke(a);return e===we||e in N}function Ze(a){let e=ke(a);return e===we||e in N||!Dt.has(e)?Promise.resolve():(e in Z||(Z[e]=fetch(`${Nt}/${e}.json`).then(t=>t.ok?t.json():null).then(t=>{t?N[e]=t:delete Z[e]}).catch(()=>{delete Z[e]})),Z[e])}var Rt=window,Ve=Rt.__msDateTimePrefs??={};function Xe(a){a&&(Ve.date=a.date_format,Ve.time=a.time_format)}function et(a,e){if(a==null)return"\u2014";let t=e||"en";return a<0?`${Math.abs(a)} ${f("d_overdue",t)}`:a===0?f("today",t):`${a} ${f(a===1?"day":"days",t)}`}var Ho=k` .field { display: flex; flex-direction: column; gap: 4px; } .field-label { font-size: 12px; color: var(--secondary-text-color); } .field-input { diff --git a/custom_components/maintenance_supporter/frontend/maintenance-card.js b/custom_components/maintenance_supporter/frontend/maintenance-card.js index 6400930e..47f2121d 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-card.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-card.js @@ -1,9 +1,9 @@ -/*! maintenance_supporter frontend 2.55.0 */ -var mt=Object.defineProperty;var Pi=Object.getOwnPropertyDescriptor;var w=(a,s,e)=>()=>{if(e)throw e[0];try{return a&&(s=a(a=0)),s}catch(t){throw e=[t],t}};var ji=(a,s)=>{for(var e in s)mt(a,e,{get:s[e],enumerable:!0})};var l=(a,s,e,t)=>{for(var i=t>1?void 0:t?Pi(s,e):s,n=a.length-1,c;n>=0;n--)(c=a[n])&&(i=(t?c(s,e,i):c(i))||i);return t&&i&&mt(s,e,i),i};var Se,Ae,Ue,ft,pe,vt,E,bt,Ve,Be=w(()=>{Se=globalThis,Ae=Se.ShadowRoot&&(Se.ShadyCSS===void 0||Se.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Ue=Symbol(),ft=new WeakMap,pe=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Ue)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(Ae&&s===void 0){let t=e!==void 0&&e.length===1;t&&(s=ft.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&ft.set(e,s))}return s}toString(){return this.cssText}},vt=a=>new pe(typeof a=="string"?a:a+"",void 0,Ue),E=(a,...s)=>{let e=a.length===1?a[0]:s.reduce((t,i,n)=>t+(c=>{if(c._$cssResult$===!0)return c.cssText;if(typeof c=="number")return c;throw Error("Value passed to 'css' function must be a 'css' function result: "+c+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new pe(e,a,Ue)},bt=(a,s)=>{if(Ae)a.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of s){let t=document.createElement("style"),i=Se.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Ve=Ae?a=>a:a=>a instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return vt(e)})(a):a});var Ri,Hi,Ni,qi,zi,Mi,Te,yt,Oi,Di,he,ue,Ce,xt,B,_e=w(()=>{Be();Be();({is:Ri,defineProperty:Hi,getOwnPropertyDescriptor:Ni,getOwnPropertyNames:qi,getOwnPropertySymbols:zi,getPrototypeOf:Mi}=Object),Te=globalThis,yt=Te.trustedTypes,Oi=yt?yt.emptyScript:"",Di=Te.reactiveElementPolyfillSupport,he=(a,s)=>a,ue={toAttribute(a,s){switch(s){case Boolean:a=a?Oi:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,s){let e=a;switch(s){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},Ce=(a,s)=>!Ri(a,s),xt={attribute:!0,type:String,converter:ue,reflect:!1,useDefault:!1,hasChanged:Ce};Symbol.metadata??=Symbol("metadata"),Te.litPropertyMetadata??=new WeakMap;B=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=xt){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&&Hi(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=Ni(this.prototype,s)??{get(){return this[e]},set(c){this[e]=c}};return{get:i,set(c){let d=i?.call(this);n?.call(this,c),this.requestUpdate(s,d,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??xt}static _$Ei(){if(this.hasOwnProperty(he("elementProperties")))return;let s=Mi(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(he("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(he("properties"))){let e=this.properties,t=[...qi(e),...zi(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(Ve(i))}else s!==void 0&&e.push(Ve(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 bt(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:ue).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),c=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:ue;this._$Em=i;let d=c.fromAttribute(e,n.type);this[i]=d??this._$Ej?.get(i)??d,this._$Em=null}}requestUpdate(s,e,t,i=!1,n){if(s!==void 0){let c=this.constructor;if(i===!1&&(n=this[s]),t??=c.getPropertyOptions(s),!((t.hasChanged??Ce)(n,e)||t.useDefault&&t.reflect&&n===this._$Ej?.get(s)&&!this.hasAttribute(c._$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},c){t&&!(this._$Ej??=new Map).has(s)&&(this._$Ej.set(s,c??e??this[s]),n!==!0||c!==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:c}=n,d=this[i];c!==!0||this._$AL.has(i)||d===void 0||this.C(i,void 0,n,d)}}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){}};B.elementStyles=[],B.shadowRootOptions={mode:"open"},B[he("elementProperties")]=new Map,B[he("finalized")]=new Map,Di?.({ReactiveElement:B}),(Te.reactiveElementVersions??=[]).push("2.1.2")});function Pt(a,s){if(!Xe(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return $t!==void 0?$t.createHTML(s):s}function ae(a,s,e=a,t){if(s===te)return s;let i=t!==void 0?e._$Co?.[t]:e._$Cl,n=fe(s)?void 0:s._$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&&(s=ae(a,i._$AS(a,s.values),i,t)),s}var Ze,wt,Le,$t,Ct,Q,Lt,Fi,ee,me,fe,Xe,Ui,We,ge,kt,Et,Z,St,At,It,et,o,oe,Us,te,h,Tt,X,Vi,ve,Ke,be,ne,Ge,Ye,Je,Qe,Bi,jt,Ie=w(()=>{Ze=globalThis,wt=a=>a,Le=Ze.trustedTypes,$t=Le?Le.createPolicy("lit-html",{createHTML:a=>a}):void 0,Ct="$lit$",Q=`lit$${Math.random().toFixed(9).slice(2)}$`,Lt="?"+Q,Fi=`<${Lt}>`,ee=document,me=()=>ee.createComment(""),fe=a=>a===null||typeof a!="object"&&typeof a!="function",Xe=Array.isArray,Ui=a=>Xe(a)||typeof a?.[Symbol.iterator]=="function",We=`[ -\f\r]`,ge=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,kt=/-->/g,Et=/>/g,Z=RegExp(`>|${We}(?:([^\\s"'>=/]+)(${We}*=${We}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),St=/'/g,At=/"/g,It=/^(?:script|style|textarea|title)$/i,et=a=>(s,...e)=>({_$litType$:a,strings:s,values:e}),o=et(1),oe=et(2),Us=et(3),te=Symbol.for("lit-noChange"),h=Symbol.for("lit-nothing"),Tt=new WeakMap,X=ee.createTreeWalker(ee,129);Vi=(a,s)=>{let e=a.length-1,t=[],i,n=s===2?"":s===3?"":"",c=ge;for(let d=0;d"?(c=i??ge,f=-1):v[1]===void 0?f=-2:(f=c.lastIndex-v[2].length,_=v[1],c=v[3]===void 0?Z:v[3]==='"'?At:St):c===At||c===St?c=Z:c===kt||c===Et?c=ge:(c=Z,i=void 0);let g=c===Z&&a[d+1].startsWith("/>")?" ":"";n+=c===ge?u+Fi:f>=0?(t.push(_),u.slice(0,f)+Ct+u.slice(f)+Q+g):u+Q+(f===-2?d:g)}return[Pt(a,n+(a[e]||"")+(s===2?"":s===3?"":"")),t]},ve=class a{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,c=0,d=s.length-1,u=this.parts,[_,v]=Vi(s,e);if(this.el=a.createElement(_,t),X.currentNode=this.el.content,e===2||e===3){let f=this.el.content.firstChild;f.replaceWith(...f.childNodes)}for(;(i=X.nextNode())!==null&&u.length0){i.textContent=Le?Le.emptyScript:"";for(let g=0;g2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=h}_$AI(s,e=this,t,i){let n=this.strings,c=!1;if(n===void 0)s=ae(this,s,e,0),c=!fe(s)||s!==this._$AH&&s!==te,c&&(this._$AH=s);else{let d=s,u,_;for(s=n[0],u=0;u{let t=e?.renderBefore??s,i=t._$litPart$;if(i===void 0){let n=e?.renderBefore??null;t._$litPart$=i=new be(s.insertBefore(me(),n),n,void 0,e??{})}return i._$AI(a),i}});var tt,k,Wi,Rt=w(()=>{_e();_e();Ie();Ie();tt=globalThis,k=class extends B{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=jt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return te}};k._$litElement$=!0,k.finalized=!0,tt.litElementHydrateSupport?.({LitElement:k});Wi=tt.litElementPolyfillSupport;Wi?.({LitElement:k});(tt.litElementVersions??=[]).push("4.2.2")});var Ht=w(()=>{});var I=w(()=>{_e();Ie();Rt();Ht()});var qt=w(()=>{});function b(a){return(s,e)=>typeof e=="object"?es(a,s,e):((t,i,n)=>{let c=i.hasOwnProperty(n);return i.constructor.createProperty(n,t),c?Object.getOwnPropertyDescriptor(i,n):void 0})(a,s,e)}var Xi,es,st=w(()=>{_e();Xi={attribute:!0,type:String,converter:ue,reflect:!1,hasChanged:Ce},es=(a=Xi,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"&&((a=Object.create(a)).wrapped=!0),n.set(e.name,a),t==="accessor"){let{name:c}=e;return{set(d){let u=s.get.call(this);s.set.call(this,d),this.requestUpdate(c,u,a,!0,d)},init(d){return d!==void 0&&this.C(c,void 0,a,d),d}}}if(t==="setter"){let{name:c}=e;return function(d){let u=this[c];s.call(this,d),this.requestUpdate(c,u,a,!0,d)}}throw Error("Unsupported decorator location: "+t)}});function p(a){return b({...a,state:!0,attribute:!1})}var zt=w(()=>{st();});var Mt=w(()=>{});var le=w(()=>{});var Ot=w(()=>{le();});var Dt=w(()=>{le();});var Ft=w(()=>{le();});var Ut=w(()=>{le();});var Vt=w(()=>{le();});var M=w(()=>{qt();st();zt();Mt();Ot();Dt();Ft();Ut();Vt()});var Wt,Bt=w(()=>{Wt={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",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.56.0 */ +var ft=Object.defineProperty;var Hi=Object.getOwnPropertyDescriptor;var $=(a,s,e)=>()=>{if(e)throw e[0];try{return a&&(s=a(a=0)),s}catch(t){throw e=[t],t}};var Mi=(a,s)=>{for(var e in s)ft(a,e,{get:s[e],enumerable:!0})};var l=(a,s,e,t)=>{for(var i=t>1?void 0:t?Hi(s,e):s,n=a.length-1,d;n>=0;n--)(d=a[n])&&(i=(t?d(s,e,i):d(i))||i);return t&&i&&ft(s,e,i),i};var Se,Ae,Ue,vt,pe,bt,E,yt,Ve,Be=$(()=>{Se=globalThis,Ae=Se.ShadowRoot&&(Se.ShadyCSS===void 0||Se.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Ue=Symbol(),vt=new WeakMap,pe=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Ue)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(Ae&&s===void 0){let t=e!==void 0&&e.length===1;t&&(s=vt.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&vt.set(e,s))}return s}toString(){return this.cssText}},bt=a=>new pe(typeof a=="string"?a:a+"",void 0,Ue),E=(a,...s)=>{let e=a.length===1?a[0]:s.reduce((t,i,n)=>t+(d=>{if(d._$cssResult$===!0)return d.cssText;if(typeof d=="number")return d;throw Error("Value passed to 'css' function must be a 'css' function result: "+d+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new pe(e,a,Ue)},yt=(a,s)=>{if(Ae)a.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of s){let t=document.createElement("style"),i=Se.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Ve=Ae?a=>a:a=>a instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return bt(e)})(a):a});var qi,Oi,zi,Fi,Di,Ui,Te,xt,Vi,Bi,he,ue,Ie,$t,B,_e=$(()=>{Be();Be();({is:qi,defineProperty:Oi,getOwnPropertyDescriptor:zi,getOwnPropertyNames:Fi,getOwnPropertySymbols:Di,getPrototypeOf:Ui}=Object),Te=globalThis,xt=Te.trustedTypes,Vi=xt?xt.emptyScript:"",Bi=Te.reactiveElementPolyfillSupport,he=(a,s)=>a,ue={toAttribute(a,s){switch(s){case Boolean:a=a?Vi:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,s){let e=a;switch(s){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},Ie=(a,s)=>!qi(a,s),$t={attribute:!0,type:String,converter:ue,reflect:!1,useDefault:!1,hasChanged:Ie};Symbol.metadata??=Symbol("metadata"),Te.litPropertyMetadata??=new WeakMap;B=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=$t){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&&Oi(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=zi(this.prototype,s)??{get(){return this[e]},set(d){this[e]=d}};return{get:i,set(d){let c=i?.call(this);n?.call(this,d),this.requestUpdate(s,c,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??$t}static _$Ei(){if(this.hasOwnProperty(he("elementProperties")))return;let s=Ui(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(he("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(he("properties"))){let e=this.properties,t=[...Fi(e),...Di(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(Ve(i))}else s!==void 0&&e.push(Ve(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 yt(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:ue).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),d=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:ue;this._$Em=i;let c=d.fromAttribute(e,n.type);this[i]=c??this._$Ej?.get(i)??c,this._$Em=null}}requestUpdate(s,e,t,i=!1,n){if(s!==void 0){let d=this.constructor;if(i===!1&&(n=this[s]),t??=d.getPropertyOptions(s),!((t.hasChanged??Ie)(n,e)||t.useDefault&&t.reflect&&n===this._$Ej?.get(s)&&!this.hasAttribute(d._$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},d){t&&!(this._$Ej??=new Map).has(s)&&(this._$Ej.set(s,d??e??this[s]),n!==!0||d!==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:d}=n,c=this[i];d!==!0||this._$AL.has(i)||c===void 0||this.C(i,void 0,n,c)}}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){}};B.elementStyles=[],B.shadowRootOptions={mode:"open"},B[he("elementProperties")]=new Map,B[he("finalized")]=new Map,Bi?.({ReactiveElement:B}),(Te.reactiveElementVersions??=[]).push("2.1.2")});function Rt(a,s){if(!Xe(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return kt!==void 0?kt.createHTML(s):s}function ae(a,s,e=a,t){if(s===te)return s;let i=t!==void 0?e._$Co?.[t]:e._$Cl,n=fe(s)?void 0:s._$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&&(s=ae(a,i._$AS(a,s.values),i,t)),s}var Ze,wt,Ce,kt,Ct,Q,Lt,Wi,ee,me,fe,Xe,Ki,We,ge,Et,St,Z,At,Tt,Pt,et,o,oe,Gs,te,u,It,X,Gi,ve,Ke,be,ne,Ge,Ye,Je,Qe,Yi,jt,Le=$(()=>{Ze=globalThis,wt=a=>a,Ce=Ze.trustedTypes,kt=Ce?Ce.createPolicy("lit-html",{createHTML:a=>a}):void 0,Ct="$lit$",Q=`lit$${Math.random().toFixed(9).slice(2)}$`,Lt="?"+Q,Wi=`<${Lt}>`,ee=document,me=()=>ee.createComment(""),fe=a=>a===null||typeof a!="object"&&typeof a!="function",Xe=Array.isArray,Ki=a=>Xe(a)||typeof a?.[Symbol.iterator]=="function",We=`[ +\f\r]`,ge=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Et=/-->/g,St=/>/g,Z=RegExp(`>|${We}(?:([^\\s"'>=/]+)(${We}*=${We}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),At=/'/g,Tt=/"/g,Pt=/^(?:script|style|textarea|title)$/i,et=a=>(s,...e)=>({_$litType$:a,strings:s,values:e}),o=et(1),oe=et(2),Gs=et(3),te=Symbol.for("lit-noChange"),u=Symbol.for("lit-nothing"),It=new WeakMap,X=ee.createTreeWalker(ee,129);Gi=(a,s)=>{let e=a.length-1,t=[],i,n=s===2?"":s===3?"":"",d=ge;for(let c=0;c"?(d=i??ge,f=-1):v[1]===void 0?f=-2:(f=d.lastIndex-v[2].length,_=v[1],d=v[3]===void 0?Z:v[3]==='"'?Tt:At):d===Tt||d===At?d=Z:d===Et||d===St?d=ge:(d=Z,i=void 0);let g=d===Z&&a[c+1].startsWith("/>")?" ":"";n+=d===ge?h+Wi:f>=0?(t.push(_),h.slice(0,f)+Ct+h.slice(f)+Q+g):h+Q+(f===-2?c:g)}return[Rt(a,n+(a[e]||"")+(s===2?"":s===3?"":"")),t]},ve=class a{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,d=0,c=s.length-1,h=this.parts,[_,v]=Gi(s,e);if(this.el=a.createElement(_,t),X.currentNode=this.el.content,e===2||e===3){let f=this.el.content.firstChild;f.replaceWith(...f.childNodes)}for(;(i=X.nextNode())!==null&&h.length0){i.textContent=Ce?Ce.emptyScript:"";for(let g=0;g2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=u}_$AI(s,e=this,t,i){let n=this.strings,d=!1;if(n===void 0)s=ae(this,s,e,0),d=!fe(s)||s!==this._$AH&&s!==te,d&&(this._$AH=s);else{let c=s,h,_;for(s=n[0],h=0;h{let t=e?.renderBefore??s,i=t._$litPart$;if(i===void 0){let n=e?.renderBefore??null;t._$litPart$=i=new be(s.insertBefore(me(),n),n,void 0,e??{})}return i._$AI(a),i}});var tt,k,Ji,Nt=$(()=>{_e();_e();Le();Le();tt=globalThis,k=class extends B{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=jt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return te}};k._$litElement$=!0,k.finalized=!0,tt.litElementHydrateSupport?.({LitElement:k});Ji=tt.litElementPolyfillSupport;Ji?.({LitElement:k});(tt.litElementVersions??=[]).push("4.2.2")});var Ht=$(()=>{});var L=$(()=>{_e();Le();Nt();Ht()});var qt=$(()=>{});function b(a){return(s,e)=>typeof e=="object"?rs(a,s,e):((t,i,n)=>{let d=i.hasOwnProperty(n);return i.constructor.createProperty(n,t),d?Object.getOwnPropertyDescriptor(i,n):void 0})(a,s,e)}var ss,rs,st=$(()=>{_e();ss={attribute:!0,type:String,converter:ue,reflect:!1,hasChanged:Ie},rs=(a=ss,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"&&((a=Object.create(a)).wrapped=!0),n.set(e.name,a),t==="accessor"){let{name:d}=e;return{set(c){let h=s.get.call(this);s.set.call(this,c),this.requestUpdate(d,h,a,!0,c)},init(c){return c!==void 0&&this.C(d,void 0,a,c),c}}}if(t==="setter"){let{name:d}=e;return function(c){let h=this[d];s.call(this,c),this.requestUpdate(d,h,a,!0,c)}}throw Error("Unsupported decorator location: "+t)}});function p(a){return b({...a,state:!0,attribute:!1})}var Ot=$(()=>{st();});var zt=$(()=>{});var le=$(()=>{});var Ft=$(()=>{le();});var Dt=$(()=>{le();});var Ut=$(()=>{le();});var Vt=$(()=>{le();});var Bt=$(()=>{le();});var O=$(()=>{qt();st();Ot();zt();Ft();Dt();Ut();Vt();Bt()});var Kt,Wt=$(()=>{Kt={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",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)",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",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",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",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",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",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_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_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).",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",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 (comma-separated)",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_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_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"}});var ie,Kt=w(()=>{"use strict";ie={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 He(a){let s=(a||rt).toLowerCase();return s.startsWith("pt")&&s.endsWith("br")?"pt-br":s.substring(0,2)}function r(a,s){let e=He(s);return se[e]?.[a]??se.en[a]??a}function Ne(a){let s=He(a);return s===rt||s in se}function qe(a){let s=He(a);return s===rt||s in se||!is.has(s)?Promise.resolve():(s in xe||(xe[s]=fetch(`${ss}/${s}.json`).then(e=>e.ok?e.json():null).then(e=>{e?se[s]=e:delete xe[s]}).catch(()=>{delete xe[s]})),xe[s])}function we(a){let s=He(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"}[s]??"en-US"}function Yt(a){a&&(je.date=a.date_format,je.time=a.time_format)}function Jt(a,s){let e=String(a.getDate()).padStart(2,"0"),t=String(a.getMonth()+1).padStart(2,"0"),i=String(a.getFullYear());switch(je.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(we(s),{day:"2-digit",month:"2-digit",year:"numeric"})}}function as(a,s){switch(je.time){case"12":return a.toLocaleTimeString(we(s),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return a.toLocaleTimeString(we(s),{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(we(s),{hour:"2-digit",minute:"2-digit"})}}function W(a,s){if(!a)return"\u2014";try{let e=a.includes("T")?a:a+"T00:00:00";return Jt(new Date(e),s)}catch{return a}}function Qt(a,s){if(!a)return"\u2014";try{let e=new Date(a);return Jt(e,s)+" "+as(e,s)}catch{return a}}function at(a,s){if(a==null)return"\u2014";let e=s||"en";return a<0?`${Math.abs(a)} ${r("d_overdue",e)}`:a===0?r("today",e):`${a} ${r(a===1?"day":"days",e)}`}function Re(a,s,e){return a==null?"\u2014":`${a} ${r("unit_"+(s||"days"),e)}`}function $e(a,s,e="long"){return new Date(Date.UTC(2024,0,1+a)).toLocaleDateString(we(s),{weekday:e,timeZone:"UTC"})}function Zt(a,s){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=>$e(i,s,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?r("ord_last",s):r("ord_"+e.nth,s)} ${$e(e.weekday,s,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?r(e.business?"last_business_day_month":"last_day_month",s):`${r("day_word",s)} ${e.day}`)+t;case"one_time":return a.due_date?W(a.due_date,s):r("one_time",s);case"manual":return r("manual",s);case"interval":return Re(e.every,e.unit,s)}return a.schedule_type==="one_time"?a.due_date?W(a.due_date,s):r("one_time",s):a.schedule_type==="manual"?r("manual",s):a.schedule_type==="sensor_based"?r("sensor_based",s):a.interval_days!=null?Re(a.interval_days,a.interval_unit,s):"\u2014"}function Xt(a,s){a.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:s},bubbles:!0,composed:!0}))}var rt,Gt,se,is,ss,xe,rs,je,ei,ze,L=w(()=>{"use strict";I();Bt();Kt();rt="en",Gt=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),se=Gt.store;se.en||(se.en=Wt);is=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),ss="/maintenance_supporter_locales",xe=Gt.inflight;rs=window,je=rs.__msDateTimePrefs??={};ei=E` +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)",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",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",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",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",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_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_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).",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",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 (comma-separated)",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_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_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"}});var ie,Gt=$(()=>{"use strict";ie={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 Ne(a){let s=(a||rt).toLowerCase();return s.startsWith("pt")&&s.endsWith("br")?"pt-br":s.substring(0,2)}function r(a,s){let e=Ne(s);return se[e]?.[a]??se.en[a]??a}function He(a){let s=Ne(a);return s===rt||s in se}function Me(a){let s=Ne(a);return s===rt||s in se||!ns.has(s)?Promise.resolve():(s in xe||(xe[s]=fetch(`${os}/${s}.json`).then(e=>e.ok?e.json():null).then(e=>{e?se[s]=e:delete xe[s]}).catch(()=>{delete xe[s]})),xe[s])}function $e(a){let s=Ne(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"}[s]??"en-US"}function Jt(a){a&&(Re.date=a.date_format,Re.time=a.time_format)}function Qt(a,s){let e=String(a.getDate()).padStart(2,"0"),t=String(a.getMonth()+1).padStart(2,"0"),i=String(a.getFullYear());switch(Re.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($e(s),{day:"2-digit",month:"2-digit",year:"numeric"})}}function ds(a,s){switch(Re.time){case"12":return a.toLocaleTimeString($e(s),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return a.toLocaleTimeString($e(s),{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($e(s),{hour:"2-digit",minute:"2-digit"})}}function W(a,s){if(!a)return"\u2014";try{let e=a.includes("T")?a:a+"T00:00:00";return Qt(new Date(e),s)}catch{return a}}function Zt(a,s){if(!a)return"\u2014";try{let e=new Date(a);return Qt(e,s)+" "+ds(e,s)}catch{return a}}function at(a,s){if(a==null)return"\u2014";let e=s||"en";return a<0?`${Math.abs(a)} ${r("d_overdue",e)}`:a===0?r("today",e):`${a} ${r(a===1?"day":"days",e)}`}function je(a,s,e){return a==null?"\u2014":`${a} ${r("unit_"+(s||"days"),e)}`}function we(a,s,e="long"){return new Date(Date.UTC(2024,0,1+a)).toLocaleDateString($e(s),{weekday:e,timeZone:"UTC"})}function Xt(a,s){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=>we(i,s,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?r("ord_last",s):r("ord_"+e.nth,s)} ${we(e.weekday,s,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?r(e.business?"last_business_day_month":"last_day_month",s):`${r("day_word",s)} ${e.day}`)+t;case"one_time":return a.due_date?W(a.due_date,s):r("one_time",s);case"manual":return r("manual",s);case"interval":return je(e.every,e.unit,s)}return a.schedule_type==="one_time"?a.due_date?W(a.due_date,s):r("one_time",s):a.schedule_type==="manual"?r("manual",s):a.schedule_type==="sensor_based"?r("sensor_based",s):a.interval_days!=null?je(a.interval_days,a.interval_unit,s):"\u2014"}function ei(a,s){a.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:s},bubbles:!0,composed:!0}))}var rt,Yt,se,ns,os,xe,ls,Re,ti,qe,C=$(()=>{"use strict";L();Wt();Gt();rt="en",Yt=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),se=Yt.store;se.en||(se.en=Kt);ns=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),os="/maintenance_supporter_locales",xe=Yt.inflight;ls=window,Re=ls.__msDateTimePrefs??={};ti=E` .field { display: flex; flex-direction: column; gap: 4px; } .field-label { font-size: 12px; color: var(--secondary-text-color); } .field-input { @@ -14,7 +14,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-family: inherit; width: 100%; box-sizing: border-box; } .field-input:focus { outline: none; border-color: var(--primary-color); } -`,ze=E` +`,qe=E` :host { --maint-ok-color: var(--success-color, #4caf50); --maint-due-soon-color: var(--warning-color, #ff9800); @@ -1157,7 +1157,7 @@ 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; } } -`});var ce,nt=w(()=>{"use strict";ce=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestampt.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}}});function D(a){return`${a.entry_id??""}\0${a.part_id}`}function ns(a,s,e,t){let i=!!a.entry_id&&a.entry_id!==s,n=i?a.entry_id:s,c=e.find(v=>v.entry_id===n),d=(c?.parts||[]).find(v=>v.id===a.part_id)||null,u=i&&c?.object?.name||"",_=d?.name||r("shared_part_unknown",t);return{part:d,foreign:i,ownerName:u,label:u?`${_} (${u})`:_}}function ti(a,s,e,t){let n=(e.find(d=>d.entry_id===s)?.parts||[]).map(d=>({...d})),c=new Set(n.map(d=>D({part_id:d.id})));for(let d of a?.consumes_parts||[]){if(!d.entry_id||d.entry_id===s)continue;let u=D(d);if(c.has(u))continue;c.add(u);let{part:_,ownerName:v}=ns(d,s,e,t);n.push({id:d.part_id,name:_?.name||r("shared_part_unknown",t),unit:_?.unit,stock:_?.stock??null,storage_location:_?.storage_location,entry_id:d.entry_id,owner_name:v})}return n}var Me=w(()=>{"use strict";L()});function cs(a,s){let e=ls[a];if(!e)return a;let t=r(e,s);return t&&t!==e?t:a}function ds(a){let e=a.match(/data\['([^']+)'\]/)?.[1],t;return(t=a.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=a.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=a.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=a.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(a)?{field:e,rule:"required"}:(t=a.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(a)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(a)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function P(a,s,e){if(e=e??r("action_error",s),typeof a=="string")return a;if(typeof a!="object"||a===null)return e;let t=a,i=t.message||t.error?.message||"";if(!i)return e;let n=ds(i),c=n.field?cs(n.field,s):"",d=u=>r(u,s).replace("{field}",c).replace("{n}",n.param??"");switch(n.rule){case"too_long":return d("err_too_long");case"too_short":return d("err_too_short");case"value_too_high":return d("err_value_too_high");case"value_too_low":return d("err_value_too_low");case"required":return d("err_required");case"wrong_type":return d("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return d("err_invalid_choice");case"invalid_value":return d("err_invalid_value");default:return i||e}}var ls,re=w(()=>{"use strict";L();ls={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_for_minutes:"trigger_for_minutes"}});var ii,Oe,ot=w(()=>{"use strict";ii=["notes","cost","duration","photo","user"],Oe={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"}});var $,lt=w(()=>{"use strict";I();M();L();re();Me();ot();$=class extends k{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._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._usedParts=Object.fromEntries(this.consumesParts.map(e=>[D(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 c=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:n});if(!c.ok){this._error=c.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let d=await c.json();d.id&&(this._photoDocId=d.id,this._photoPreview=URL.createObjectURL(i))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._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=P(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?o``: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(c=>D({part_id:c.id,entry_id:c.entry_id})===D(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 o` `)} -
`:h} + `:u}
${f.days_until_due!==null&&f.days_until_due!==void 0?f.days_until_due<0?o`${at(f.days_until_due,e)}`:at(f.days_until_due,e):f.trigger_active?"\u26A1":"\u2014"}
@@ -3922,11 +4071,11 @@ ${u?`
${u}
`:""} {y.stopPropagation();let g=this.shadowRoot.querySelector("maintenance-complete-dialog");g.entryId=_,g.taskId=f.id,g.taskName=f.name,g.checklist=f.checklist||[],g.adaptiveEnabled=!!f.adaptive_config?.enabled,g.taskType=f.type||"",g.readingUnit=f.reading_unit||"",g.requiredFields=f.required_completion_fields||[],g.lang=e;let x=!!f.part_ref;g.parts=x?[]:ti(f,_,this._objects,e),g.consumesParts=x?[]:f.consumes_parts||[],g.open()}} + @click=${y=>{y.stopPropagation();let g=this.shadowRoot.querySelector("maintenance-complete-dialog");g.entryId=_,g.taskId=f.id,g.taskName=f.name,g.checklist=f.checklist||[],g.adaptiveEnabled=!!f.adaptive_config?.enabled,g.taskType=f.type||"",g.readingUnit=f.reading_unit||"",g.requiredFields=f.required_completion_fields||[],g.lang=e;let x=!!f.part_ref;g.parts=x?[]:ii(f,_,this._objects,e),g.consumesParts=x?[]:f.consumes_parts||[],g.open()}} > - `:h} + `:u} `)} @@ -3936,7 +4085,7 @@ ${u?`
${u}
`:""} .hass=${this.hass} @task-completed=${this._onCompleted} > - `}};q.styles=[ze,E` + `}};M.styles=[qe,E` ha-card { overflow: hidden; } .card-header { @@ -4068,7 +4217,7 @@ ${u?`
${u}
`:""} --mdc-icon-size: 18px; color: var(--primary-color); } - `],l([b({attribute:!1})],q.prototype,"hass",2),l([p()],q.prototype,"_config",2),l([p()],q.prototype,"_objects",2),l([p()],q.prototype,"_stats",2),l([p()],q.prototype,"_unsub",2),l([p()],q.prototype,"_viewFilters",2),l([p()],q.prototype,"_userNames",2),l([p()],q.prototype,"_taskDocs",2);customElements.get("maintenance-supporter-card")||customElements.define("maintenance-supporter-card",q);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-supporter-card",name:"Maintenance Supporter",description:"Overview of your maintenance tasks with quick actions.",preview:!0});export{q as MaintenanceSupporterCard}; + `],l([b({attribute:!1})],M.prototype,"hass",2),l([p()],M.prototype,"_config",2),l([p()],M.prototype,"_objects",2),l([p()],M.prototype,"_stats",2),l([p()],M.prototype,"_unsub",2),l([p()],M.prototype,"_viewFilters",2),l([p()],M.prototype,"_userNames",2),l([p()],M.prototype,"_taskDocs",2);customElements.get("maintenance-supporter-card")||customElements.define("maintenance-supporter-card",M);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-supporter-card",name:"Maintenance Supporter",description:"Overview of your maintenance tasks with quick actions.",preview:!0});export{M as MaintenanceSupporterCard}; /*! Bundled license information: @lit/reactive-element/css-tag.js: diff --git a/custom_components/maintenance_supporter/frontend/maintenance-panel.js b/custom_components/maintenance_supporter/frontend/maintenance-panel.js index 84acf454..3d63be45 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-panel.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-panel.js @@ -1,5 +1,5 @@ -/*! maintenance_supporter frontend 2.55.0 */ -import"/maintenance_supporter_panelfiles/panel-chunks/chunk-BDAGEP22.js";import{b as Ut,c as re}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4HD7ODUX.js";import{a as E}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XYTY2SBA.js";import{a as se,b as ae,c as ne,d as oe,e as At}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZIQ7JY7R.js";import{a as le}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-6RMRSFSY.js";import{a as g,b as D,c as o,d as P,f as d,g as C,h as ee,i as T,j as m,k as St,l as Mt,m as ut,n as s,o as Et,p as q,q as Dt,r as Y,s as Ct,t as gt,v as xt,w as mt,x as ie,y as Lt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NHOMCQWL.js";function it(n){return!!n&&/^https?:\/\//i.test(n)}var He=["assignee_pool","required_completion_fields","checklist","labels","history"],Fe=["checklist_progress"],Be=["tasks","parts"],Ve=["manual_docs","battery_fleet_excluded"];function Wt(n,r,t=[]){for(let e of r)n[e]===void 0&&(n[e]=[]);for(let e of t)n[e]===void 0&&(n[e]={})}function Ne(n){let r=n;Wt(r,Be),r.object&&typeof r.object=="object"&&Wt(r.object,Ve);for(let t of r.tasks)Wt(t,He,Fe);return n}function $t(n){for(let r of n)Ne(r);return n}function Ue(n,r){if(r.objects)return r.objects;let t=r.delta||[],e=r.removed||[];if(!t.length&&!e.length)return null;let i=new Map(n.map(a=>[a.entry_id,a]));for(let a of t)i.set(a.entry_id,a);for(let a of e)i.delete(a);return[...i.values()]}function ce(n,r){return r.objects&&$t(r.objects),r.delta&&$t(r.delta),Ue(n,r)}var Rt="2.55.0";function de(n,r=Rt){return!n||!r||r==="dev"?!1:n!==r}var F={overviewTab:"msp-overview-tab",collapsedSections:"msp-collapsed-sections",chartRange:"msp-chart-range",chartHideOutliers:"msp-chart-hide-outliers",taskSort:"maintenance_supporter_sort",objectSort:"maintenance_supporter_object_sort",groupBy:"maintenance_supporter_groupby",objectView:"maintenance_supporter_object_view",objectsCache:"msp-objects-cache",gettingStartedDismissed:"msp-gs-dismissed"};var We=168*3600*1e3;function pe(){try{let n=localStorage.getItem(F.objectsCache);if(!n)return null;let r=JSON.parse(n);return r.v!==Rt||!Number.isFinite(r.at)||Date.now()-r.at>We||!Array.isArray(r.objects)||r.objects.length===0?null:{objects:r.objects,stats:r.stats??null}}catch{return null}}function qt(n,r){if(!(!Array.isArray(n)||n.length===0))try{let t={v:Rt,at:Date.now(),objects:n,stats:r};localStorage.setItem(F.objectsCache,JSON.stringify(t))}catch{}}var qe={days:1,weeks:7,months:30.4368,years:365.25};function Yt(n,r){return!n||n<=0?0:n*(qe[r||"days"]??1)}function Ot(n,r,t){let e=Yt(n,t);if(e<=0||r==null)return{pct:0,overflow:!1};let i=(e-r)/e*100;return{pct:Math.max(0,Math.min(100,i)),overflow:i>100}}function A(n){return String(n??"").replace(/[&<>"']/g,r=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[r])}function he(n,r,t,e,i,a){let l=[[t.manufacturer,n.manufacturer],[t.model,n.model],[t.serial,n.serial_number],[t.installed,n.installation_date?e(n.installation_date):null],[t.warranty,n.warranty_expiry?e(n.warranty_expiry):null]].filter(([,p])=>!!p),h=r.map(p=>{let u=t.scheduleLabel(p);return` +/*! maintenance_supporter frontend 2.56.0 */ +import"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZK3W7TF6.js";import{b as Ut,c as re}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-SD6IEJBA.js";import{a as E}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as se,b as ae,c as ne,d as oe,e as At}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DV4UHMJC.js";import{a as le}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-I7J3AORE.js";import{a as g,b as D,c as o,d as P,f as d,g as C,h as ee,i as T,j as m,k as St,l as Mt,m as ut,n as s,o as Et,p as q,q as Dt,r as Y,s as Ct,t as gt,v as xt,w as mt,x as ie,y as Lt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";function it(n){return!!n&&/^https?:\/\//i.test(n)}var He=["assignee_pool","required_completion_fields","checklist","labels","history"],Fe=["checklist_progress"],Be=["tasks","parts"],Ve=["manual_docs","battery_fleet_excluded"];function Wt(n,r,t=[]){for(let e of r)n[e]===void 0&&(n[e]=[]);for(let e of t)n[e]===void 0&&(n[e]={})}function Ne(n){let r=n;Wt(r,Be),r.object&&typeof r.object=="object"&&Wt(r.object,Ve);for(let t of r.tasks)Wt(t,He,Fe);return n}function $t(n){for(let r of n)Ne(r);return n}function Ue(n,r){if(r.objects)return r.objects;let t=r.delta||[],e=r.removed||[];if(!t.length&&!e.length)return null;let i=new Map(n.map(a=>[a.entry_id,a]));for(let a of t)i.set(a.entry_id,a);for(let a of e)i.delete(a);return[...i.values()]}function ce(n,r){return r.objects&&$t(r.objects),r.delta&&$t(r.delta),Ue(n,r)}var Rt="2.56.0";function de(n,r=Rt){return!n||!r||r==="dev"?!1:n!==r}var F={overviewTab:"msp-overview-tab",collapsedSections:"msp-collapsed-sections",chartRange:"msp-chart-range",chartHideOutliers:"msp-chart-hide-outliers",taskSort:"maintenance_supporter_sort",objectSort:"maintenance_supporter_object_sort",groupBy:"maintenance_supporter_groupby",objectView:"maintenance_supporter_object_view",objectsCache:"msp-objects-cache",gettingStartedDismissed:"msp-gs-dismissed"};var We=168*3600*1e3;function pe(){try{let n=localStorage.getItem(F.objectsCache);if(!n)return null;let r=JSON.parse(n);return r.v!==Rt||!Number.isFinite(r.at)||Date.now()-r.at>We||!Array.isArray(r.objects)||r.objects.length===0?null:{objects:r.objects,stats:r.stats??null}}catch{return null}}function qt(n,r){if(!(!Array.isArray(n)||n.length===0))try{let t={v:Rt,at:Date.now(),objects:n,stats:r};localStorage.setItem(F.objectsCache,JSON.stringify(t))}catch{}}var qe={days:1,weeks:7,months:30.4368,years:365.25};function Yt(n,r){return!n||n<=0?0:n*(qe[r||"days"]??1)}function Ot(n,r,t){let e=Yt(n,t);if(e<=0||r==null)return{pct:0,overflow:!1};let i=(e-r)/e*100;return{pct:Math.max(0,Math.min(100,i)),overflow:i>100}}function A(n){return String(n??"").replace(/[&<>"']/g,r=>({"&":"&","<":"<",">":">",'"':""","'":"'"})[r])}function he(n,r,t,e,i,a){let l=[[t.manufacturer,n.manufacturer],[t.model,n.model],[t.serial,n.serial_number],[t.installed,n.installation_date?e(n.installation_date):null],[t.warranty,n.warranty_expiry?e(n.warranty_expiry):null]].filter(([,p])=>!!p),h=r.map(p=>{let u=t.scheduleLabel(p);return` ${A(p.name)} ${A(t.typeLabel(p.type))} ${A(t.statusLabel(p.status))} @@ -4556,7 +4556,7 @@ ${A(n.notes)}`:""} .canWrite=${!r.isOperator} > - `}var jt=class extends C{createRenderRoot(){return this}render(){return!this.task||!this.ctx?d:o`${ze(this.task,this.ctx)}`}};g([T({attribute:!1})],jt.prototype,"task",2),g([T({attribute:!1})],jt.prototype,"ctx",2);customElements.get("maintenance-task-detail-view")||customElements.define("maintenance-task-detail-view",jt);function Ie(n){if(n.total<=0)return{start:0,end:0,padTop:0,padBottom:0};let r=n.overscan??12,t=Math.max(1,n.step??6),e=Math.max(1,n.rowHeight),i=Math.floor((n.scrollTop-n.listTop)/e),a=Math.ceil(n.viewportHeight/e)+1,l=Math.max(0,i-r);l=Math.floor(l/t)*t;let h=Math.min(n.total,Math.max(i,0)+a+r);return h=Math.min(n.total,Math.ceil(h/t)*t),l>=h&&(l=Math.min(l,Math.max(0,n.total-1)),h=Math.min(n.total,l+Math.max(a,1))),{start:l,end:h,padTop:l*e,padBottom:(n.total-h)*e}}var k=class extends C{constructor(){super(...arguments);this.narrow=!1;this.panel={};this._objects=[];this._stats=null;this._view="overview";this._selectedEntryId=null;this._selectedTaskId=null;this._filterStatus="";this._filterUser=null;this._filterLabel=null;this._savedViews=[];this._activeViewId="";this._unsub=null;this._chartRangeDays=(()=>{try{let t=parseInt(localStorage.getItem(F.chartRange)||"",10);return[7,30,90,365].includes(t)?t:30}catch{return 30}})();this._hideOutliers=(()=>{try{return localStorage.getItem(F.chartHideOutliers)==="1"}catch{return!1}})();this._historyFilter=null;this._budget=null;this._groups={};this._detailStatsData=new Map;this._miniStatsData=new Map;this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._adminPanelUserIds=[];this._operatorWriteEnabled=!1;this._defaultWarningDays=7;this._actionLoading=!1;this._moreMenuOpen=!1;this._objMenuOpen=!1;this._toastMessage="";this._toastUndo=null;this._toastActionLabel="";this._filtersOpen=!1;this._newMenuOpen=!1;this._gsSetupsCount=0;this._gsAdoptCount=0;this._gsLoaded=!1;this._batteryFleetSetupAvailable=!1;this._staleBundle=!1;this._staleChecked=!1;this._toastTimer=null;this._dismissedSuggestions=new Set;this._overviewTab=(()=>{try{let t=localStorage.getItem(F.overviewTab);return t==="today"||t==="calendar"?t:"dashboard"}catch{return"dashboard"}})();this._activeTab="overview";this._costDurationToggle="both";this._historySearch="";this._sortMode="due_date";this._objectSortMode="alphabetical";this._groupByMode="none";this._objectViewMode="cards";this._objectsTableColumns=ae;this._showArchived=!1;this._bulkMode=!1;this._bulkSelected=new Set;this._virtStart=0;this._virtEnd=0;this._virtRowHeight=53;this._virtTotalRows=0;this._virtScrollAttached=!1;this._virtRaf=0;this._collapsedSections=(()=>{try{return new Set(JSON.parse(localStorage.getItem(F.collapsedSections)||"[]"))}catch{return new Set}})();this._paletteOpen=!1;this._paletteQuery="";this._paletteActive=0;this._templateGalleryOpen=!1;this._templates=[];this._templateCategories={};this._templateBusy=!1;this._statsService=null;this._userService=null;this._dataLoaded=!1;this._lastConnection=null;this._popstateHandler=t=>this._onPopState(t);this._lazyUi=null;this._onVirtualScroll=()=>{this._virtRaf||(this._virtRaf=requestAnimationFrame(()=>{this._virtRaf=0,this._updateVirtualWindow()}))};this._deepLinkHandled=!1;this._paletteKeydown=t=>{if(t.key==="/"&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&!this._paletteOpen){let i=t.composedPath()[0];if(i instanceof HTMLElement&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA"||i.tagName==="SELECT"||i.isContentEditable))return;t.preventDefault(),this._openPalette();return}if(!this._paletteOpen)return;let e=this._paletteResults;if(t.key==="Escape")t.preventDefault(),this._closePalette();else if(t.key==="ArrowDown")t.preventDefault(),this._paletteActive=Math.min(this._paletteActive+1,e.length-1);else if(t.key==="ArrowUp")t.preventDefault(),this._paletteActive=Math.max(this._paletteActive-1,0);else if(t.key==="Enter"){t.preventDefault();let i=e[this._paletteActive];i&&this._selectPaletteResult(i)}};this._onDialogEvent=async()=>{try{await this._loadData()}catch{}};this._onCalendarLlCustom=t=>{let e=t.detail;e?.type==="maintenance-supporter:open-task"&&e.entry_id&&e.task_id&&(t.stopPropagation(),this._showTask(e.entry_id,e.task_id))};this._fullHistory=null;this._onHistoryEntrySaved=async()=>{await this._loadData()}}get _lang(){return this.hass?.language||"en"}get _isOperator(){let t=this.hass?.user;return t?t.is_admin?!1:!(this._operatorWriteEnabled&&this._adminPanelUserIds.includes(t.id)):!0}_ensureLazyUi(){return this._lazyUi||(this._lazyUi=Promise.all([import("/maintenance_supporter_panelfiles/panel-chunks/object-dialog-YRX6AWLQ.js"),import("/maintenance_supporter_panelfiles/panel-chunks/task-dialog-FU3K4IJH.js"),import("/maintenance_supporter_panelfiles/panel-chunks/complete-dialog-DYCBJTZV.js"),import("/maintenance_supporter_panelfiles/panel-chunks/qr-dialog-ONGEWKLY.js"),import("/maintenance_supporter_panelfiles/panel-chunks/adopt-problem-sensors-dialog-2KGMOVVK.js"),import("/maintenance_supporter_panelfiles/panel-chunks/suggested-setups-dialog-2MBPABZN.js"),import("/maintenance_supporter_panelfiles/panel-chunks/settings-view-IR7NAWZ4.js")]).then(()=>this.updateComplete)),this._lazyUi}async _ui(t){return await this._ensureLazyUi(),this.shadowRoot?.querySelector(t)??null}connectedCallback(){super.connectedCallback();let t=window.requestIdleCallback,e=()=>this._ensureLazyUi();t?t(e,{timeout:3e3}):window.setTimeout(e,1500),window.addEventListener("popstate",this._popstateHandler),window.addEventListener("keydown",this._paletteKeydown),window.addEventListener("resize",this._onVirtualScroll,{passive:!0});try{let i=localStorage.getItem(F.taskSort);i&&["due_date","object","type","task_name","area","assigned_user","group"].includes(i)&&(this._sortMode=i);let a=localStorage.getItem(F.objectSort);a&&["alphabetical","due_soonest","task_count"].includes(a)&&(this._objectSortMode=a);let l=localStorage.getItem(F.groupBy);l&&["none","area","group","user"].includes(l)&&(this._groupByMode=l);let h=localStorage.getItem(F.objectView);(h==="cards"||h==="table")&&(this._objectViewMode=h)}catch{}if(this._objects.length===0){let i=pe();i&&(this._objects=i.objects,i.stats&&(this._stats=i.stats))}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("popstate",this._popstateHandler),window.removeEventListener("keydown",this._paletteKeydown),window.removeEventListener("resize",this._onVirtualScroll),this.shadowRoot?.querySelector(".content")?.removeEventListener("scroll",this._onVirtualScroll),this._virtScrollAttached=!1,this._virtRaf&&cancelAnimationFrame(this._virtRaf),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._lastConnection=null,this._deepLinkHandled=!1,this._statsService?.clearCache(),this._statsService=null}updated(t){super.updated(t),t.has("hass")&&Dt(this.hass?.locale);let e=this.hass?.language;if(e&&!Et(e)&&q(e).then(()=>this.requestUpdate()),t.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,history.replaceState({msp_view:"overview",msp_entry:null,msp_task:null},""),this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}this._statsService?this._statsService.updateHass(this.hass):(this._statsService=new Pt(this.hass),this._fetchMiniStatsForOverview()),this._userService?this._userService.updateHass(this.hass):(this._userService=new le(this.hass),this._userService.getUsers())}let i=this.shadowRoot?.querySelector(".content");i&&!this._virtScrollAttached&&(i.addEventListener("scroll",this._onVirtualScroll,{passive:!0}),this._virtScrollAttached=!0),this._updateVirtualWindow()}_updateVirtualWindow(){let t=this.shadowRoot?.querySelector(".content"),e=this.shadowRoot?.querySelector(".task-table.virtual");if(!t||!e)return;let i=e.querySelector(".task-row:not(.virt-sizer)");i&&i.offsetHeight>20&&(this._virtRowHeight=i.offsetHeight);let a=e.getBoundingClientRect().top-t.getBoundingClientRect().top+t.scrollTop,l=Ie({scrollTop:t.scrollTop,viewportHeight:t.clientHeight,listTop:a,rowHeight:this._virtRowHeight,total:this._virtTotalRows});(l.start!==this._virtStart||l.end!==this._virtEnd)&&(this._virtStart=l.start,this._virtEnd=l.end)}async _loadData(){let[t,e,i,a,l,h]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"}).catch(()=>null)]);if(h&&(this._savedViews=h.views||[]),t&&(this._objects=$t(t.objects),qt(this._objects,e??this._stats??null),this._maybeLoadGettingStarted()),this._view==="task"&&this._selectedEntryId&&this._selectedTaskId&&this._fetchFullHistory(this._selectedEntryId,this._selectedTaskId),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/status"}).then(c=>{this._batteryFleetSetupAvailable=!!c.available&&!c.configured}).catch(()=>{this._batteryFleetSetupAvailable=!1}),this._staleChecked||(this._staleChecked=!0,this.hass.connection.sendMessagePromise({type:"maintenance_supporter/version"}).then(c=>{this._staleBundle=de(c?.version)}).catch(()=>{})),e&&(this._stats=e),i&&(this._budget=i),a&&(this._groups=a.groups||{}),l){let c=l;this._features=c.features,this._adminPanelUserIds=c.admin_panel_user_ids||[],this._operatorWriteEnabled=c.operator_write_enabled??!1;let p=c.general?.default_warning_days;typeof p=="number"&&p>=0&&p<=365&&(this._defaultWarningDays=p),this._objectsTableColumns=ne(c.objects_table_columns)}this._fetchMiniStatsForOverview(),this._handleDeepLink()}_handleDeepLink(){if(this._deepLinkHandled)return;let t=new URLSearchParams(window.location.search),e=t.get("ms_action"),i=()=>{let u=window.location.pathname+window.location.hash;history.replaceState(history.state,"",u)};if(e==="add_object"){this._deepLinkHandled=!0,i(),this._ui("maintenance-object-dialog").then(u=>u?.openCreate());return}if(e==="open_vacation"||e==="open_budget"||e==="open_groups"||e==="open_settings"){this._deepLinkHandled=!0,i(),this._overviewTab="settings",this._ensureLazyUi().then(()=>requestAnimationFrame(()=>{let u=this.shadowRoot?.querySelector("maintenance-settings-view"),_=e.replace("open_","");u?.scrollToSection?.(_)}));return}let a=t.get("entry_id");if(!a)return;this._deepLinkHandled=!0;let l=t.get("task_id"),h=t.get("action"),c=window.location.pathname+window.location.hash;history.replaceState(history.state,"",c);let p=this._getObject(a);if(!p){this._showOverview();return}if(l){let u=p.tasks.find(_=>_.id===l);if(!u){this._showObject(a);return}this._showTask(a,l),h==="complete"?requestAnimationFrame(()=>{this._openCompleteDialog(a,l,u.name,this._features.checklists?u.checklist:void 0,this._features.adaptive&&!!u.adaptive_config?.enabled)}):h==="quick_complete"&&requestAnimationFrame(()=>{this._handleQuickComplete(a,l,u)})}else this._showObject(a)}_isCounterEntity(t){if(!t)return!1;let e=t.type||"threshold";return e==="counter"||e==="state_change"}async _fetchDetailStats(t,e){if(!this._statsService)return;let i=await this._statsService.getDetailStats(t,e,this._chartRangeDays),a=new Map(this._detailStatsData);a.set(t,i),this._detailStatsData=a}_setChartRange(t){if(t===this._chartRangeDays)return;this._chartRangeDays=t;try{localStorage.setItem(F.chartRange,String(t))}catch{}let e=this._selectedEntryId&&this._selectedTaskId?this._getTask(this._selectedEntryId,this._selectedTaskId):null,i=e?.trigger_config?.entity_id;if(i){let a=new Map(this._detailStatsData);a.delete(i),this._detailStatsData=a,this._fetchDetailStats(i,this._isCounterEntity(e.trigger_config))}}_setHideOutliers(t){if(t!==this._hideOutliers){this._hideOutliers=t;try{localStorage.setItem(F.chartHideOutliers,t?"1":"0")}catch{}}}async _fetchMiniStatsForOverview(){if(!this._statsService)return;let t=[];for(let i of this._objects)for(let a of i.tasks){let l=a.trigger_config?.entity_id;l&&t.push({entityId:l,isCounter:this._isCounterEntity(a.trigger_config)})}if(t.length===0)return;let e=await this._statsService.getBatchMiniStats(t);this._miniStatsData=new Map([...this._miniStatsData,...e])}async _subscribe(){try{let t=await this.hass.connection.subscribeMessage(e=>{let i=e,a=ce(this._objects,i);a!==null&&(this._objects=a,e.objects&&qt(a,this._stats??null))},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){t();return}this._unsub=t}catch{}}get _taskRows(){let t=[];for(let _ of this._objects)for(let v of _.tasks){if(!this._showArchived&&v.archived||this._filterStatus&&v.status!==this._filterStatus)continue;if(this._filterUser){let x=this._filterUser==="current_user"?this._userService?.getCurrentUserId():this._filterUser;if(v.responsible_user_id!==x)continue}if(this._filterLabel&&!(v.labels||[]).includes(this._filterLabel))continue;let b=[];for(let x of Object.values(this._groups))x.task_refs?.some(f=>f.entry_id===_.entry_id&&f.task_id===v.id)&&b.push(x.name);t.push({entry_id:_.entry_id,task_id:v.id,object_name:_.object.name,task_name:v.name,type:v.type,schedule_type:v.schedule_type,status:v.status,days_until_due:v.days_until_due??null,next_due:v.next_due??null,trigger_active:v.trigger_active,trigger_current_value:v.trigger_current_value??null,trigger_current_delta:v.trigger_current_delta??null,trigger_config:v.trigger_config??null,trigger_entity_info:v.trigger_entity_info??null,times_performed:v.times_performed,total_cost:v.total_cost,interval_days:v.interval_days??null,interval_unit:v.interval_unit??null,interval_anchor:v.interval_anchor??null,is_done:v.is_done??!1,archived:v.archived??!1,history:v.history||[],enabled:v.enabled,nfc_tag_id:v.nfc_tag_id??null,priority:v.priority??"normal",labels:v.labels??[],area_id:_.object.area_id??null,responsible_user_id:v.responsible_user_id??null,group_names:b})}let e={overdue:0,triggered:1,due_soon:2,ok:3},i=(_,v)=>(e[_.status]??9)-(e[v.status]??9),a=(_,v)=>(_.days_until_due??99999)-(v.days_until_due??99999),l=(_,v)=>i(_,v)||a(_,v),h=_=>_.area_id&&this.hass?.areas?.[_.area_id]?.name||"",c=_=>_.responsible_user_id&&this._userService?.getUserName(_.responsible_user_id)||"",p=_=>_.group_names[0]||"",u={due_date:l,object:(_,v)=>_.object_name.localeCompare(v.object_name)||l(_,v),type:(_,v)=>_.type.localeCompare(v.type)||l(_,v),task_name:(_,v)=>_.task_name.localeCompare(v.task_name),area:(_,v)=>{let b=h(_),x=h(v);return!b&&x?1:b&&!x?-1:b.localeCompare(x)||l(_,v)},assigned_user:(_,v)=>{let b=c(_),x=c(v);return!b&&x?1:b&&!x?-1:b.localeCompare(x)||l(_,v)},group:(_,v)=>{let b=p(_),x=p(v);return!b&&x?1:b&&!x?-1:b.localeCompare(x)||l(_,v)}};return t.sort(u[this._sortMode]),t}_getObject(t){return this._objects.find(e=>e.entry_id===t)}_getTask(t,e){return this._getObject(t)?.tasks.find(a=>a.id===e)}_pushPanelState(t,e,i){let a={msp_view:t,msp_entry:e||null,msp_task:i||null};history.pushState(a,"")}_onPopState(t){let e=t.state;if(e?.msp_view&&(this._view=e.msp_view,this._selectedEntryId=e.msp_entry||null,this._selectedTaskId=e.msp_task||null,this._moreMenuOpen=!1,e.msp_view==="task"&&e.msp_entry&&e.msp_task)){this._historyFilter=null;let i=this._getTask(e.msp_entry,e.msp_task);i?.trigger_config?.entity_id&&this._fetchDetailStats(i.trigger_config.entity_id,this._isCounterEntity(i.trigger_config))}}_showOverview(){this._pushPanelState("overview"),this._view="overview",this._selectedEntryId=null,this._selectedTaskId=null,this._moreMenuOpen=!1,this._scrollContentToTop()}_showAllObjects(){this._pushPanelState("all_objects"),this._view="all_objects",this._selectedEntryId=null,this._selectedTaskId=null,this._scrollContentToTop()}_filterByStatus(t){this._filterStatus=t,this._activeViewId="",this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard"),this._scrollContentToTop()}get _allLabels(){let t=new Set;for(let e of this._objects)for(let i of e.tasks)for(let a of i.labels||[])t.add(a);return[...t].sort((e,i)=>e.localeCompare(i))}get _currentFilters(){return{status:this._filterStatus,user_id:this._filterUser,label:this._filterLabel,archived:this._showArchived,sort_mode:this._sortMode,group_by:this._groupByMode}}_applyView(t){if(this._activeViewId=t,!t)return;let e=this._savedViews.find(a=>a.id===t);if(!e)return;let i=e.filters;this._filterStatus=i.status||"",this._filterUser=i.user_id||null,this._filterLabel=i.label||null,this._showArchived=!!i.archived,["due_date","object","type","task_name","area","assigned_user","group"].includes(i.sort_mode)&&(this._sortMode=i.sort_mode),["none","area","group","user"].includes(i.group_by)&&(this._groupByMode=i.group_by);try{localStorage.setItem(F.taskSort,this._sortMode),localStorage.setItem(F.groupBy,this._groupByMode)}catch{}this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard")}_openSavedViewsDialog(){this.shadowRoot.querySelector("maintenance-saved-views-dialog")?.open(this._currentFilters,this._savedViews)}_onSavedViewsChanged(t){this._savedViews=t.detail.views||[],this._activeViewId&&!this._savedViews.some(e=>e.id===this._activeViewId)&&(this._activeViewId="")}_scrollContentToTop(){requestAnimationFrame(()=>{let t=this.shadowRoot?.querySelector(".content");t&&t.scrollTo({top:0,behavior:"smooth"})})}_showObject(t){this._pushPanelState("object",t),this._view="object",this._selectedEntryId=t,this._selectedTaskId=null,this._scrollContentToTop()}_showTask(t,e){this._pushPanelState("task",t,e),this._view="task",this._selectedEntryId=t,this._selectedTaskId=e,this._activeTab="overview",this._historyFilter=null,this._scrollContentToTop(),this._fetchFullHistory(t,e);let i=this._getTask(t,e);if(i?.trigger_config?.entity_id){let a=i.trigger_config.entity_id,l=this._isCounterEntity(i.trigger_config);this._fetchDetailStats(a,l)}}_showToast(t){this._toastTimer&&clearTimeout(this._toastTimer),this._toastUndo=null,this._toastActionLabel="",this._toastMessage=t,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastTimer=null},4e3)}_showActionToast(t,e,i){this._showUndoToast(t,i),this._toastActionLabel=e}_showUndoToast(t,e){this._toastTimer&&clearTimeout(this._toastTimer),this._toastActionLabel="",this._toastMessage=t,this._toastUndo=e,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastUndo=null,this._toastTimer=null},7e3)}_runToastUndo(){let t=this._toastUndo;this._toastTimer&&clearTimeout(this._toastTimer),this._toastMessage="",this._toastUndo=null,this._toastTimer=null,t?.()}_openPalette(){this._paletteQuery="",this._paletteActive=0,this._paletteOpen=!0,this.updateComplete.then(()=>{this.shadowRoot?.querySelector(".palette-input")?.focus()})}_closePalette(){this._paletteOpen=!1,this._paletteQuery=""}get _paletteResults(){let t=this._paletteQuery.trim().toLowerCase(),e=[];for(let i of this._objects){let a=i.object.name||"";(!t||a.toLowerCase().includes(t))&&e.push({kind:"object",entryId:i.entry_id,label:a,sub:s("object",this._lang)});for(let l of i.tasks){if(l.archived)continue;let h=l.name||"",c=(l.labels||[]).some(p=>p.toLowerCase().includes(t));if(!t||h.toLowerCase().includes(t)||a.toLowerCase().includes(t)||c){let p=(l.labels||[]).length?` #${(l.labels||[]).join(" #")}`:"";e.push({kind:"task",entryId:i.entry_id,taskId:l.id,label:h,sub:a+p})}}if(e.length>60)break}return e.slice(0,40)}_selectPaletteResult(t){this._closePalette(),t.kind==="task"&&t.taskId?this._showTask(t.entryId,t.taskId):this._showObject(t.entryId)}_renderPalette(){if(!this._paletteOpen)return d;let t=this._lang,e=this._paletteResults;return o` + `}var jt=class extends C{createRenderRoot(){return this}render(){return!this.task||!this.ctx?d:o`${ze(this.task,this.ctx)}`}};g([T({attribute:!1})],jt.prototype,"task",2),g([T({attribute:!1})],jt.prototype,"ctx",2);customElements.get("maintenance-task-detail-view")||customElements.define("maintenance-task-detail-view",jt);function Ie(n){if(n.total<=0)return{start:0,end:0,padTop:0,padBottom:0};let r=n.overscan??12,t=Math.max(1,n.step??6),e=Math.max(1,n.rowHeight),i=Math.floor((n.scrollTop-n.listTop)/e),a=Math.ceil(n.viewportHeight/e)+1,l=Math.max(0,i-r);l=Math.floor(l/t)*t;let h=Math.min(n.total,Math.max(i,0)+a+r);return h=Math.min(n.total,Math.ceil(h/t)*t),l>=h&&(l=Math.min(l,Math.max(0,n.total-1)),h=Math.min(n.total,l+Math.max(a,1))),{start:l,end:h,padTop:l*e,padBottom:(n.total-h)*e}}var k=class extends C{constructor(){super(...arguments);this.narrow=!1;this.panel={};this._objects=[];this._stats=null;this._view="overview";this._selectedEntryId=null;this._selectedTaskId=null;this._filterStatus="";this._filterUser=null;this._filterLabel=null;this._savedViews=[];this._activeViewId="";this._unsub=null;this._chartRangeDays=(()=>{try{let t=parseInt(localStorage.getItem(F.chartRange)||"",10);return[7,30,90,365].includes(t)?t:30}catch{return 30}})();this._hideOutliers=(()=>{try{return localStorage.getItem(F.chartHideOutliers)==="1"}catch{return!1}})();this._historyFilter=null;this._budget=null;this._groups={};this._detailStatsData=new Map;this._miniStatsData=new Map;this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._adminPanelUserIds=[];this._operatorWriteEnabled=!1;this._defaultWarningDays=7;this._actionLoading=!1;this._moreMenuOpen=!1;this._objMenuOpen=!1;this._toastMessage="";this._toastUndo=null;this._toastActionLabel="";this._filtersOpen=!1;this._newMenuOpen=!1;this._gsSetupsCount=0;this._gsAdoptCount=0;this._gsLoaded=!1;this._batteryFleetSetupAvailable=!1;this._staleBundle=!1;this._staleChecked=!1;this._toastTimer=null;this._dismissedSuggestions=new Set;this._overviewTab=(()=>{try{let t=localStorage.getItem(F.overviewTab);return t==="today"||t==="calendar"?t:"dashboard"}catch{return"dashboard"}})();this._activeTab="overview";this._costDurationToggle="both";this._historySearch="";this._sortMode="due_date";this._objectSortMode="alphabetical";this._groupByMode="none";this._objectViewMode="cards";this._objectsTableColumns=ae;this._showArchived=!1;this._bulkMode=!1;this._bulkSelected=new Set;this._virtStart=0;this._virtEnd=0;this._virtRowHeight=53;this._virtTotalRows=0;this._virtScrollAttached=!1;this._virtRaf=0;this._collapsedSections=(()=>{try{return new Set(JSON.parse(localStorage.getItem(F.collapsedSections)||"[]"))}catch{return new Set}})();this._paletteOpen=!1;this._paletteQuery="";this._paletteActive=0;this._templateGalleryOpen=!1;this._templates=[];this._templateCategories={};this._templateBusy=!1;this._statsService=null;this._userService=null;this._dataLoaded=!1;this._lastConnection=null;this._popstateHandler=t=>this._onPopState(t);this._lazyUi=null;this._onVirtualScroll=()=>{this._virtRaf||(this._virtRaf=requestAnimationFrame(()=>{this._virtRaf=0,this._updateVirtualWindow()}))};this._deepLinkHandled=!1;this._paletteKeydown=t=>{if(t.key==="/"&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&!this._paletteOpen){let i=t.composedPath()[0];if(i instanceof HTMLElement&&(i.tagName==="INPUT"||i.tagName==="TEXTAREA"||i.tagName==="SELECT"||i.isContentEditable))return;t.preventDefault(),this._openPalette();return}if(!this._paletteOpen)return;let e=this._paletteResults;if(t.key==="Escape")t.preventDefault(),this._closePalette();else if(t.key==="ArrowDown")t.preventDefault(),this._paletteActive=Math.min(this._paletteActive+1,e.length-1);else if(t.key==="ArrowUp")t.preventDefault(),this._paletteActive=Math.max(this._paletteActive-1,0);else if(t.key==="Enter"){t.preventDefault();let i=e[this._paletteActive];i&&this._selectPaletteResult(i)}};this._onDialogEvent=async()=>{try{await this._loadData()}catch{}};this._onCalendarLlCustom=t=>{let e=t.detail;e?.type==="maintenance-supporter:open-task"&&e.entry_id&&e.task_id&&(t.stopPropagation(),this._showTask(e.entry_id,e.task_id))};this._fullHistory=null;this._onHistoryEntrySaved=async()=>{await this._loadData()}}get _lang(){return this.hass?.language||"en"}get _isOperator(){let t=this.hass?.user;return t?t.is_admin?!1:!(this._operatorWriteEnabled&&this._adminPanelUserIds.includes(t.id)):!0}_ensureLazyUi(){return this._lazyUi||(this._lazyUi=Promise.all([import("/maintenance_supporter_panelfiles/panel-chunks/object-dialog-HNU4YSB4.js"),import("/maintenance_supporter_panelfiles/panel-chunks/task-dialog-FGXLL4GU.js"),import("/maintenance_supporter_panelfiles/panel-chunks/complete-dialog-AN3A2ME6.js"),import("/maintenance_supporter_panelfiles/panel-chunks/qr-dialog-QWUERDTM.js"),import("/maintenance_supporter_panelfiles/panel-chunks/adopt-problem-sensors-dialog-UBDFZUSU.js"),import("/maintenance_supporter_panelfiles/panel-chunks/suggested-setups-dialog-7XGBTDCC.js"),import("/maintenance_supporter_panelfiles/panel-chunks/settings-view-2CBM3PKW.js")]).then(()=>this.updateComplete)),this._lazyUi}async _ui(t){return await this._ensureLazyUi(),this.shadowRoot?.querySelector(t)??null}connectedCallback(){super.connectedCallback();let t=window.requestIdleCallback,e=()=>this._ensureLazyUi();t?t(e,{timeout:3e3}):window.setTimeout(e,1500),window.addEventListener("popstate",this._popstateHandler),window.addEventListener("keydown",this._paletteKeydown),window.addEventListener("resize",this._onVirtualScroll,{passive:!0});try{let i=localStorage.getItem(F.taskSort);i&&["due_date","object","type","task_name","area","assigned_user","group"].includes(i)&&(this._sortMode=i);let a=localStorage.getItem(F.objectSort);a&&["alphabetical","due_soonest","task_count"].includes(a)&&(this._objectSortMode=a);let l=localStorage.getItem(F.groupBy);l&&["none","area","group","user"].includes(l)&&(this._groupByMode=l);let h=localStorage.getItem(F.objectView);(h==="cards"||h==="table")&&(this._objectViewMode=h)}catch{}if(this._objects.length===0){let i=pe();i&&(this._objects=i.objects,i.stats&&(this._stats=i.stats))}}disconnectedCallback(){super.disconnectedCallback(),window.removeEventListener("popstate",this._popstateHandler),window.removeEventListener("keydown",this._paletteKeydown),window.removeEventListener("resize",this._onVirtualScroll),this.shadowRoot?.querySelector(".content")?.removeEventListener("scroll",this._onVirtualScroll),this._virtScrollAttached=!1,this._virtRaf&&cancelAnimationFrame(this._virtRaf),this._unsub&&(this._unsub(),this._unsub=null),this._dataLoaded=!1,this._lastConnection=null,this._deepLinkHandled=!1,this._statsService?.clearCache(),this._statsService=null}updated(t){super.updated(t),t.has("hass")&&Dt(this.hass?.locale);let e=this.hass?.language;if(e&&!Et(e)&&q(e).then(()=>this.requestUpdate()),t.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,history.replaceState({msp_view:"overview",msp_entry:null,msp_task:null},""),this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}this._statsService?this._statsService.updateHass(this.hass):(this._statsService=new Pt(this.hass),this._fetchMiniStatsForOverview()),this._userService?this._userService.updateHass(this.hass):(this._userService=new le(this.hass),this._userService.getUsers())}let i=this.shadowRoot?.querySelector(".content");i&&!this._virtScrollAttached&&(i.addEventListener("scroll",this._onVirtualScroll,{passive:!0}),this._virtScrollAttached=!0),this._updateVirtualWindow()}_updateVirtualWindow(){let t=this.shadowRoot?.querySelector(".content"),e=this.shadowRoot?.querySelector(".task-table.virtual");if(!t||!e)return;let i=e.querySelector(".task-row:not(.virt-sizer)");i&&i.offsetHeight>20&&(this._virtRowHeight=i.offsetHeight);let a=e.getBoundingClientRect().top-t.getBoundingClientRect().top+t.scrollTop,l=Ie({scrollTop:t.scrollTop,viewportHeight:t.clientHeight,listTop:a,rowHeight:this._virtRowHeight,total:this._virtTotalRows});(l.start!==this._virtStart||l.end!==this._virtEnd)&&(this._virtStart=l.start,this._virtEnd=l.end)}async _loadData(){let[t,e,i,a,l,h]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).catch(()=>null),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/views/list"}).catch(()=>null)]);if(h&&(this._savedViews=h.views||[]),t&&(this._objects=$t(t.objects),qt(this._objects,e??this._stats??null),this._maybeLoadGettingStarted()),this._view==="task"&&this._selectedEntryId&&this._selectedTaskId&&this._fetchFullHistory(this._selectedEntryId,this._selectedTaskId),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/battery_fleet/status"}).then(c=>{this._batteryFleetSetupAvailable=!!c.available&&!c.configured}).catch(()=>{this._batteryFleetSetupAvailable=!1}),this._staleChecked||(this._staleChecked=!0,this.hass.connection.sendMessagePromise({type:"maintenance_supporter/version"}).then(c=>{this._staleBundle=de(c?.version)}).catch(()=>{})),e&&(this._stats=e),i&&(this._budget=i),a&&(this._groups=a.groups||{}),l){let c=l;this._features=c.features,this._adminPanelUserIds=c.admin_panel_user_ids||[],this._operatorWriteEnabled=c.operator_write_enabled??!1;let p=c.general?.default_warning_days;typeof p=="number"&&p>=0&&p<=365&&(this._defaultWarningDays=p),this._objectsTableColumns=ne(c.objects_table_columns)}this._fetchMiniStatsForOverview(),this._handleDeepLink()}_handleDeepLink(){if(this._deepLinkHandled)return;let t=new URLSearchParams(window.location.search),e=t.get("ms_action"),i=()=>{let u=window.location.pathname+window.location.hash;history.replaceState(history.state,"",u)};if(e==="add_object"){this._deepLinkHandled=!0,i(),this._ui("maintenance-object-dialog").then(u=>u?.openCreate());return}if(e==="open_vacation"||e==="open_budget"||e==="open_groups"||e==="open_settings"){this._deepLinkHandled=!0,i(),this._overviewTab="settings",this._ensureLazyUi().then(()=>requestAnimationFrame(()=>{let u=this.shadowRoot?.querySelector("maintenance-settings-view"),_=e.replace("open_","");u?.scrollToSection?.(_)}));return}let a=t.get("entry_id");if(!a)return;this._deepLinkHandled=!0;let l=t.get("task_id"),h=t.get("action"),c=window.location.pathname+window.location.hash;history.replaceState(history.state,"",c);let p=this._getObject(a);if(!p){this._showOverview();return}if(l){let u=p.tasks.find(_=>_.id===l);if(!u){this._showObject(a);return}this._showTask(a,l),h==="complete"?requestAnimationFrame(()=>{this._openCompleteDialog(a,l,u.name,this._features.checklists?u.checklist:void 0,this._features.adaptive&&!!u.adaptive_config?.enabled)}):h==="quick_complete"&&requestAnimationFrame(()=>{this._handleQuickComplete(a,l,u)})}else this._showObject(a)}_isCounterEntity(t){if(!t)return!1;let e=t.type||"threshold";return e==="counter"||e==="state_change"}async _fetchDetailStats(t,e){if(!this._statsService)return;let i=await this._statsService.getDetailStats(t,e,this._chartRangeDays),a=new Map(this._detailStatsData);a.set(t,i),this._detailStatsData=a}_setChartRange(t){if(t===this._chartRangeDays)return;this._chartRangeDays=t;try{localStorage.setItem(F.chartRange,String(t))}catch{}let e=this._selectedEntryId&&this._selectedTaskId?this._getTask(this._selectedEntryId,this._selectedTaskId):null,i=e?.trigger_config?.entity_id;if(i){let a=new Map(this._detailStatsData);a.delete(i),this._detailStatsData=a,this._fetchDetailStats(i,this._isCounterEntity(e.trigger_config))}}_setHideOutliers(t){if(t!==this._hideOutliers){this._hideOutliers=t;try{localStorage.setItem(F.chartHideOutliers,t?"1":"0")}catch{}}}async _fetchMiniStatsForOverview(){if(!this._statsService)return;let t=[];for(let i of this._objects)for(let a of i.tasks){let l=a.trigger_config?.entity_id;l&&t.push({entityId:l,isCounter:this._isCounterEntity(a.trigger_config)})}if(t.length===0)return;let e=await this._statsService.getBatchMiniStats(t);this._miniStatsData=new Map([...this._miniStatsData,...e])}async _subscribe(){try{let t=await this.hass.connection.subscribeMessage(e=>{let i=e,a=ce(this._objects,i);a!==null&&(this._objects=a,e.objects&&qt(a,this._stats??null))},{type:"maintenance_supporter/subscribe",deltas:!0,compact:!0});if(!this.isConnected){t();return}this._unsub=t}catch{}}get _taskRows(){let t=[];for(let _ of this._objects)for(let v of _.tasks){if(!this._showArchived&&v.archived||this._filterStatus&&v.status!==this._filterStatus)continue;if(this._filterUser){let x=this._filterUser==="current_user"?this._userService?.getCurrentUserId():this._filterUser;if(v.responsible_user_id!==x)continue}if(this._filterLabel&&!(v.labels||[]).includes(this._filterLabel))continue;let b=[];for(let x of Object.values(this._groups))x.task_refs?.some(f=>f.entry_id===_.entry_id&&f.task_id===v.id)&&b.push(x.name);t.push({entry_id:_.entry_id,task_id:v.id,object_name:_.object.name,task_name:v.name,type:v.type,schedule_type:v.schedule_type,status:v.status,days_until_due:v.days_until_due??null,next_due:v.next_due??null,trigger_active:v.trigger_active,trigger_current_value:v.trigger_current_value??null,trigger_current_delta:v.trigger_current_delta??null,trigger_config:v.trigger_config??null,trigger_entity_info:v.trigger_entity_info??null,times_performed:v.times_performed,total_cost:v.total_cost,interval_days:v.interval_days??null,interval_unit:v.interval_unit??null,interval_anchor:v.interval_anchor??null,is_done:v.is_done??!1,archived:v.archived??!1,history:v.history||[],enabled:v.enabled,nfc_tag_id:v.nfc_tag_id??null,priority:v.priority??"normal",labels:v.labels??[],area_id:_.object.area_id??null,responsible_user_id:v.responsible_user_id??null,group_names:b})}let e={overdue:0,triggered:1,due_soon:2,ok:3},i=(_,v)=>(e[_.status]??9)-(e[v.status]??9),a=(_,v)=>(_.days_until_due??99999)-(v.days_until_due??99999),l=(_,v)=>i(_,v)||a(_,v),h=_=>_.area_id&&this.hass?.areas?.[_.area_id]?.name||"",c=_=>_.responsible_user_id&&this._userService?.getUserName(_.responsible_user_id)||"",p=_=>_.group_names[0]||"",u={due_date:l,object:(_,v)=>_.object_name.localeCompare(v.object_name)||l(_,v),type:(_,v)=>_.type.localeCompare(v.type)||l(_,v),task_name:(_,v)=>_.task_name.localeCompare(v.task_name),area:(_,v)=>{let b=h(_),x=h(v);return!b&&x?1:b&&!x?-1:b.localeCompare(x)||l(_,v)},assigned_user:(_,v)=>{let b=c(_),x=c(v);return!b&&x?1:b&&!x?-1:b.localeCompare(x)||l(_,v)},group:(_,v)=>{let b=p(_),x=p(v);return!b&&x?1:b&&!x?-1:b.localeCompare(x)||l(_,v)}};return t.sort(u[this._sortMode]),t}_getObject(t){return this._objects.find(e=>e.entry_id===t)}_getTask(t,e){return this._getObject(t)?.tasks.find(a=>a.id===e)}_pushPanelState(t,e,i){let a={msp_view:t,msp_entry:e||null,msp_task:i||null};history.pushState(a,"")}_onPopState(t){let e=t.state;if(e?.msp_view&&(this._view=e.msp_view,this._selectedEntryId=e.msp_entry||null,this._selectedTaskId=e.msp_task||null,this._moreMenuOpen=!1,e.msp_view==="task"&&e.msp_entry&&e.msp_task)){this._historyFilter=null;let i=this._getTask(e.msp_entry,e.msp_task);i?.trigger_config?.entity_id&&this._fetchDetailStats(i.trigger_config.entity_id,this._isCounterEntity(i.trigger_config))}}_showOverview(){this._pushPanelState("overview"),this._view="overview",this._selectedEntryId=null,this._selectedTaskId=null,this._moreMenuOpen=!1,this._scrollContentToTop()}_showAllObjects(){this._pushPanelState("all_objects"),this._view="all_objects",this._selectedEntryId=null,this._selectedTaskId=null,this._scrollContentToTop()}_filterByStatus(t){this._filterStatus=t,this._activeViewId="",this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard"),this._scrollContentToTop()}get _allLabels(){let t=new Set;for(let e of this._objects)for(let i of e.tasks)for(let a of i.labels||[])t.add(a);return[...t].sort((e,i)=>e.localeCompare(i))}get _currentFilters(){return{status:this._filterStatus,user_id:this._filterUser,label:this._filterLabel,archived:this._showArchived,sort_mode:this._sortMode,group_by:this._groupByMode}}_applyView(t){if(this._activeViewId=t,!t)return;let e=this._savedViews.find(a=>a.id===t);if(!e)return;let i=e.filters;this._filterStatus=i.status||"",this._filterUser=i.user_id||null,this._filterLabel=i.label||null,this._showArchived=!!i.archived,["due_date","object","type","task_name","area","assigned_user","group"].includes(i.sort_mode)&&(this._sortMode=i.sort_mode),["none","area","group","user"].includes(i.group_by)&&(this._groupByMode=i.group_by);try{localStorage.setItem(F.taskSort,this._sortMode),localStorage.setItem(F.groupBy,this._groupByMode)}catch{}this._overviewTab!=="dashboard"&&(this._overviewTab="dashboard")}_openSavedViewsDialog(){this.shadowRoot.querySelector("maintenance-saved-views-dialog")?.open(this._currentFilters,this._savedViews)}_onSavedViewsChanged(t){this._savedViews=t.detail.views||[],this._activeViewId&&!this._savedViews.some(e=>e.id===this._activeViewId)&&(this._activeViewId="")}_scrollContentToTop(){requestAnimationFrame(()=>{let t=this.shadowRoot?.querySelector(".content");t&&t.scrollTo({top:0,behavior:"smooth"})})}_showObject(t){this._pushPanelState("object",t),this._view="object",this._selectedEntryId=t,this._selectedTaskId=null,this._scrollContentToTop()}_showTask(t,e){this._pushPanelState("task",t,e),this._view="task",this._selectedEntryId=t,this._selectedTaskId=e,this._activeTab="overview",this._historyFilter=null,this._scrollContentToTop(),this._fetchFullHistory(t,e);let i=this._getTask(t,e);if(i?.trigger_config?.entity_id){let a=i.trigger_config.entity_id,l=this._isCounterEntity(i.trigger_config);this._fetchDetailStats(a,l)}}_showToast(t){this._toastTimer&&clearTimeout(this._toastTimer),this._toastUndo=null,this._toastActionLabel="",this._toastMessage=t,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastTimer=null},4e3)}_showActionToast(t,e,i){this._showUndoToast(t,i),this._toastActionLabel=e}_showUndoToast(t,e){this._toastTimer&&clearTimeout(this._toastTimer),this._toastActionLabel="",this._toastMessage=t,this._toastUndo=e,this._toastTimer=setTimeout(()=>{this._toastMessage="",this._toastUndo=null,this._toastTimer=null},7e3)}_runToastUndo(){let t=this._toastUndo;this._toastTimer&&clearTimeout(this._toastTimer),this._toastMessage="",this._toastUndo=null,this._toastTimer=null,t?.()}_openPalette(){this._paletteQuery="",this._paletteActive=0,this._paletteOpen=!0,this.updateComplete.then(()=>{this.shadowRoot?.querySelector(".palette-input")?.focus()})}_closePalette(){this._paletteOpen=!1,this._paletteQuery=""}get _paletteResults(){let t=this._paletteQuery.trim().toLowerCase(),e=[];for(let i of this._objects){let a=i.object.name||"";(!t||a.toLowerCase().includes(t))&&e.push({kind:"object",entryId:i.entry_id,label:a,sub:s("object",this._lang)});for(let l of i.tasks){if(l.archived)continue;let h=l.name||"",c=(l.labels||[]).some(p=>p.toLowerCase().includes(t));if(!t||h.toLowerCase().includes(t)||a.toLowerCase().includes(t)||c){let p=(l.labels||[]).length?` #${(l.labels||[]).join(" #")}`:"";e.push({kind:"task",entryId:i.entry_id,taskId:l.id,label:h,sub:a+p})}}if(e.length>60)break}return e.slice(0,40)}_selectPaletteResult(t){this._closePalette(),t.kind==="task"&&t.taskId?this._showTask(t.entryId,t.taskId):this._showObject(t.entryId)}_renderPalette(){if(!this._paletteOpen)return d;let t=this._lang,e=this._paletteResults;return o`
this._closePalette()}>
i.stopPropagation()}> r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})(); +/*! maintenance_supporter frontend 2.56.0 */ +var S="2.56.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})(); diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-UBDFZUSU.js b/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-UBDFZUSU.js new file mode 100644 index 00000000..fceac13e --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/adopt-problem-sensors-dialog-UBDFZUSU.js @@ -0,0 +1,222 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import{a as d}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-I7J3AORE.js";import{a,b as _,c as t,f as l,g as h,i as g,j as n,n as i,p as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._localeReady=!1;this._userService=null;this._toggle=s=>{let o=new Set(this._selected);o.has(s)?o.delete(s):o.add(s),this._selected=o};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0})),o=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:o})),this._open=!1}catch(s){this._error=d(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return this.hass?.language||"en"}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="";try{this._userService?this._userService.updateHass(this.hass):this._userService=new u(this.hass);let[s,o]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=o}catch(s){this._error=d(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return t``;let s=this._lang,o=this._sensors.length>0&&this._selected.size===this._sensors.length;return t` +
+
e.stopPropagation()}> +
${i("adopt_problem_title",s)}
+
${i("adopt_problem_hint",s)}
+ ${this._error?t`
${this._error}
`:l} + + ${this._loading?t`
`:this._sensors.length===0?t`
${i("adopt_problem_none",s)}
`:t` + +
+ ${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),p=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return t` + + `})} +
+ `} + + ${!this._loading&&this._sensors.length>0&&this._users.length>0?t` + + `:l} + +
+ + ${i("cancel",s)} + + + ${i("adopt_problem_adopt",s)} + +
+
+
+ `}};r.styles=_` + .overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + } + .card { + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + border-radius: 12px; + padding: 20px; + display: flex; + flex-direction: column; + gap: 12px; + min-width: min(360px, calc(100vw - 24px)); + max-width: 560px; + width: 90vw; + max-height: 80vh; + overflow: hidden; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + .title { + font-size: 18px; + font-weight: 500; + } + .hint { + color: var(--secondary-text-color); + font-size: 13px; + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + .loading, + .empty { + color: var(--secondary-text-color); + font-size: 14px; + padding: 12px 0; + } + .select-all { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--secondary-text-color); + cursor: pointer; + } + .select-all input { + cursor: pointer; + } + .list { + display: flex; + flex-direction: column; + gap: 6px; + overflow-y: auto; + max-height: 50vh; + } + .row { + display: flex; + align-items: flex-start; + gap: 10px; + padding: 8px; + border: 1px solid var(--divider-color); + border-radius: 6px; + cursor: pointer; + } + .row input { + margin-top: 2px; + cursor: pointer; + } + .row-main { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; + flex: 1; + } + .row-top { + display: flex; + align-items: center; + gap: 8px; + } + .row-name { + font-weight: 500; + font-size: 13px; + } + .row-sub { + color: var(--secondary-text-color); + font-size: 12px; + } + .row-target { + color: var(--secondary-text-color); + font-size: 12px; + } + .row-part { + color: var(--secondary-text-color); + font-size: 12px; + display: flex; + align-items: center; + gap: 4px; + } + .row-part ha-icon { + --mdc-icon-size: 14px; + } + .new-tag { + font-style: italic; + } + .chip { + font-size: 11px; + padding: 1px 8px; + border-radius: 10px; + white-space: nowrap; + } + .chip-active { + background: var(--error-color, #f44336); + color: #fff; + } + .chip-ok { + background: var(--divider-color); + color: var(--secondary-text-color); + } + .responsible { + display: flex; + align-items: center; + gap: 8px; + font-size: 12px; + color: var(--secondary-text-color); + flex-wrap: wrap; + } + .responsible select { + flex: 1; + min-width: 140px; + padding: 4px 6px; + border-radius: 4px; + border: 1px solid var(--divider-color); + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + } + .actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 8px; + } + `,a([g({attribute:!1})],r.prototype,"hass",2),a([n()],r.prototype,"_open",2),a([n()],r.prototype,"_loading",2),a([n()],r.prototype,"_adopting",2),a([n()],r.prototype,"_error",2),a([n()],r.prototype,"_sensors",2),a([n()],r.prototype,"_selected",2),a([n()],r.prototype,"_users",2),a([n()],r.prototype,"_responsible",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-C5W5B43R.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-C5W5B43R.js new file mode 100644 index 00000000..f1c5a389 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-C5W5B43R.js @@ -0,0 +1,1201 @@ +/*! maintenance_supporter frontend 2.56.0 */ +var Te=Object.defineProperty;var je=Object.getOwnPropertyDescriptor;var Xe=(o,e,t,r)=>{for(var a=r>1?void 0:r?je(e,t):e,n=o.length-1,i;n>=0;n--)(i=o[n])&&(a=(r?i(e,t,a):i(a))||a);return r&&a&&Te(e,t,a),a};var L=globalThis,M=L.ShadowRoot&&(L.ShadyCSS===void 0||L.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,G=Symbol(),ne=new WeakMap,$=class{constructor(e,t,r){if(this._$cssResult$=!0,r!==G)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o,t=this.t;if(M&&e===void 0){let r=t!==void 0&&t.length===1;r&&(e=ne.get(t)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),r&&ne.set(t,e))}return e}toString(){return this.cssText}},ie=o=>new $(typeof o=="string"?o:o+"",void 0,G),U=(o,...e)=>{let t=o.length===1?o[0]:e.reduce((r,a,n)=>r+(i=>{if(i._$cssResult$===!0)return i.cssText;if(typeof i=="number")return i;throw Error("Value passed to 'css' function must be a 'css' function result: "+i+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(a)+o[n+1],o[0]);return new $(t,o,G)},se=(o,e)=>{if(M)o.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of e){let r=document.createElement("style"),a=L.litNonce;a!==void 0&&r.setAttribute("nonce",a),r.textContent=t.cssText,o.appendChild(r)}},V=M?o=>o:o=>o instanceof CSSStyleSheet?(e=>{let t="";for(let r of e.cssRules)t+=r.cssText;return ie(t)})(o):o;var{is:Ne,defineProperty:Pe,getOwnPropertyDescriptor:Re,getOwnPropertyNames:ze,getOwnPropertySymbols:Oe,getPrototypeOf:De}=Object,q=globalThis,le=q.trustedTypes,Le=le?le.emptyScript:"",Me=q.reactiveElementPolyfillSupport,C=(o,e)=>o,E={toAttribute(o,e){switch(e){case Boolean:o=o?Le:null;break;case Object:case Array:o=o==null?o:JSON.stringify(o)}return o},fromAttribute(o,e){let t=o;switch(e){case Boolean:t=o!==null;break;case Number:t=o===null?null:Number(o);break;case Object:case Array:try{t=JSON.parse(o)}catch{t=null}}return t}},H=(o,e)=>!Ne(o,e),ce={attribute:!0,type:String,converter:E,reflect:!1,useDefault:!1,hasChanged:H};Symbol.metadata??=Symbol("metadata"),q.litPropertyMetadata??=new WeakMap;var g=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=ce){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){let r=Symbol(),a=this.getPropertyDescriptor(e,r,t);a!==void 0&&Pe(this.prototype,e,a)}}static getPropertyDescriptor(e,t,r){let{get:a,set:n}=Re(this.prototype,e)??{get(){return this[t]},set(i){this[t]=i}};return{get:a,set(i){let l=a?.call(this);n?.call(this,i),this.requestUpdate(e,l,r)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??ce}static _$Ei(){if(this.hasOwnProperty(C("elementProperties")))return;let e=De(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(C("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(C("properties"))){let t=this.properties,r=[...ze(t),...Oe(t)];for(let a of r)this.createProperty(a,t[a])}let e=this[Symbol.metadata];if(e!==null){let t=litPropertyMetadata.get(e);if(t!==void 0)for(let[r,a]of t)this.elementProperties.set(r,a)}this._$Eh=new Map;for(let[t,r]of this.elementProperties){let a=this._$Eu(t,r);a!==void 0&&this._$Eh.set(a,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let t=[];if(Array.isArray(e)){let r=new Set(e.flat(1/0).reverse());for(let a of r)t.unshift(V(a))}else e!==void 0&&t.push(V(e));return t}static _$Eu(e,t){let r=t.attribute;return r===!1?void 0:typeof r=="string"?r:typeof e=="string"?e.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(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,t=this.constructor.elementProperties;for(let r of t.keys())this.hasOwnProperty(r)&&(e.set(r,this[r]),delete this[r]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return se(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,r){this._$AK(e,r)}_$ET(e,t){let r=this.constructor.elementProperties.get(e),a=this.constructor._$Eu(e,r);if(a!==void 0&&r.reflect===!0){let n=(r.converter?.toAttribute!==void 0?r.converter:E).toAttribute(t,r.type);this._$Em=e,n==null?this.removeAttribute(a):this.setAttribute(a,n),this._$Em=null}}_$AK(e,t){let r=this.constructor,a=r._$Eh.get(e);if(a!==void 0&&this._$Em!==a){let n=r.getPropertyOptions(a),i=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:E;this._$Em=a;let l=i.fromAttribute(t,n.type);this[a]=l??this._$Ej?.get(a)??l,this._$Em=null}}requestUpdate(e,t,r,a=!1,n){if(e!==void 0){let i=this.constructor;if(a===!1&&(n=this[e]),r??=i.getPropertyOptions(e),!((r.hasChanged??H)(n,t)||r.useDefault&&r.reflect&&n===this._$Ej?.get(e)&&!this.hasAttribute(i._$Eu(e,r))))return;this.C(e,t,r)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(e,t,{useDefault:r,reflect:a,wrapped:n},i){r&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,i??t??this[e]),n!==!0||i!==void 0)||(this._$AL.has(e)||(this.hasUpdated||r||(t=void 0),this._$AL.set(e,t)),a===!0&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}let e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[a,n]of this._$Ep)this[a]=n;this._$Ep=void 0}let r=this.constructor.elementProperties;if(r.size>0)for(let[a,n]of r){let{wrapped:i}=n,l=this[a];i!==!0||this._$AL.has(a)||l===void 0||this.C(a,void 0,n,l)}}let e=!1,t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(r=>r.hostUpdate?.()),this.update(t)):this._$EM()}catch(r){throw e=!1,this._$EM(),r}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(e){}firstUpdated(e){}};g.elementStyles=[],g.shadowRootOptions={mode:"open"},g[C("elementProperties")]=new Map,g[C("finalized")]=new Map,Me?.({ReactiveElement:g}),(q.reactiveElementVersions??=[]).push("2.1.2");var ee=globalThis,de=o=>o,I=ee.trustedTypes,pe=I?I.createPolicy("lit-html",{createHTML:o=>o}):void 0,fe="$lit$",f=`lit$${Math.random().toFixed(9).slice(2)}$`,be="?"+f,Ue=`<${be}>`,v=document,j=()=>v.createComment(""),N=o=>o===null||typeof o!="object"&&typeof o!="function",te=Array.isArray,qe=o=>te(o)||typeof o?.[Symbol.iterator]=="function",K=`[ +\f\r]`,T=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,_e=/-->/g,ue=/>/g,b=RegExp(`>|${K}(?:([^\\s"'>=/]+)(${K}*=${K}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),he=/'/g,ge=/"/g,ye=/^(?:script|style|textarea|title)$/i,oe=o=>(e,...t)=>({_$litType$:o,strings:e,values:t}),nt=oe(1),it=oe(2),st=oe(3),x=Symbol.for("lit-noChange"),p=Symbol.for("lit-nothing"),me=new WeakMap,y=v.createTreeWalker(v,129);function ve(o,e){if(!te(o)||!o.hasOwnProperty("raw"))throw Error("invalid template strings array");return pe!==void 0?pe.createHTML(e):e}var He=(o,e)=>{let t=o.length-1,r=[],a,n=e===2?"":e===3?"":"",i=T;for(let l=0;l"?(i=a??T,c=-1):_[1]===void 0?c=-2:(c=i.lastIndex-_[2].length,d=_[1],i=_[3]===void 0?b:_[3]==='"'?ge:he):i===ge||i===he?i=b:i===_e||i===ue?i=T:(i=b,a=void 0);let m=i===b&&o[l+1].startsWith("/>")?" ":"";n+=i===T?s+Ue:c>=0?(r.push(d),s.slice(0,c)+fe+s.slice(c)+f+m):s+f+(c===-2?l:m)}return[ve(o,n+(o[t]||"")+(e===2?"":e===3?"":"")),r]},P=class o{constructor({strings:e,_$litType$:t},r){let a;this.parts=[];let n=0,i=0,l=e.length-1,s=this.parts,[d,_]=He(e,t);if(this.el=o.createElement(d,r),y.currentNode=this.el.content,t===2||t===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(a=y.nextNode())!==null&&s.length0){a.textContent=I?I.emptyScript:"";for(let m=0;m2||r[0]!==""||r[1]!==""?(this._$AH=Array(r.length-1).fill(new String),this.strings=r):this._$AH=p}_$AI(e,t=this,r,a){let n=this.strings,i=!1;if(n===void 0)e=k(this,e,t,0),i=!N(e)||e!==this._$AH&&e!==x,i&&(this._$AH=e);else{let l=e,s,d;for(e=n[0],s=0;s{let r=t?.renderBefore??e,a=r._$litPart$;if(a===void 0){let n=t?.renderBefore??null;r._$litPart$=a=new R(e.insertBefore(j(),n),n,void 0,t??{})}return a._$AI(o),a};var re=globalThis,A=class extends g{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=xe(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return x}};A._$litElement$=!0,A.finalized=!0,re.litElementHydrateSupport?.({LitElement:A});var Fe=re.litElementPolyfillSupport;Fe?.({LitElement:A});(re.litElementVersions??=[]).push("4.2.2");var vt=o=>(e,t)=>{t!==void 0?t.addInitializer(()=>{customElements.define(o,e)}):customElements.define(o,e)};var Be={attribute:!0,type:String,converter:E,reflect:!1,hasChanged:H},We=(o=Be,e,t)=>{let{kind:r,metadata:a}=t,n=globalThis.litPropertyMetadata.get(a);if(n===void 0&&globalThis.litPropertyMetadata.set(a,n=new Map),r==="setter"&&((o=Object.create(o)).wrapped=!0),n.set(t.name,o),r==="accessor"){let{name:i}=t;return{set(l){let s=e.get.call(this);e.set.call(this,l),this.requestUpdate(i,s,o,!0,l)},init(l){return l!==void 0&&this.C(i,void 0,o,l),l}}}if(r==="setter"){let{name:i}=t;return function(l){let s=this[i];e.call(this,l),this.requestUpdate(i,s,o,!0,l)}}throw Error("Unsupported decorator location: "+r)};function we(o){return(e,t)=>typeof t=="object"?We(o,e,t):((r,a,n)=>{let i=a.hasOwnProperty(n);return a.constructor.createProperty(n,r),i?Object.getOwnPropertyDescriptor(a,n):void 0})(o,e,t)}function At(o){return we({...o,state:!0,attribute:!1})}var Ge={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)"},Ve={ok:"mdi:check-circle",due_soon:"mdi:alert-circle",overdue:"mdi:alert-octagon",triggered:"mdi:bell-alert",archived:"mdi:archive-outline",paused:"mdi:pause-circle-outline",completed:"mdi:check-circle",skipped:"mdi:skip-next",missed:"mdi:calendar-remove",reset:"mdi:refresh"};var ke={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",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)",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",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",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",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",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_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_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).",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",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 (comma-separated)",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_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_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"};var Xt="\u20AC",ae="en",Ce=(()=>{let o=window;return o.__msLocales||(o.__msLocales={store:{},inflight:{}}),o.__msLocales})(),w=Ce.store;w.en||(w.en=ke);var Qe=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),Ye="/maintenance_supporter_locales",z=Ce.inflight;function W(o){let e=(o||ae).toLowerCase();return e.startsWith("pt")&&e.endsWith("br")?"pt-br":e.substring(0,2)}function u(o,e){let t=W(e);return w[t]?.[o]??w.en[o]??o}function eo(o){let e=W(o);return e===ae||e in w}function to(o){let e=W(o);return e===ae||e in w||!Qe.has(e)?Promise.resolve():(e in z||(z[e]=fetch(`${Ye}/${e}.json`).then(t=>t.ok?t.json():null).then(t=>{t?w[e]=t:delete z[e]}).catch(()=>{delete z[e]})),z[e])}function O(o){let e=W(o);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"}[e]??"en-US"}var Je=window,B=Je.__msDateTimePrefs??={};function oo(o){o&&(B.date=o.date_format,B.time=o.time_format)}function Ee(o,e){let t=String(o.getDate()).padStart(2,"0"),r=String(o.getMonth()+1).padStart(2,"0"),a=String(o.getFullYear());switch(B.date){case"DMY":return`${t}/${r}/${a}`;case"MDY":return`${r}/${t}/${a}`;case"YMD":return`${a}-${r}-${t}`;case"system":return o.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return o.toLocaleDateString(O(e),{day:"2-digit",month:"2-digit",year:"numeric"})}}function Ze(o,e){switch(B.time){case"12":return o.toLocaleTimeString(O(e),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return o.toLocaleTimeString(O(e),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return o.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return o.toLocaleTimeString(O(e),{hour:"2-digit",minute:"2-digit"})}}function Se(o,e){if(!o)return"\u2014";try{let t=o.includes("T")?o:o+"T00:00:00";return Ee(new Date(t),e)}catch{return o}}function ro(o,e){if(!o)return"\u2014";try{let t=new Date(o);return Ee(t,e)+" "+Ze(t,e)}catch{return o}}function ao(o,e){if(o==null)return"\u2014";let t=e||"en";return o<0?`${Math.abs(o)} ${u("d_overdue",t)}`:o===0?u("today",t):`${o} ${u(o===1?"day":"days",t)}`}function Ae(o,e,t){return o==null?"\u2014":`${o} ${u("unit_"+(e||"days"),t)}`}function $e(o,e,t="long"){return new Date(Date.UTC(2024,0,1+o)).toLocaleDateString(O(e),{weekday:t,timeZone:"UTC"})}function no(o,e){let t=o.schedule,r=t?.offset?` ${t.offset>0?"+":"\u2212"}${Math.abs(t.offset)}d`:"";switch(t?.kind){case"weekdays":return((t.weekdays||[]).map(a=>$e(a,e,"short")).join(" & ")||"\u2014")+r;case"nth_weekday":return t.weekday==null||t.nth==null?"\u2014":`${t.nth===-1?u("ord_last",e):u("ord_"+t.nth,e)} ${$e(t.weekday,e,"long")}${r}`;case"day_of_month":return t.day==null?"\u2014":(t.day===-1?u(t.business?"last_business_day_month":"last_day_month",e):`${u("day_word",e)} ${t.day}`)+r;case"one_time":return o.due_date?Se(o.due_date,e):u("one_time",e);case"manual":return u("manual",e);case"interval":return Ae(t.every,t.unit,e)}return o.schedule_type==="one_time"?o.due_date?Se(o.due_date,e):u("one_time",e):o.schedule_type==="manual"?u("manual",e):o.schedule_type==="sensor_based"?u("sensor_based",e):o.interval_days!=null?Ae(o.interval_days,o.interval_unit,e):"\u2014"}function io(o,e){o.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:e},bubbles:!0,composed:!0}))}var so=U` + .field { display: flex; flex-direction: column; gap: 4px; } + .field-label { font-size: 12px; color: var(--secondary-text-color); } + .field-input { + padding: 8px 10px; font-size: 14px; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + font-family: inherit; width: 100%; box-sizing: border-box; + } + .field-input:focus { outline: none; border-color: var(--primary-color); } +`,lo=U` + :host { + --maint-ok-color: var(--success-color, #4caf50); + --maint-due-soon-color: var(--warning-color, #ff9800); + --maint-overdue-color: var(--error-color, #f44336); + /* Theme-token first so it follows dark/custom themes (was a bare #ff5722, + inconsistent with STATUS_COLORS.triggered which already tokenised it). */ + --maint-triggered-color: var(--deep-orange-color, #ff5722); + } + + .status-badge { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + color: white; + white-space: nowrap; + /* Fixed minimum so OK / Due Soon / Overdue / Triggered pills are uniform + width in the task table — keeps the object-name column aligned. */ + min-width: 70px; + box-sizing: border-box; + } + /* Shape icon so status is not conveyed by colour alone (accessibility). */ + .status-badge ha-icon { --mdc-icon-size: 14px; margin-left: -1px; } + + /* Light-background statuses (green/orange/grey) carry DARK text: white on + them fails even the 3:1 WCAG UI-contrast floor (2.2–2.8:1), while the + saturated statuses below keep white (≥3.1:1). Matches the calendar pills. */ + .status-badge.ok { background-color: var(--maint-ok-color); color: #000; } + .status-badge.due_soon { background-color: var(--maint-due-soon-color); color: #000; } + .status-badge.overdue { background-color: var(--maint-overdue-color); } + .status-badge.triggered { background-color: var(--maint-triggered-color); } + /* Completed one-time task ("done") — muted blue-grey. */ + .status-badge.done { background-color: var(--maint-done-color, #78909c); } + /* v2.10.0: archived (retire-but-retain) — neutral grey, clearly inert. */ + .status-badge.archived { background-color: var(--disabled-color, #9e9e9e); color: #000; } + /* v2.20 (N3): paused — frozen but present, info blue. */ + .status-badge.paused { background-color: var(--info-color, #2196f3); } + + /* v1.4.7: 5-column grid so all 5 KPIs (Objects/Tasks/Overdue/Due Soon/ + Triggered) always stay in one row. The previous flex-wrap layout was + wrapping the 5th item (Triggered, the widest label) onto its own row + on narrow viewports because the natural width of the items pushed past + the container width. Grid forces equal 1/5 distribution regardless of + label length. */ + .stats-bar { + display: grid; + /* auto-fit instead of a fixed 5: with the budget feature on, two KPI + tiles join the strip (#125) — and on narrow screens the tiles wrap + instead of crushing. */ + grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); + gap: 16px; + padding: 16px; + } + + .stat-item { + display: flex; + flex-direction: column; + align-items: center; + min-width: 0; + } + .stat-item .stat-label { + /* Allow long labels to ellipsis rather than overflow the grid cell. */ + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .stat-item.clickable { cursor: pointer; border-radius: 8px; padding: 4px 8px; transition: background 0.15s, box-shadow 0.15s; } + .stat-item.clickable:hover { background: var(--secondary-background-color); } + /* v2.1.0 — KPIs that map to a status filter highlight when active so the + user can see at a glance which filter is on, even after scrolling away. */ + .stat-item.clickable.active { + background: var(--secondary-background-color); + box-shadow: inset 0 -3px 0 var(--primary-color); + } + + .objects-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 16px; + padding: 16px 0; + } + .object-card { + padding: 16px; + background: var(--card-background-color); + border-radius: 8px; + cursor: pointer; + border: 1px solid var(--divider-color); + transition: transform 0.15s, box-shadow 0.15s; + /* Large installs (100+ objects): skip rendering off-screen cards. The + intrinsic size keeps the scrollbar stable while they're skipped. */ + content-visibility: auto; + contain-intrinsic-size: auto 120px; + } + .object-card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); } + .object-card-header { display: flex; justify-content: space-between; align-items: center; } + .object-card-name { font-weight: 500; font-size: 16px; } + .object-card-count { color: var(--secondary-text-color); font-size: 13px; } + .object-card-meta { color: var(--secondary-text-color); font-size: 13px; margin-top: 4px; } + .object-card-empty { color: var(--warning-color); font-size: 13px; margin-top: 8px; font-style: italic; } + + /* Overdue indicator dot on object cards (#35) */ + .object-card { position: relative; } + .object-card-overdue { border-left: 3px solid var(--error-color); } + .overdue-dot { + position: absolute; + top: 12px; + right: 12px; + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--error-color); + box-shadow: 0 0 0 2px var(--card-background-color); + } + + /* Group-by collapsible sections (#35 + #36) */ + .group-section { + margin: 12px 0; + border: 1px solid var(--divider-color); + border-radius: 8px; + background: var(--card-background-color); + } + .group-section[open] { padding-bottom: 8px; } + .group-section-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 16px; + cursor: pointer; + font-weight: 500; + list-style: none; + user-select: none; + } + .group-section-header::-webkit-details-marker { display: none; } + .group-section-header::before { + content: "▶"; + font-size: 10px; + color: var(--secondary-text-color); + transition: transform 0.15s; + } + .group-section[open] .group-section-header::before { transform: rotate(90deg); } + .group-section-count { + color: var(--secondary-text-color); + font-size: 13px; + font-weight: 400; + } + .group-section .objects-grid, + .group-section .task-table { + padding: 0 12px; + } + + .empty-state-centered { text-align: center; padding: 32px 16px; } + .empty-state-centered ha-button { margin-top: 16px; } + + .stat-value { + font-size: 24px; + font-weight: bold; + color: var(--primary-text-color); + } + + .stat-label { + font-size: 12px; + color: var(--secondary-text-color); + } + + .card-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + } + + .card-header h1 { + margin: 0; + font-size: 20px; + font-weight: 500; + } + + .action-buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + .action-buttons ha-button { + --ha-button-font-size: 13px; + } + + .history-timeline { padding: 0 16px 16px; } + + .history-entry { + display: flex; + gap: 12px; + padding: 8px 0; + border-bottom: 1px solid var(--divider-color); + /* Long histories: skip painting off-screen entries (flex, not subgrid, so + safe — subgrid task rows can't use this without breaking alignment). */ + content-visibility: auto; + contain-intrinsic-size: auto 48px; + } + .history-entry:last-child { border-bottom: none; } + + .history-icon { + flex-shrink: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + color: white; + } + + .history-icon.completed { background: var(--maint-ok-color); } + .history-icon.skipped { background: var(--secondary-text-color); } + .history-icon.reset { background: var(--info-color, #2196f3); } + .history-icon.triggered { background: var(--maint-triggered-color); } + + .history-content { flex: 1; min-width: 0; } + + /* v2.2.0 — row holds the type label + the small Edit button */ + .history-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + } + /* v2.37 — marks completions the system recorded itself (trigger recovered). + margin-right:auto keeps it left beside the type label while the edit + button stays pinned right by the row's space-between. */ + .history-auto-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-edit-btn { + background: transparent; + color: var(--secondary-text-color); + border: none; + border-radius: 4px; + padding: 4px; + cursor: pointer; + display: inline-flex; + align-items: center; + transition: background 0.15s, color 0.15s; + } + .history-edit-btn:hover { + background: var(--secondary-background-color); + color: var(--primary-color); + } + .history-edit-btn ha-icon { --mdc-icon-size: 16px; } + + .history-date { + font-size: 12px; + color: var(--secondary-text-color); + } + + .history-details { + display: flex; + gap: 12px; + font-size: 13px; + color: var(--secondary-text-color); + margin-top: 4px; + } + + /* History filter chips */ + .history-filters { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 12px; + } + + .filter-chip { + display: inline-flex; + align-items: center; + padding: 4px 12px; + border-radius: 16px; + font-size: 12px; + cursor: pointer; + background: var(--secondary-background-color, #f5f5f5); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); + transition: all 0.2s; + user-select: none; + } + + .filter-chip:hover { background: var(--divider-color); } + + .filter-chip.active { + background: var(--primary-color); + color: var(--text-primary-color, #fff); + border-color: var(--primary-color); + } + + .filter-chip.clear { + font-style: italic; + opacity: 0.7; + } + + /* Cost/Duration history chart */ + .history-chart { + width: 100%; + height: 200px; + display: block; + } + + .chart-legend { + display: flex; + justify-content: center; + gap: 16px; + margin-top: 4px; + font-size: 11px; + color: var(--secondary-text-color); + } + + .legend-item { + display: inline-flex; + align-items: center; + gap: 4px; + } + + .legend-swatch { + display: inline-block; + width: 12px; + height: 12px; + border-radius: 2px; + } + + .empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 48px 16px; + color: var(--secondary-text-color); + } + + .empty-state ha-svg-icon { + --mdc-icon-size: 48px; + margin-bottom: 16px; + } + + /* Sparkline chart */ + .sparkline-container { position: relative; margin: 8px 0; } + + .sparkline-svg { + width: 100%; + height: 140px; + display: block; + } + + /* Trigger info card */ + .trigger-card { + background: var(--card-background-color, #fff); + border-radius: 12px; + padding: 12px 16px; + margin: 8px 0; + border: 1px solid var(--divider-color); + } + + .trigger-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 4px; + } + + .trigger-entity-name { font-weight: 500; font-size: 14px; } + .trigger-entity-id { font-size: 11px; color: var(--secondary-text-color); font-family: monospace; } + + .entity-link { + cursor: pointer; + text-decoration: underline dotted; + text-underline-offset: 2px; + } + .entity-link:hover { + color: var(--primary-color); + text-decoration: underline solid; + } + + .trigger-value-row { + display: flex; + align-items: baseline; + gap: 6px; + margin: 4px 0; + } + + .trigger-current { font-size: 28px; font-weight: 700; color: var(--primary-text-color); } + .trigger-current.active { color: var(--maint-triggered-color); } + .trigger-unit { font-size: 14px; color: var(--secondary-text-color); } + + /* Counter progress ("8,507 / 15,000 km · 57 %" + bar) */ + .counter-progress { margin: 6px 0 4px; } + .counter-progress-nums { + display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; + } + .counter-progress-main { font-size: 26px; font-weight: 700; color: var(--primary-text-color); } + .counter-progress-target { font-size: 15px; font-weight: 500; color: var(--secondary-text-color); } + .counter-progress-pct { font-size: 15px; font-weight: 700; } + .counter-progress-pct.ok { color: var(--success-color, #4caf50); } + .counter-progress-pct.near { color: var(--warning-color, #ff9800); } + .counter-progress-pct.over { color: var(--error-color, #f44336); } + .counter-progress-bar { + height: 8px; border-radius: 4px; margin: 6px 0 4px; overflow: hidden; + background: var(--secondary-background-color, rgba(0, 0, 0, 0.08)); + } + .counter-progress-fill { height: 100%; border-radius: 4px; transition: width 0.3s ease; } + .counter-progress-fill.ok { background: var(--success-color, #4caf50); } + .counter-progress-fill.near { background: var(--warning-color, #ff9800); } + .counter-progress-fill.over { background: var(--error-color, #f44336); } + .counter-progress-caption { font-size: 12px; color: var(--secondary-text-color); } + + /* Note under a chart that fell back to sparse maintenance-event values */ + .chart-note { + display: flex; align-items: center; gap: 6px; margin-top: 2px; + font-size: 12px; color: var(--secondary-text-color); + } + .chart-note ha-icon { --mdc-icon-size: 15px; flex: none; } + + .trigger-limits { + display: flex; + gap: 16px; + font-size: 13px; + color: var(--secondary-text-color); + margin: 6px 0; + flex-wrap: wrap; + } + + .trigger-limit-item { + display: flex; + align-items: center; + gap: 4px; + } + + .trigger-limit-item .dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + } + .trigger-limit-item .dot.warn { background: var(--error-color, #f44336); } + .trigger-limit-item .dot.range { background: var(--secondary-text-color); } + .trigger-limit-item .dot.ok { background: var(--maint-ok-color); } + + /* Row action buttons */ + .row-actions { + display: flex; + gap: 0; + flex-shrink: 0; + margin-left: auto; + } + + .row-actions mwc-icon-button { + --mdc-icon-button-size: 32px; + --mdc-icon-size: 18px; + } + + .row-actions .btn-complete { color: var(--maint-ok-color); } + .row-actions .btn-skip { color: var(--secondary-text-color); } + + /* Days bar for overview */ + .due-cell { + display: flex; + flex-direction: column; + align-items: flex-end; + min-width: 90px; + gap: 2px; + } + + .due-text { font-size: 13px; } + + .days-bar { + width: 100%; + height: 3px; + background: var(--divider-color); + border-radius: 2px; + overflow: hidden; + } + + .days-bar-fill { + height: 100%; + border-radius: 2px; + transition: width 0.3s; + } + + /* Trigger progress bar (overview rows). width:100% — the due-cell doesn't + stretch its children (align-items: flex-end), so without it the bar + shrinks to its label and reads shorter than the days-bar in other rows. */ + .trigger-progress { + display: flex; + flex-direction: column; + gap: 2px; + width: 100%; + min-width: 90px; + } + + .trigger-progress-bar { + width: 100%; + height: 6px; + background: var(--divider-color); + border-radius: 3px; + overflow: hidden; + } + + .trigger-progress-fill { + height: 100%; + border-radius: 2px; + transition: width 0.3s; + } + + .trigger-progress-label { + font-size: 12px; + color: var(--secondary-text-color); + text-align: right; + } + + /* Days progress bar (detail view) */ + .days-progress { + margin: 8px 0 16px; + padding: 12px 16px; + background: var(--card-background-color, #fff); + border-radius: 12px; + border: 1px solid var(--divider-color); + } + + .days-progress-labels { + display: flex; + justify-content: space-between; + font-size: 12px; + color: var(--secondary-text-color); + margin-bottom: 6px; + } + + .days-progress-bar { + width: 100%; + height: 6px; + background: var(--divider-color); + border-radius: 3px; + overflow: hidden; + } + + .days-progress-fill { + height: 100%; + border-radius: 3px; + transition: width 0.3s; + } + + .days-progress-text { + font-size: 13px; + font-weight: 500; + text-align: center; + margin-top: 6px; + color: var(--primary-text-color); + } + + /* Mini-sparkline in overview rows */ + .mini-sparkline { + width: 60px; + height: 20px; + display: block; + margin-top: 2px; + opacity: 0.7; + } + + /* Overflow indicator for overdue progress bars */ + .days-bar-fill.overflow, + .days-progress-fill.overflow, + .trigger-progress-fill.overflow { + background-image: repeating-linear-gradient( + -45deg, + transparent, + transparent 3px, + rgba(255,255,255,0.2) 3px, + rgba(255,255,255,0.2) 6px + ); + animation: overflow-pulse 2s ease-in-out infinite; + } + + @keyframes overflow-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } + } + + /* Budget KPI tiles in the stats strip (#125) — replaced the full-width + budget-bars row. */ + .stat-item.budget-tile .budget-tile-value { + font-size: 15px; + padding-top: 5px; + white-space: nowrap; + } + .budget-tile-bar { + width: 100%; + max-width: 130px; + height: 4px; + border-radius: 2px; + background: var(--divider-color); + overflow: hidden; + margin-top: 5px; + } + .budget-tile-bar > div { + height: 100%; + border-radius: 2px; + transition: width 0.3s; + } + + /* Groups section */ + .groups-section { + padding: 8px 16px 16px; + } + + .groups-section h3 { + font-size: 14px; + font-weight: 500; + color: var(--secondary-text-color); + margin: 0 0 8px; + } + + .groups-grid { + display: flex; + gap: 12px; + flex-wrap: wrap; + } + + .group-card { + background: var(--card-background-color, #fff); + border: 1px solid var(--divider-color); + border-radius: 12px; + padding: 12px 16px; + min-width: 180px; + flex: 1; + max-width: 300px; + cursor: default; + } + + .group-card-head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 8px; + } + + .group-card-name { + font-weight: 500; + font-size: 14px; + margin-bottom: 4px; + } + + .group-card-actions { + display: flex; + gap: 0; + } + .group-card-actions mwc-icon-button { + --mdc-icon-button-size: 28px; + --mdc-icon-size: 16px; + color: var(--secondary-text-color); + } + + .groups-header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-bottom: 8px; + } + .groups-header h3 { margin: 0; } + + .seasonal-actions { + display: flex; + justify-content: flex-end; + padding: 4px 0; + } + + .group-card-desc { + font-size: 12px; + color: var(--secondary-text-color); + margin-bottom: 8px; + } + + .group-card-tasks { + display: flex; + gap: 6px; + flex-wrap: wrap; + } + + .group-task-chip { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 11px; + padding: 2px 8px; + border-radius: 10px; + background: var(--secondary-background-color, #f5f5f5); + color: var(--primary-text-color); + } + + /* Adaptive scheduling suggestion badge */ + .suggestion-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + border-radius: 16px; + font-size: 12px; + font-weight: 500; + background: var(--info-color, #2196f3); + color: white; + margin-left: 8px; + } + + .suggestion-actions { + display: flex; + gap: 8px; + margin-top: 8px; + } + + .suggestion-actions ha-button { + --ha-button-font-size: 12px; + } + + .confidence-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + } + + .confidence-dot.low { background: var(--secondary-text-color); } + .confidence-dot.medium { background: var(--warning-color, #ff9800); } + .confidence-dot.high { background: var(--success-color, #4caf50); } + + /* Feedback toggle buttons in complete dialog */ + .feedback-section { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + border-top: 1px solid var(--divider-color); + } + + .feedback-label { + font-weight: 500; + font-size: 13px; + color: var(--secondary-text-color); + } + + .feedback-buttons { + display: flex; + gap: 8px; + } + + .feedback-btn { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--divider-color); + border-radius: 8px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + cursor: pointer; + text-align: center; + transition: all 0.2s; + } + + .feedback-btn:hover { + background: var(--secondary-background-color, #f5f5f5); + } + + .feedback-btn.selected { + background: var(--primary-color); + color: var(--text-primary-color, #fff); + border-color: var(--primary-color); + } + + /* Seasonal chart */ + .seasonal-chart { + padding: 12px 16px; + margin: 8px 0; + background: var(--card-background-color, #fff); + border-radius: 12px; + border: 1px solid var(--divider-color); + } + + .seasonal-chart-title { + font-size: 13px; + font-weight: 500; + color: var(--secondary-text-color); + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; + } + + .seasonal-chart-title .source-tag { + font-size: 10px; + padding: 1px 6px; + border-radius: 8px; + background: var(--secondary-background-color, #f5f5f5); + color: var(--secondary-text-color); + font-weight: 400; + } + + .seasonal-chart svg { + width: 100%; + height: 100px; + display: block; + } + + .seasonal-labels { + display: flex; + justify-content: space-between; + padding: 0 2px; + margin-top: 4px; + } + + .seasonal-label { + font-size: 10px; + color: var(--secondary-text-color); + text-align: center; + flex: 1; + } + + .seasonal-label.active-month { + font-weight: 700; + color: var(--primary-color); + } + + .seasonal-factor-tag { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + border-radius: 10px; + font-size: 11px; + font-weight: 500; + background: var(--secondary-background-color, #f5f5f5); + color: var(--secondary-text-color); + margin-left: 6px; + } + + .seasonal-factor-tag.short { + background: rgba(76, 175, 80, 0.15); + color: var(--success-color, #4caf50); + } + + .seasonal-factor-tag.long { + background: rgba(255, 152, 0, 0.15); + color: var(--warning-color, #ff9800); + } + + /* --- Sensor Prediction Section (Phase 3) --- */ + + .prediction-section { + margin: 16px 0; + padding: 12px 16px; + background: var(--card-background-color, #fff); + border-radius: 12px; + border: 1px solid var(--divider-color, #e0e0e0); + } + + .prediction-urgency-banner { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + margin-bottom: 12px; + border-radius: 8px; + background: rgba(255, 152, 0, 0.15); + color: var(--warning-color, #ff9800); + font-size: 13px; + font-weight: 500; + } + .prediction-urgency-banner ha-svg-icon { + --mdc-icon-size: 18px; + flex-shrink: 0; + } + + .prediction-title { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 600; + color: var(--primary-text-color); + margin-bottom: 10px; + } + .prediction-title ha-svg-icon { + --mdc-icon-size: 16px; + color: var(--primary-color); + } + + .prediction-grid { + display: flex; + flex-wrap: wrap; + gap: 12px; + } + + .prediction-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--secondary-text-color); + } + .prediction-item ha-svg-icon { + --mdc-icon-size: 14px; + color: var(--secondary-text-color); + flex-shrink: 0; + } + + .prediction-label { + font-weight: 500; + } + + .prediction-value { + font-weight: 600; + color: var(--primary-text-color); + } + .prediction-value.rising { color: var(--error-color, #f44336); } + .prediction-value.falling { color: var(--info-color, #2196f3); } + .prediction-value.stable { color: var(--success-color, #4caf50); } + .prediction-value.exceeded { color: var(--error-color, #f44336); font-weight: 700; } + .prediction-value.urgent { color: var(--warning-color, #ff9800); font-weight: 700; } + + .prediction-rate { + font-size: 11px; + opacity: 0.7; + font-family: monospace; + } + + .prediction-date { + font-size: 11px; + opacity: 0.7; + } + + .prediction-entity { + font-size: 10px; + opacity: 0.6; + font-family: monospace; + } + + /* --- Weibull Reliability Section (Phase 4) --- */ + + .weibull-section { + margin: 16px 0; + padding: 12px 16px; + background: var(--card-background-color, #fff); + border-radius: 12px; + border: 1px solid var(--divider-color, #e0e0e0); + } + + .weibull-title { + display: flex; + align-items: center; + gap: 6px; + font-size: 13px; + font-weight: 600; + color: var(--primary-text-color); + margin-bottom: 10px; + } + .weibull-title ha-svg-icon { + --mdc-icon-size: 16px; + color: var(--primary-color); + } + + .weibull-chart svg { + width: 100%; + height: 160px; + display: block; + } + + .weibull-info-row { + display: flex; + flex-wrap: wrap; + gap: 16px; + margin-top: 10px; + } + + .weibull-info-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--secondary-text-color); + } + + .weibull-info-value { + font-weight: 600; + color: var(--primary-text-color); + } + + /* Beta interpretation badge */ + .beta-badge { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 600; + white-space: nowrap; + } + .beta-badge ha-svg-icon { + --mdc-icon-size: 14px; + } + + .beta-badge.early_failures { + background: rgba(244, 67, 54, 0.15); + color: var(--error-color, #f44336); + } + .beta-badge.random_failures { + background: var(--secondary-background-color, #f5f5f5); + color: var(--secondary-text-color); + } + .beta-badge.wear_out { + background: rgba(255, 152, 0, 0.15); + color: var(--warning-color, #ff9800); + } + .beta-badge.highly_predictable { + background: rgba(76, 175, 80, 0.15); + color: var(--success-color, #4caf50); + } + + /* Confidence interval range bar */ + .confidence-range { + margin-top: 12px; + } + + .confidence-range-title { + font-size: 12px; + font-weight: 500; + color: var(--secondary-text-color); + margin-bottom: 6px; + } + + .confidence-bar { + position: relative; + width: 100%; + height: 8px; + background: var(--divider-color, #e0e0e0); + border-radius: 4px; + overflow: visible; + } + + .confidence-fill { + position: absolute; + height: 100%; + border-radius: 4px; + background: var(--primary-color, #03a9f4); + opacity: 0.25; + } + + .confidence-marker { + position: absolute; + top: -4px; + width: 3px; + height: 16px; + border-radius: 1px; + transform: translateX(-50%); + } + .confidence-marker.recommended { + background: var(--success-color, #4caf50); + } + .confidence-marker.current { + background: var(--primary-color, #03a9f4); + } + + .confidence-labels { + display: flex; + justify-content: space-between; + margin-top: 4px; + } + + .confidence-text { + font-size: 10px; + color: var(--secondary-text-color); + } + .confidence-text.low { + text-align: left; + } + .confidence-text.high { + text-align: right; + } + + .task-disabled { opacity: 0.5; } + .badge-disabled { + font-size: 10px; + padding: 1px 6px; + border-radius: 8px; + background: var(--disabled-color, #9e9e9e); + color: white; + } + + /* ── Shared responsive styles (panel + card) ── */ + @media (max-width: 600px) { + .row-actions mwc-icon-button { + --mdc-icon-button-size: 44px; + --mdc-icon-size: 22px; + } + + .due-cell { min-width: 70px; } + + .trigger-card { padding: 10px 12px; } + .trigger-current { font-size: 22px; } + + .prediction-grid { flex-direction: column; gap: 8px; } + + .weibull-info-row { flex-direction: column; gap: 8px; } + + /* Budget tiles on narrow screens (#125): slightly smaller value so the + "x / y €" pair fits the wrapped grid cell. */ + .stat-item.budget-tile .budget-tile-value { font-size: 13px; } + + .group-card { min-width: 0; max-width: 100%; } + + .filter-chip { padding: 6px 12px; font-size: 13px; } + + .history-details { flex-wrap: wrap; gap: 6px; } + + .sparkline-container { max-width: 100%; overflow: hidden; } + .sparkline-svg { height: 100px; } + + .stats-bar { gap: 8px; padding: 12px; } + /* min-width: 0 (NOT a fixed floor) — a fixed min-width re-enables the + grid's auto minimum, so the 5 KPI tracks couldn't shrink below their + label text and the last KPI clipped off-screen on phones (the header + only *looked* cut — .content scrolls sideways, but nothing hints so). + With 0 the tracks compress and the labels wrap to a second line. */ + .stat-item { min-width: 0; } + .stat-item.clickable { padding: 4px 4px; } + .stat-item .stat-label { font-size: 11px; white-space: normal; text-align: center; line-height: 1.2; } + .stat-value { font-size: 20px; } + } +`;export{Xe as a,U as b,nt as c,it as d,x as e,p as f,A as g,vt as h,we as i,At as j,Ge as k,Ve as l,Xt as m,u as n,eo as o,to as p,oo as q,Se as r,ro as s,ao as t,$e as u,no as v,io as w,so as x,lo as y}; +/*! Bundled license information: + +@lit/reactive-element/css-tag.js: + (** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +@lit/reactive-element/reactive-element.js: +lit-html/lit-html.js: +lit-element/lit-element.js: +@lit/reactive-element/decorators/custom-element.js: +@lit/reactive-element/decorators/property.js: +@lit/reactive-element/decorators/state.js: +@lit/reactive-element/decorators/event-options.js: +@lit/reactive-element/decorators/base.js: +@lit/reactive-element/decorators/query.js: +@lit/reactive-element/decorators/query-all.js: +@lit/reactive-element/decorators/query-async.js: +@lit/reactive-element/decorators/query-assigned-nodes.js: + (** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +lit-html/is-server.js: + (** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) + +@lit/reactive-element/decorators/query-assigned-elements.js: + (** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + *) +*/ diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-DV4UHMJC.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-DV4UHMJC.js new file mode 100644 index 00000000..6835e270 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-DV4UHMJC.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.56.0 */ +var i=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],s=i.map(n=>n.key),l=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function c(n){if(!Array.isArray(n))return[...l];let o=new Set,e=[];for(let a of n)typeof a=="string"&&s.includes(a)&&!o.has(a)&&(o.add(a),e.push(a));return e.length?(e.includes("name")||e.unshift("name"),e):[...l]}function u(n,o,e){let a=new Blob([n],{type:e}),r=URL.createObjectURL(a),t=document.createElement("a");t.href=r,t.download=o,t.target="_blank",t.rel="noopener",t.style.display="none",document.body.appendChild(t),t.dispatchEvent(new MouseEvent("click")),document.body.removeChild(t),setTimeout(()=>URL.revokeObjectURL(r),6e4)}function y(n,o){let e=document.createElement("a");e.href=n,e.download=o,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{i as a,l as b,c,u as d,y as e}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-I7J3AORE.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-I7J3AORE.js new file mode 100644 index 00000000..84c41412 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-I7J3AORE.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.56.0 */ +var r=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestampt.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{r as a}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-LJXSDCLS.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-LJXSDCLS.js new file mode 100644 index 00000000..b43dbe35 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-LJXSDCLS.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import{n as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var s={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_for_minutes:"trigger_for_minutes"};function c(r,o){let e=s[r];if(!e)return r;let t=a(e,o);return t&&t!==e?t:r}function d(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 g(r,o,e){if(e=e??a("action_error",o),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,_=t.message||t.error?.message||"";if(!_)return e;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||e}}export{g as a}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-NGMG4DEY.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-NGMG4DEY.js new file mode 100644 index 00000000..55a0c11f --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-NGMG4DEY.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.56.0 */ +var o=["notes","cost","duration","photo","user"],t={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{o as a,t as b}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-SD6IEJBA.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-SD6IEJBA.js new file mode 100644 index 00000000..b495b697 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-SD6IEJBA.js @@ -0,0 +1,2 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import{n as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";function _(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let t=!!e.entry_id&&e.entry_id!==r,a=t?e.entry_id:r,o=s.find(p=>p.entry_id===a),n=(o?.parts||[]).find(p=>p.id===e.part_id)||null,d=t&&o?.object?.name||"",i=n?.name||u("shared_part_unknown",c);return{part:n,foreign:t,ownerName:d,label:d?`${i} (${d})`:i}}function f(e,r,s,c){let{part:t,label:a}=l(e,r,s,c),o=t&&t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:"",n=t?.storage_location?` \u2014 ${t.storage_location}`:"";return`${e.quantity}\xD7 ${a}${o}${n}`}function g(e,r,s,c){let a=(s.find(n=>n.entry_id===r)?.parts||[]).map(n=>({...n})),o=new Set(a.map(n=>_({part_id:n.id})));for(let n of e?.consumes_parts||[]){if(!n.entry_id||n.entry_id===r)continue;let d=_(n);if(o.has(d))continue;o.add(d);let{part:i,ownerName:p}=l(n,r,s,c);a.push({id:n.part_id,name:i?.name||u("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:n.entry_id,owner_name:p})}return a}export{_ as a,f as b,g as c}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-ZK3W7TF6.js b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-ZK3W7TF6.js new file mode 100644 index 00000000..24ba5652 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/chunk-ZK3W7TF6.js @@ -0,0 +1,54 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import{a as t,b as a,c as i,f as l,g as p,i as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var e=class extends p{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(n){let o=n.target.value;this.value=o,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:o}}))}render(){return i` + + `}};e.styles=a` + :host { display: block; } + .field { + display: flex; + flex-direction: column; + gap: 4px; + } + .label { + font-size: 12px; + color: var(--secondary-text-color, #888); + font-weight: 500; + } + .req { color: var(--error-color, #f44336); margin-left: 2px; } + input { + padding: 8px 10px; + font-size: 14px; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, rgba(255,255,255,0.12)); + border-radius: 6px; + font-family: inherit; + width: 100%; + box-sizing: border-box; + outline: none; + } + input:focus { + border-color: var(--primary-color); + } + input:disabled { opacity: 0.5; cursor: not-allowed; } + .helper { + font-size: 11px; + color: var(--secondary-text-color); + font-style: italic; + } + `,t([r()],e.prototype,"label",2),t([r()],e.prototype,"value",2),t([r()],e.prototype,"placeholder",2),t([r()],e.prototype,"type",2),t([r({type:Boolean})],e.prototype,"required",2),t([r({type:Boolean})],e.prototype,"disabled",2),t([r()],e.prototype,"step",2),t([r()],e.prototype,"min",2),t([r()],e.prototype,"max",2),t([r()],e.prototype,"pattern",2),t([r()],e.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e); diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/complete-dialog-AN3A2ME6.js b/custom_components/maintenance_supporter/frontend/panel-chunks/complete-dialog-AN3A2ME6.js new file mode 100644 index 00000000..dff6c202 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/complete-dialog-AN3A2ME6.js @@ -0,0 +1,292 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import{b as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NGMG4DEY.js";import{a as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-SD6IEJBA.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as s,b,c as a,f as d,g as v,i as n,j as l,n as r,x as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this._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._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._usedParts=Object.fromEntries(this.consumesParts.map(e=>[_(e),{...e}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,o=t.files?.[0];if(t.value="",!!o){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",o,o.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(o))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=g(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a``:d}_partsCostSuggestion(){if(this.restockDefault!==null){let o=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(o)||o<=0?null:Math.round(this.restockUnitCost*o*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let o of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(o));c?.cost!=null&&(e+=c.cost*(o.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let o=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return a``}_close(){this._open=!1}render(){if(!this._open)return a``;let e=this.lang||this.hass?.language||"en";return a` + +
${r("complete_title",e)}${this.taskName}
+
+ ${this._error?a`
${this._error}
`:d} + ${this.checklist.length>0?a` +
+ + ${this.checklist.map((t,o)=>a` + + `)} +
+ `:d} + ${this.taskType==="reading"?a` + `:d} + ${this.parts.length?a`
+ ${r("complete_parts_used",e)} + ${this.parts.map(t=>{let o=_({part_id:t.id,entry_id:t.entry_id}),c=this._usedParts[o],p=c!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return a`
+ + ${p?a`{let h=parseFloat(f.target.value);this._usedParts={...this._usedParts,[o]:{...u,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:d} +
`})} +
`:this.consumesInfo.length?a`
+ ${this.consumesInfo.map(t=>a`
${t}
`)} +
`:d} + ${this.restockDefault!==null?a` + `:d} + + + + +
+ ${r("completion_photo_optional",e)}${this._req("photo")} + ${this._photoPreview?a` +
+ + +
`:a` + `} +
+ ${this.adaptiveEnabled?a` + + `:d} +
+
+ + ${r("cancel",e)} + + 0} + title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(m[t]??t,e))).join(" \xB7 "):""} + > + ${this._loading?r("completing",e):r("complete",e)} + +
+
+ `}};i.styles=[k,b` + .req-mark { + color: var(--error-color, #f44336); + margin-left: 2px; + font-weight: 600; + } + /* #104: one-click cost suggestion from parts — quiet link-style chip. */ + .cost-suggestion { + align-self: flex-start; + margin-top: 4px; + padding: 0; + border: none; + background: none; + color: var(--primary-color); + font-size: 12.5px; + cursor: pointer; + text-decoration: underline dotted; + text-underline-offset: 2px; + } + .dialog-title { + font-size: 18px; + font-weight: 500; + padding-bottom: 12px; + } + .content { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 300px; + } + .dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + } + .consumes-hint { + font-size: 13px; + color: var(--secondary-text-color); + border-left: 3px solid var(--primary-color); + padding: 4px 8px; + margin: 4px 0 8px; + } + /* #99: editable per-completion parts selection */ + .used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; } + .used-part-row { display: flex; align-items: center; gap: 8px; } + .used-part-check { + display: flex; align-items: center; gap: 6px; flex: 1; + font-size: 13px; cursor: pointer; + } + .used-part-check input { cursor: pointer; } + /* #111: whose stock this row draws on. Muted but never omitted — an + unlabelled foreign pool is indistinguishable from an own part. */ + .used-part-owner { color: var(--secondary-text-color); } + .used-part-qty { + width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px; + border: 1px solid var(--divider-color); + background: var(--card-background-color); + color: var(--primary-text-color); + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + /* .field/.field-label/.field-input come from nativeFieldStyles */ + .photo-pick { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border: 1px dashed var(--divider-color); + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--secondary-text-color); + width: fit-content; + } + .photo-pick:hover { border-color: var(--primary-color); } + .photo-pick input[type="file"] { display: none; } + .photo-preview { + position: relative; + width: fit-content; + } + .photo-preview img { + max-width: 160px; + max-height: 160px; + border-radius: 8px; + display: block; + } + .photo-remove { + position: absolute; + top: -8px; + right: -8px; + width: 24px; + height: 24px; + border-radius: 50%; + border: none; + background: var(--error-color, #db4437); + color: #fff; + cursor: pointer; + font-size: 12px; + line-height: 1; + } + .checklist-section { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + border-bottom: 1px solid var(--divider-color); + margin-bottom: 4px; + } + .checklist-label { + font-weight: 500; + font-size: 13px; + color: var(--secondary-text-color); + } + .checklist-item { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 4px 0; + font-size: 14px; + } + .checklist-item input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + } + .feedback-section { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + border-top: 1px solid var(--divider-color); + } + .feedback-label { + font-weight: 500; + font-size: 13px; + color: var(--secondary-text-color); + } + .feedback-buttons { + display: flex; + gap: 8px; + } + .feedback-btn { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--divider-color); + border-radius: 8px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + cursor: pointer; + text-align: center; + transition: all 0.2s; + } + .feedback-btn:hover { + background: var(--secondary-background-color, #f5f5f5); + } + .feedback-btn.selected { + background: var(--primary-color); + color: var(--text-primary-color, #fff); + border-color: var(--primary-color); + } + `],s([n({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"entryId",2),s([n()],i.prototype,"taskId",2),s([n()],i.prototype,"taskName",2),s([n()],i.prototype,"lang",2),s([n({type:Array})],i.prototype,"checklist",2),s([n({type:Boolean})],i.prototype,"adaptiveEnabled",2),s([n()],i.prototype,"taskType",2),s([n()],i.prototype,"readingUnit",2),s([n({attribute:!1})],i.prototype,"restockDefault",2),s([n({attribute:!1})],i.prototype,"restockUnitCost",2),s([n()],i.prototype,"currencySymbol",2),s([n({attribute:!1})],i.prototype,"parts",2),s([n({attribute:!1})],i.prototype,"consumesParts",2),s([n({type:Array})],i.prototype,"consumesInfo",2),s([n({type:Array})],i.prototype,"requiredFields",2),s([l()],i.prototype,"_open",2),s([l()],i.prototype,"_notes",2),s([l()],i.prototype,"_cost",2),s([l()],i.prototype,"_duration",2),s([l()],i.prototype,"_loading",2),s([l()],i.prototype,"_error",2),s([l()],i.prototype,"_checklistState",2),s([l()],i.prototype,"_feedback",2),s([l()],i.prototype,"_photoDocId",2),s([l()],i.prototype,"_photoPreview",2),s([l()],i.prototype,"_photoUploading",2),s([l()],i.prototype,"_readingValue",2),s([l()],i.prototype,"_restockQty",2),s([l()],i.prototype,"_usedParts",2),s([n({attribute:!1})],i.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",i);export{i as MaintenanceCompleteDialog}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/object-dialog-HNU4YSB4.js b/custom_components/maintenance_supporter/frontend/panel-chunks/object-dialog-HNU4YSB4.js new file mode 100644 index 00000000..eb09abd6 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/object-dialog-HNU4YSB4.js @@ -0,0 +1,143 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZK3W7TF6.js";import{a as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as r,b as _,c as l,f as o,g as p,i as d,j as s,n as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var e=class extends p{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return this.hass?.language??navigator.language.split("-")[0]??"en"}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(a,n){this._entryId=a,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(a){this._error=h(a,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(a=>a.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let a=this._lang,n=this._entryId?i("edit_object",a):i("new_object",a);return l` + +
${n}
+
+ ${this._error?l`
${this._error}
`:o} + this._name=t.target.value} + > + this._manufacturer=t.target.value} + > + this._model=t.target.value} + > + this._serialNumber=t.target.value} + > + this._documentationUrl=t.target.value} + > + this._areaId=t.detail.value||""} + > + this._installationDate=t.target.value} + > + this._warrantyExpiry=t.target.value} + > + i("link_device_optional",a)} + @value-changed=${t=>this._haDeviceId=t.detail.value?.device||""} + > + ${this._parentChoices().length?l``:o} + +
+
+ + ${i("cancel",this._lang)} + + + ${this._loading?i("saving",this._lang):i("save",this._lang)} + +
+
+ `}};e.styles=_` + .dialog-title { + font-size: 18px; + font-weight: 500; + padding-bottom: 12px; + } + .content { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 300px; + } + .dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + } + ms-textfield { + display: block; + } + .textarea-field { + display: flex; flex-direction: column; gap: 4px; + } + .textarea-label { + font-size: 12px; color: var(--secondary-text-color, #888); font-weight: 500; + } + .textarea-field textarea { + padding: 8px 10px; font-size: 14px; font-family: inherit; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + resize: vertical; + } + .textarea-field textarea:focus { + outline: none; border-color: var(--primary-color); + } + .parent-select { + padding: 8px 10px; font-size: 14px; font-family: inherit; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + `,r([d({attribute:!1})],e.prototype,"hass",2),r([d({attribute:!1})],e.prototype,"objects",2),r([s()],e.prototype,"_open",2),r([s()],e.prototype,"_loading",2),r([s()],e.prototype,"_error",2),r([s()],e.prototype,"_name",2),r([s()],e.prototype,"_manufacturer",2),r([s()],e.prototype,"_model",2),r([s()],e.prototype,"_serialNumber",2),r([s()],e.prototype,"_areaId",2),r([s()],e.prototype,"_installationDate",2),r([s()],e.prototype,"_warrantyExpiry",2),r([s()],e.prototype,"_documentationUrl",2),r([s()],e.prototype,"_notes",2),r([s()],e.prototype,"_haDeviceId",2),r([s()],e.prototype,"_parentEntryId",2),r([s()],e.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",e);export{e as MaintenanceObjectDialog}; diff --git a/custom_components/maintenance_supporter/frontend/panel-chunks/qr-dialog-QWUERDTM.js b/custom_components/maintenance_supporter/frontend/panel-chunks/qr-dialog-QWUERDTM.js new file mode 100644 index 00000000..5bc14841 --- /dev/null +++ b/custom_components/maintenance_supporter/frontend/panel-chunks/qr-dialog-QWUERDTM.js @@ -0,0 +1,213 @@ +/*! maintenance_supporter frontend 2.56.0 */ +import{a,b as v,c as n,f as g,g as b,i as m,j as c,n as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";function p(l){return l.replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}function x(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":p(l)}function $(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var r=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,o,s){this._entryId=e,this._taskId=i,this._objectName=o,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let o=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&o.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(o);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let o=i?.code,s=i?.message;this._error=o==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,o=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let h=this.lang||"en",d=p(i),u=p(o),_=!!this._completeResult,f=p(t("qr_action_view",h)),w=p(t("qr_action_complete",h));s.document.write(` + +${d} + +

${d}

+${u?`
${u}
`:""} +
+
+ QR Info +
${f}
+
+ ${_?`
+ QR Complete +
${w}
+
`:""} +
+
${p(this._viewResult.url)}
+