diff --git a/.storage/lovelace_resources b/.storage/lovelace_resources index fcfc3a9f..a20386d7 100644 --- a/.storage/lovelace_resources +++ b/.storage/lovelace_resources @@ -201,7 +201,7 @@ }, { "id": "4d1a17aa96b74064af713fc5e9334862", - "url": "/hacsfiles/ha-treemap-card/treemap-card.js?hacstag=11141174680152", + "url": "/hacsfiles/ha-treemap-card/treemap-card.js?hacstag=11141174680153", "type": "module" }, { diff --git a/custom_components/maintenance_supporter/config_flow_options_task_trigger.py b/custom_components/maintenance_supporter/config_flow_options_task_trigger.py index 2a735c79..027c8bfa 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_trigger.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_trigger.py @@ -184,6 +184,26 @@ class TriggerStepsMixin(TriggerConfigMixin): self._on_cancel = self._show_task_action_menu return await self.async_step_opt_sensor_select() + def _clear_stale_trigger_runtime(self, old_tc: dict[str, Any], new_tc: dict[str, Any]) -> None: + """Drop persisted trigger runtime when the trigger fundamentally changed. + + Mirrors the WS update path (websocket/tasks_crud.py): the Store + runtime wins over config on restore (#102), so an options-flow edit + that changes type/entities/baseline must clear it or the old + counters/anchors silently survive the edit (bug audit 2026-08-22). + """ + if ( + old_tc.get("type") != new_tc.get("type") + or old_tc.get("entity_id") != new_tc.get("entity_id") + or old_tc.get("entity_ids") != new_tc.get("entity_ids") + or old_tc.get("trigger_baseline_value") != new_tc.get("trigger_baseline_value") + ): + rd = getattr(self.config_entry, "runtime_data", None) + store = getattr(rd, "store", None) if rd else None + if store is not None: + store.clear_trigger_runtime(self._selected_task_id or "") + store.async_delay_save() + def _save_edited_trigger(self) -> ConfigFlowResult: """Save edited trigger configuration to an existing task.""" new_data = dict(self.config_entry.data) @@ -191,6 +211,10 @@ class TriggerStepsMixin(TriggerConfigMixin): updated_task = dict(new_tasks.get(self._selected_task_id or "", {})) if "trigger_config" in self._current_task: + self._clear_stale_trigger_runtime( + updated_task.get("trigger_config") or {}, + self._current_task["trigger_config"] or {}, + ) updated_task["trigger_config"] = self._current_task["trigger_config"] if CONF_TASK_SCHEDULE_TYPE in self._current_task: updated_task["schedule_type"] = self._current_task[CONF_TASK_SCHEDULE_TYPE] @@ -231,17 +255,20 @@ class TriggerStepsMixin(TriggerConfigMixin): new_tasks = dict(new_data.get(CONF_TASKS, {})) updated_task = dict(new_tasks.get(self._selected_task_id or "", {})) + old_tc = updated_task.get("trigger_config") or {} if remaining: # Partial removal — keep trigger with remaining entities updated_tc = dict(updated_task.get("trigger_config", {})) updated_tc["entity_ids"] = remaining updated_tc.pop("entity_id", None) updated_task["trigger_config"] = updated_tc + self._clear_stale_trigger_runtime(old_tc, updated_tc) else: # Full removal — remove entire trigger config updated_task.pop("trigger_config", None) if updated_task.get("schedule_type") == ScheduleType.SENSOR_BASED: updated_task["schedule_type"] = ScheduleType.TIME_BASED + self._clear_stale_trigger_runtime(old_tc, {}) new_tasks[self._selected_task_id or ""] = updated_task new_data[CONF_TASKS] = new_tasks diff --git a/custom_components/maintenance_supporter/coordinator.py b/custom_components/maintenance_supporter/coordinator.py index 2adee15b..6f178e3e 100644 --- a/custom_components/maintenance_supporter/coordinator.py +++ b/custom_components/maintenance_supporter/coordinator.py @@ -106,6 +106,12 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): self._recently_completed: dict[str, float] = {} # task_id -> monotonic timestamp # Manual completions only — the double-tap dedup window (journey M1). self._recent_manual_completions: dict[str, float] = {} + # Backdated completions (explicit completed_at) get their OWN dedup, + # keyed by (task_id, timestamp): a double-submitted backfill wrote two + # identical history entries and consumed parts/budget twice (bug audit + # 2026-08-22). Distinct timestamps stay unguarded on purpose — a user + # backfilling several past days in a row is legitimate. + self._recent_backfills: dict[tuple[str, str], float] = {} # Trigger entity availability tracking self._startup_time: float = time.monotonic() @@ -806,7 +812,18 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Use cached budget totals (recalculate if stale or missing) cache: dict[str, Any] | None = self.hass.data.get(DOMAIN, {}).get(BUDGET_CACHE_KEY) - if cache is None or (dt_util.now() - cache["last_updated"]).total_seconds() > 3600: + # Stale when old — OR when the local month/year rolled over since the + # compute: the cached buckets are tied to the month they were computed + # in, and a purely age-based rule fired a false "budget nearly + # exhausted" alert for the NEW month during the first cached hour of + # the 1st (bug audit 2026-08-22). + now_local = dt_util.now() + if ( + cache is None + or (now_local - cache["last_updated"]).total_seconds() > 3600 + or cache["last_updated"].month != now_local.month + or cache["last_updated"].year != now_local.year + ): self._recalculate_budget_cache() cache = self.hass.data[DOMAIN][BUDGET_CACHE_KEY] @@ -994,7 +1011,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # — it neither checks nor stamps the guard (a stamped guard would # swallow a normal completion made right after backfilling, and a # normal completion's stamp must not swallow the backfill). - if completed_at is None: + if completed_at is None and not auto: last_manual = self._recent_manual_completions.get(task_id) if last_manual is not None and time.monotonic() - last_manual < MANUAL_COMPLETION_DEDUP_SECONDS: _LOGGER.info( @@ -1009,6 +1026,18 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # part-consume / history entry. self._recent_manual_completions[task_id] = time.monotonic() + if completed_at is not None: + backfill_key = (task_id, completed_at.isoformat()) + last_backfill = self._recent_backfills.get(backfill_key) + if last_backfill is not None and time.monotonic() - last_backfill < MANUAL_COMPLETION_DEDUP_SECONDS: + _LOGGER.info( + "Ignoring duplicate backdated completion of %s @ %s (double submit)", + task_id, + completed_at.isoformat(), + ) + return + self._recent_backfills[backfill_key] = time.monotonic() + task = MaintenanceTask.from_dict(merged[task_id]) pre_rotation_responsible = task.responsible_user_id effective_ts = completed_at if completed_at is not None else dt_util.now() @@ -1192,15 +1221,20 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): for entry in reversed(history): if entry.get("type") != "completed": continue - try: - last_ts = dt_util.parse_datetime(entry.get("timestamp", "")) - except (ValueError, TypeError): - last_ts = None - if last_ts is not None and (dt_util.now() - last_ts).total_seconds() < 120: + # parse_persisted_utc, NOT dt_util.parse_datetime: history can + # hold NAIVE timestamps (the history-edit dialog sends + # datetime-local without an offset), and `aware - naive` raised + # TypeError OUTSIDE the old try — the recovery coroutine died and + # the auto-completion was silently never recorded (bug audit + # 2026-08-22). The UTC assumption is harmless for a 120 s guard. + from .helpers.dates import parse_persisted_utc + + last_ts = parse_persisted_utc(entry.get("timestamp", "")) + if last_ts is not None and (dt_util.utcnow() - last_ts).total_seconds() < 120: _LOGGER.debug( "Skipping auto-complete for %s: completed %.0fs ago", task_id, - (dt_util.now() - last_ts).total_seconds(), + (dt_util.utcnow() - last_ts).total_seconds(), ) return break diff --git a/custom_components/maintenance_supporter/entity/triggers/__init__.py b/custom_components/maintenance_supporter/entity/triggers/__init__.py index d274e615..0f256742 100644 --- a/custom_components/maintenance_supporter/entity/triggers/__init__.py +++ b/custom_components/maintenance_supporter/entity/triggers/__init__.py @@ -103,10 +103,12 @@ def _inject_per_entity_state(config: dict[str, Any], entity_state: dict[str, Any elif trigger_type == TriggerType.STATE_CHANGE: if "change_count" in entity_state: config["trigger_change_count"] = entity_state["change_count"] - # #136: a hold window that was open when HA went down. - if "pending_since" in entity_state: + # #136: a hold window that was open when HA went down. Truthy-gated, + # not `in`: stores written before set_trigger_runtime became a replace + # (2026-08-22) can carry stale/None pending keys forever. + if entity_state.get("pending_since"): config["trigger_state_pending_since"] = entity_state["pending_since"] - if "pending_state" in entity_state: + if entity_state.get("pending_state"): config["trigger_state_pending_state"] = entity_state["pending_state"] elif trigger_type == TriggerType.THRESHOLD: tes = entity_state.get("threshold_exceeded_since") diff --git a/custom_components/maintenance_supporter/entity/triggers/runtime.py b/custom_components/maintenance_supporter/entity/triggers/runtime.py index ea578723..ef21dbb1 100644 --- a/custom_components/maintenance_supporter/entity/triggers/runtime.py +++ b/custom_components/maintenance_supporter/entity/triggers/runtime.py @@ -185,6 +185,17 @@ class RuntimeTrigger(BaseTrigger): self._on_since_dt = now self._on_since = now.isoformat() self.hass.async_create_task(self._persist_runtime()) + elif not self._is_on(new_val) and self._on_since_dt is not None: + # Restored anchor but the device APPEARS off (deferred setup + # kept the anchor, then the first real state is OFF). Without + # this, nothing ever cleared it and the 5-min periodic persist + # baked wall-clock time into runtime forever — an idle pump + # "ran" 24 h/day (bug audit 2026-08-22). Mirror the setup + # path: accumulate the ON-until-now gap once, then clear. + self._accumulate_elapsed() + self._on_since_dt = None + self._on_since = None + self.hass.async_create_task(self._persist_runtime()) self._update_evaluation() return @@ -239,6 +250,15 @@ class RuntimeTrigger(BaseTrigger): "Runtime trigger: %s turned ON (tracking started)", self.entity_id, ) + elif not now_on and self._on_since_dt is not None: + # OFF with a lingering anchor (e.g. unavailable→off right after a + # deferred setup kept the restored anchor: was_on reads the + # unavailable old state as not-on, so neither branch above fired). + # Same stale-anchor hazard as the appearance path — settle it. + self._accumulate_elapsed() + self._on_since_dt = None + self._on_since = None + self.hass.async_create_task(self._persist_runtime()) self._update_evaluation() diff --git a/custom_components/maintenance_supporter/entity/triggers/threshold.py b/custom_components/maintenance_supporter/entity/triggers/threshold.py index 76d86bd6..d8c3ea7a 100644 --- a/custom_components/maintenance_supporter/entity/triggers/threshold.py +++ b/custom_components/maintenance_supporter/entity/triggers/threshold.py @@ -130,6 +130,23 @@ class ThresholdTrigger(BaseTrigger): @callback def _timer_fired(_now: datetime) -> None: """Handle timer completion.""" + # Safety net (mirrors the state_change hold timer): only commit + # while the premise still HOLDS. _threshold_exceeded is cleared + # only by a numeric in-range reading, so a sensor that went + # unavailable right after crossing kept it True and the timer + # activated on a value nobody had observed for the whole window + # (bug audit 2026-08-22). Discard the window entirely — a bare + # return would leave the latch set and evaluate() would swallow + # every future exceeding reading; the next one re-arms fresh. + state = self.hass.states.get(self.entity_id) + live = self._get_numeric_value(state) if state is not None else None + if live is None or not self._value_exceeds_threshold(live): + self._threshold_exceeded = False + self._exceeded_since = None + self._exceeded_since_dt = None + if self.hass.is_running: + self.hass.async_create_task(self._persist_exceeded_since()) + return if self._threshold_exceeded: _LOGGER.debug( "Threshold for-timer fired: %s (%d min)", diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts index e7fba1be..ff78af5e 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/panel-budget-spent-only.test.ts @@ -48,8 +48,13 @@ describe("budget KPI tiles: spent-only display (#104)", () => { const tiles = sr(el).querySelectorAll(".budget-tile"); expect(tiles.length).to.equal(2); expect(sr(el).querySelectorAll(".budget-tile-bar").length).to.equal(1); - expect(tiles[0].textContent).to.contain("9.00 / 150 €"); + // 2026-08-24: the spent amount carries the full stat-value typography + // (same size as the other KPI chips); the "/ max" ratio is its own + // small line so it can't overflow the grid cell. + expect(tiles[0].querySelector(".stat-value")!.textContent).to.contain("9.00 €"); + expect(tiles[0].querySelector(".budget-tile-max")!.textContent).to.contain("/ 150 €"); expect(tiles[1].textContent).to.contain("429.60 €"); + expect(tiles[1].querySelector(".budget-tile-max"), "spent-only tile has no ratio line").to.equal(null); }); it("tiles live INSIDE the stats strip (#125)", async () => { diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/trigger-chart.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/trigger-chart.test.ts index b38ea7e5..bd401b62 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/trigger-chart.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/trigger-chart.test.ts @@ -99,6 +99,25 @@ describe("trigger-chart", () => { const marks = el.shadowRoot!.querySelectorAll('rect[fill="var(--success-color, #4caf50)"]'); expect(marks.length).to.equal(1); }); + + it("renders the production-shaped projection with real horizontal extent", async () => { + // sparkline.ts builds the projection as [last sample, last sample + 30d]. + // Before the domain fix (2026-08-24) the data-only time domain put that + // start on the right plot edge and the x2 clamp collapsed the dashed + // line to zero width — the degradation projection never rendered. + const el = await mount(); + const last = POINTS[POINTS.length - 1]; + el.projection = [last, { ts: last.ts + 30 * DAY, val: last.val + 15 }]; + await el.updateComplete; + const line = el.shadowRoot!.querySelector('line[stroke-dasharray="4,3"]'); + expect(line, "projection line rendered").to.exist; + const x1 = Number(line!.getAttribute("x1")); + const x2 = Number(line!.getAttribute("x2")); + expect(x2 - x1, `projection width (${x1} -> ${x2})`).to.be.greaterThan(50); + // still clamped inside the plot + const svgW = Number(el.shadowRoot!.querySelector("svg")!.getAttribute("width")); + expect(x2).to.be.at.most(svgW); + }); }); describe("chart-utils", () => { diff --git a/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts b/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts index 56876560..d7361eae 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/budget-section-card.ts @@ -76,8 +76,11 @@ export class MaintenanceBudgetSectionCard extends LitElement { this._busy = true; this._error = ""; try { - const m = parseFloat(this._localMonthly); - const y = parseFloat(this._localYearly); + // An emptied field means "remove this budget" and must SEND 0 (the + // backend's off-state) — omitting the key kept the old value, so a + // budget could never be cleared from this card (bug audit 2026-08-22). + const m = this._localMonthly.trim() === "" ? 0 : parseFloat(this._localMonthly); + const y = this._localYearly.trim() === "" ? 0 : parseFloat(this._localYearly); const settings: Record = {}; if (!isNaN(m) && m >= 0) settings.budget_monthly = m; if (!isNaN(y) && y >= 0) settings.budget_yearly = y; diff --git a/custom_components/maintenance_supporter/frontend-src/components/object-quick-actions-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/object-quick-actions-dialog.ts index 3cc172be..2d041267 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/object-quick-actions-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/object-quick-actions-dialog.ts @@ -71,7 +71,7 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement { private _onAddTask(): void { if (!this._entryId) return; import("../dialog-mount").then(({ openCreateTaskDialog }) => { - openCreateTaskDialog(); + openCreateTaskDialog(this._entryId!); this.close(); }); } diff --git a/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts index eeff139d..d145b38a 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/task-quick-actions-dialog.ts @@ -16,6 +16,8 @@ import { LitElement, html, css, nothing } from "lit"; import { property, state } from "lit/decorators.js"; import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInterval, formatRecurrence, langOf } from "../styles"; import { describeWsError } from "../ws-errors"; +import { isoDateLocal } from "../helpers/calendar-bucket"; +import { partsForCompletion } from "../helpers/shared-parts"; import { renderWeibullSection } from "../renderers/weibull"; import { renderPredictionSection } from "../renderers/prediction"; import { renderRecommendationBars } from "../renderers/recommendation"; @@ -75,7 +77,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { this._showReset = false; this._showAdaptive = false; this._skipReason = ""; - this._resetDate = new Date().toISOString().slice(0, 10); + // Local calendar date — toISOString() is UTC and prefills YESTERDAY for + // users east of UTC before their morning (bug audit 2026-08-22). + this._resetDate = isoDateLocal(new Date()); this._open = true; await Promise.all([this._loadTask(), this._loadFeatures()]); } @@ -146,14 +150,36 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement { private _onComplete(): void { if (!this._entryId || !this._taskId || !this._task) return; - // Reuse the existing rich complete-dialog by mounting it on body - import("../dialog-mount").then(({ openCompleteDialog }) => { + // Reuse the existing rich complete-dialog by mounting it on body. + // Pass EVERYTHING the card's direct path passes — omitting + // required_completion_fields let a mandatory note be skipped, and a + // reading task without type+unit never rendered its value field + // (bug audit 2026-08-22). + import("../dialog-mount").then(async ({ openCompleteDialog }) => { + const task = this._task!; + const isBuy = !!(task as { part_ref?: string }).part_ref; + let parts: Parameters[0]["parts"] = []; + if (!isBuy) { + try { + const r = await this.hass.connection.sendMessagePromise<{ + objects: MaintenanceObjectResponse[]; + }>({ type: "maintenance_supporter/objects", compact: true }); + parts = partsForCompletion(task, this._entryId!, r.objects || [], this._lang); + } catch { + // Parts stay empty — the dialog still completes without them. + } + } const ok = openCompleteDialog({ entry_id: this._entryId!, task_id: this._taskId!, - task_name: this._task!.name, - checklist: this._task!.checklist || [], - adaptive_enabled: !!this._task!.adaptive_config?.enabled, + task_name: task.name, + checklist: task.checklist || [], + adaptive_enabled: !!task.adaptive_config?.enabled, + required_completion_fields: task.required_completion_fields || [], + task_type: task.type || "", + reading_unit: (task as { reading_unit?: string }).reading_unit || "", + parts, + consumes_parts: isBuy ? [] : (task.consumes_parts || []), }); if (ok) { this._notifyChanged("complete"); diff --git a/custom_components/maintenance_supporter/frontend-src/components/trigger-chart.ts b/custom_components/maintenance_supporter/frontend-src/components/trigger-chart.ts index 794a1e47..6b55e56b 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/trigger-chart.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/trigger-chart.ts @@ -156,7 +156,14 @@ export class MaintenanceTriggerChart extends LitElement { } const tsMin = pts[0].ts; - const tsMax = pts[pts.length - 1].ts; + // The dashed projection extends the TIME domain. sparkline.ts builds it + // as [last sample, last sample + 30 d]; with a data-only domain its + // start sat exactly on the right plot edge and the x2 clamp below + // collapsed the line to zero width — the production degradation + // projection never rendered (found via the design-system previews, + // 2026-08-24). + const projEnd = this.projection && this.projection.length === 2 ? this.projection[1].ts : null; + const tsMax = projEnd != null ? Math.max(pts[pts.length - 1].ts, projEnd) : pts[pts.length - 1].ts; const tsSpan = tsMax - tsMin || 1; const withYear = needsYear(tsMin, tsMax); diff --git a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts index 431561cc..405f2450 100644 --- a/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts +++ b/custom_components/maintenance_supporter/frontend-src/dialog-mount.ts @@ -167,7 +167,10 @@ export function openEditObjectDialog( return true; } -export function openCreateTaskDialog(): boolean { +export function openCreateTaskDialog( + entryId = "", + objects?: Array<{ entry_id: string; object: { name: string } }>, +): boolean { const dlg = getOrCreate(TASK_DIALOG_TAG); if (!syncHass(dlg)) return false; const hass = getHass(); @@ -179,13 +182,20 @@ export function openCreateTaskDialog(): boolean { scheduleTimeEnabled: boolean; completionActionsEnabled: boolean; defaultWarningDays: number; - openCreate: (entryId?: string) => void; + openCreate: ( + entryId: string, + objects?: Array<{ entry_id: string; object: { name: string } }>, + ) => void; }; dlgFull.checklistsEnabled = settings.features.checklists; dlgFull.scheduleTimeEnabled = settings.features.schedule_time; dlgFull.completionActionsEnabled = settings.features.completion_actions; dlgFull.defaultWarningDays = settings.defaultWarningDays; - dlgFull.openCreate(); + // openCreate NEEDS a target: a bare call left _entryId undefined and + // _objectChoices empty — no object picker, and save failed on the + // backend's required entry_id (bug audit 2026-08-22). Callers pass the + // entry (quick-actions) or their object list (card header button). + dlgFull.openCreate(entryId, objects); })(); return true; } @@ -273,6 +283,12 @@ export function openCompleteDialog(args: { adaptive_enabled?: boolean; /** Details the task demands before it counts as done (v2.44). */ required_completion_fields?: string[]; + /** Reading tasks need type+unit or the value field never renders. */ + task_type?: string; + reading_unit?: string; + /** #99/#111: per-completion parts selection incl. shared pools. */ + parts?: MaintenanceCompleteDialog["parts"]; + consumes_parts?: MaintenanceCompleteDialog["consumesParts"]; }): boolean { const dlg = getOrCreate(COMPLETE_DIALOG_TAG); if (!syncHass(dlg)) return false; @@ -282,6 +298,12 @@ export function openCompleteDialog(args: { dlg.checklist = args.checklist ?? []; dlg.adaptiveEnabled = !!args.adaptive_enabled; dlg.requiredFields = args.required_completion_fields ?? []; + // Always assign — the dialog is a singleton, so an omitted field must not + // leak the previous task's value. + dlg.taskType = args.task_type ?? ""; + dlg.readingUnit = args.reading_unit ?? ""; + dlg.parts = args.parts ?? []; + dlg.consumesParts = args.consumes_parts ?? []; dlg.lang = (getHass()?.language) || "en"; dlg.open(); return true; diff --git a/custom_components/maintenance_supporter/frontend-src/locales/cs.json b/custom_components/maintenance_supporter/frontend-src/locales/cs.json index d1f39ab2..da868733 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/cs.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Výchozí filtr uživatele", "cal_editor_my_tasks": "Moje úkoly (aktuální uživatel)", "cal_editor_show_object_filter": "Zobrazit filtr objektu", - "cal_editor_object_hint": "Předvyberte objekt přes YAML: object_filter: \"\" — nebo seznam názvů pro omezení karty na více objektů." + "cal_editor_object_hint": "Předvyberte objekt přes YAML: object_filter: \"\" — nebo seznam názvů pro omezení karty na více objektů.", + "object_history_section": "Historie (všechny úkoly)", + "object_history_all_tasks": "Všechny úkoly", + "object_history_empty": "V tomto období nejsou žádné záznamy.", + "object_history_cap_note": "Historie uchovává až 500 záznamů na úkol — velmi staré záznamy mohou chybět.", + "service_record_title": "Servisní knížka", + "service_record_print": "Servisní knížka (PDF)", + "date": "Datum", + "service_record_entries": "záznamů", + "completed_by": "Dokončil", + "date_from": "Od", + "date_to": "Do" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/da.json b/custom_components/maintenance_supporter/frontend-src/locales/da.json index 2ea7c6fc..c89c96ee 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/da.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/da.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standard brugerfilter", "cal_editor_my_tasks": "Mine opgaver (aktuel bruger)", "cal_editor_show_object_filter": "Vis objektfilter", - "cal_editor_object_hint": "Forvælg et objekt via YAML: object_filter: \"\" — eller en liste af navne for at begrænse kortet til flere objekter." + "cal_editor_object_hint": "Forvælg et objekt via YAML: object_filter: \"\" — eller en liste af navne for at begrænse kortet til flere objekter.", + "object_history_section": "Historik (alle opgaver)", + "object_history_all_tasks": "Alle opgaver", + "object_history_empty": "Ingen poster i denne periode.", + "object_history_cap_note": "Historikken gemmer op til 500 poster pr. opgave — meget gamle poster kan mangle.", + "service_record_title": "Servicebog", + "service_record_print": "Servicebog (PDF)", + "date": "Dato", + "service_record_entries": "poster", + "completed_by": "Udført af", + "date_from": "Fra", + "date_to": "Til" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/de.json b/custom_components/maintenance_supporter/frontend-src/locales/de.json index 1415a8b8..ed01d558 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/de.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/de.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standard-Benutzerfilter", "cal_editor_my_tasks": "Meine Aufgaben (aktueller Benutzer)", "cal_editor_show_object_filter": "Objektfilter-Dropdown anzeigen", - "cal_editor_object_hint": "Ein Objekt per YAML vorauswählen: object_filter: \"\" — oder eine Namensliste, um die Karte auf mehrere Objekte zu beschränken." + "cal_editor_object_hint": "Ein Objekt per YAML vorauswählen: object_filter: \"\" — oder eine Namensliste, um die Karte auf mehrere Objekte zu beschränken.", + "object_history_section": "Verlauf (alle Aufgaben)", + "object_history_all_tasks": "Alle Aufgaben", + "object_history_empty": "Keine Einträge in diesem Zeitraum.", + "object_history_cap_note": "Der Verlauf umfasst bis zu 500 Einträge pro Aufgabe — sehr alte Einträge können fehlen.", + "service_record_title": "Serviceheft", + "service_record_print": "Serviceheft (PDF)", + "date": "Datum", + "service_record_entries": "Einträge", + "completed_by": "Erledigt von", + "date_from": "Von", + "date_to": "Bis" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/en.json b/custom_components/maintenance_supporter/frontend-src/locales/en.json index 2f644f29..9de226a5 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/en.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/en.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Default user filter", "cal_editor_my_tasks": "My tasks (current user)", "cal_editor_show_object_filter": "Show object filter dropdown", - "cal_editor_object_hint": "Pre-select one object via YAML: object_filter: \"\" — or a list of names to restrict the card to several objects." + "cal_editor_object_hint": "Pre-select one object via YAML: object_filter: \"\" — or a list of names to restrict the card to several objects.", + "object_history_section": "History (all tasks)", + "object_history_all_tasks": "All tasks", + "object_history_empty": "No entries in this range.", + "object_history_cap_note": "History keeps up to 500 entries per task — very old entries may be missing.", + "service_record_title": "Service record", + "service_record_print": "Service record (PDF)", + "date": "Date", + "service_record_entries": "entries", + "completed_by": "Completed by", + "date_from": "From", + "date_to": "To" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/es.json b/custom_components/maintenance_supporter/frontend-src/locales/es.json index 3ec66893..534eac42 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/es.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/es.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro de usuario predeterminado", "cal_editor_my_tasks": "Mis tareas (usuario actual)", "cal_editor_show_object_filter": "Mostrar filtro de objeto", - "cal_editor_object_hint": "Preselecciona un objeto por YAML: object_filter: \"\" — o una lista de nombres para limitar la tarjeta a varios objetos." + "cal_editor_object_hint": "Preselecciona un objeto por YAML: object_filter: \"\" — o una lista de nombres para limitar la tarjeta a varios objetos.", + "object_history_section": "Historial (todas las tareas)", + "object_history_all_tasks": "Todas las tareas", + "object_history_empty": "No hay entradas en este periodo.", + "object_history_cap_note": "El historial conserva hasta 500 entradas por tarea; las entradas muy antiguas pueden faltar.", + "service_record_title": "Registro de mantenimiento", + "service_record_print": "Registro de mantenimiento (PDF)", + "date": "Fecha", + "service_record_entries": "entradas", + "completed_by": "Realizado por", + "date_from": "Desde", + "date_to": "Hasta" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fi.json b/custom_components/maintenance_supporter/frontend-src/locales/fi.json index 7626cc42..a47f3652 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fi.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Oletuskäyttäjäsuodatin", "cal_editor_my_tasks": "Omat tehtävät (nykyinen käyttäjä)", "cal_editor_show_object_filter": "Näytä kohdesuodatin", - "cal_editor_object_hint": "Esivalitse kohde YAML:lla: object_filter: \"\" — tai nimilista rajataksesi kortin useisiin kohteisiin." + "cal_editor_object_hint": "Esivalitse kohde YAML:lla: object_filter: \"\" — tai nimilista rajataksesi kortin useisiin kohteisiin.", + "object_history_section": "Historia (kaikki tehtävät)", + "object_history_all_tasks": "Kaikki tehtävät", + "object_history_empty": "Ei merkintöjä tällä aikavälillä.", + "object_history_cap_note": "Historia säilyttää enintään 500 merkintää tehtävää kohden — hyvin vanhat merkinnät voivat puuttua.", + "service_record_title": "Huoltokirja", + "service_record_print": "Huoltokirja (PDF)", + "date": "Päivämäärä", + "service_record_entries": "merkintää", + "completed_by": "Suorittanut", + "date_from": "Alkaen", + "date_to": "Asti" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fr.json b/custom_components/maintenance_supporter/frontend-src/locales/fr.json index 2d66ef54..dea343cd 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fr.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtre utilisateur par défaut", "cal_editor_my_tasks": "Mes tâches (utilisateur actuel)", "cal_editor_show_object_filter": "Afficher le filtre d'objet", - "cal_editor_object_hint": "Présélectionnez un objet via YAML : object_filter : \"\" — ou une liste de noms pour limiter la carte à plusieurs objets." + "cal_editor_object_hint": "Présélectionnez un objet via YAML : object_filter : \"\" — ou une liste de noms pour limiter la carte à plusieurs objets.", + "object_history_section": "Historique (toutes les tâches)", + "object_history_all_tasks": "Toutes les tâches", + "object_history_empty": "Aucune entrée sur cette période.", + "object_history_cap_note": "L'historique conserve jusqu'à 500 entrées par tâche — les entrées très anciennes peuvent manquer.", + "service_record_title": "Carnet d'entretien", + "service_record_print": "Carnet d'entretien (PDF)", + "date": "Date", + "service_record_entries": "entrées", + "completed_by": "Réalisé par", + "date_from": "Du", + "date_to": "Au" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hi.json b/custom_components/maintenance_supporter/frontend-src/locales/hi.json index 569983e8..2109fbf5 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hi.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "डिफ़ॉल्ट उपयोगकर्ता फ़िल्टर", "cal_editor_my_tasks": "मेरे कार्य (वर्तमान उपयोगकर्ता)", "cal_editor_show_object_filter": "ऑब्जेक्ट फ़िल्टर दिखाएँ", - "cal_editor_object_hint": "YAML से एक ऑब्जेक्ट पहले से चुनें: object_filter: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।" + "cal_editor_object_hint": "YAML से एक ऑब्जेक्ट पहले से चुनें: object_filter: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।", + "object_history_section": "इतिहास (सभी कार्य)", + "object_history_all_tasks": "सभी कार्य", + "object_history_empty": "इस अवधि में कोई प्रविष्टि नहीं है।", + "object_history_cap_note": "इतिहास प्रति कार्य अधिकतम 500 प्रविष्टियाँ रखता है — बहुत पुरानी प्रविष्टियाँ अनुपस्थित हो सकती हैं।", + "service_record_title": "सेवा रिकॉर्ड", + "service_record_print": "सेवा रिकॉर्ड (PDF)", + "date": "दिनांक", + "service_record_entries": "प्रविष्टियाँ", + "completed_by": "द्वारा पूर्ण", + "date_from": "से", + "date_to": "तक" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hu.json b/custom_components/maintenance_supporter/frontend-src/locales/hu.json index e111ee8e..5abfabcc 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hu.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Alapértelmezett felhasználószűrő", "cal_editor_my_tasks": "Saját feladatok (aktuális felhasználó)", "cal_editor_show_object_filter": "Objektumszűrő megjelenítése", - "cal_editor_object_hint": "Előválasztás YAML-lel: object_filter: \"\" — vagy névlista, hogy a kártya több objektumra korlátozódjon." + "cal_editor_object_hint": "Előválasztás YAML-lel: object_filter: \"\" — vagy névlista, hogy a kártya több objektumra korlátozódjon.", + "object_history_section": "Előzmények (összes feladat)", + "object_history_all_tasks": "Összes feladat", + "object_history_empty": "Nincs bejegyzés ebben az időszakban.", + "object_history_cap_note": "Az előzmények feladatonként legfeljebb 500 bejegyzést őriznek meg — a nagyon régiek hiányozhatnak.", + "service_record_title": "Szervizkönyv", + "service_record_print": "Szervizkönyv (PDF)", + "date": "Dátum", + "service_record_entries": "bejegyzés", + "completed_by": "Elvégezte", + "date_from": "Ettől", + "date_to": "Eddig" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/it.json b/custom_components/maintenance_supporter/frontend-src/locales/it.json index e63b8634..d6c95703 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/it.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/it.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro utente predefinito", "cal_editor_my_tasks": "Le mie attività (utente attuale)", "cal_editor_show_object_filter": "Mostra il filtro oggetto", - "cal_editor_object_hint": "Preseleziona un oggetto via YAML: object_filter: \"\" — o un elenco di nomi per limitare la scheda a più oggetti." + "cal_editor_object_hint": "Preseleziona un oggetto via YAML: object_filter: \"\" — o un elenco di nomi per limitare la scheda a più oggetti.", + "object_history_section": "Cronologia (tutte le attività)", + "object_history_all_tasks": "Tutte le attività", + "object_history_empty": "Nessuna voce in questo periodo.", + "object_history_cap_note": "La cronologia conserva fino a 500 voci per attività — le voci molto vecchie potrebbero mancare.", + "service_record_title": "Libretto di manutenzione", + "service_record_print": "Libretto di manutenzione (PDF)", + "date": "Data", + "service_record_entries": "voci", + "completed_by": "Completato da", + "date_from": "Dal", + "date_to": "Al" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ja.json b/custom_components/maintenance_supporter/frontend-src/locales/ja.json index 0d246858..52e5c56b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ja.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "既定のユーザーフィルター", "cal_editor_my_tasks": "自分のタスク(現在のユーザー)", "cal_editor_show_object_filter": "オブジェクトフィルターを表示", - "cal_editor_object_hint": "YAML でオブジェクトを事前選択:object_filter: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。" + "cal_editor_object_hint": "YAML でオブジェクトを事前選択:object_filter: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。", + "object_history_section": "履歴(全タスク)", + "object_history_all_tasks": "すべてのタスク", + "object_history_empty": "この期間の記録はありません。", + "object_history_cap_note": "履歴はタスクごとに最大500件まで保持されます。非常に古い記録は含まれない場合があります。", + "service_record_title": "整備記録", + "service_record_print": "整備記録(PDF)", + "date": "日付", + "service_record_entries": "件", + "completed_by": "実施者", + "date_from": "開始", + "date_to": "終了" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ko.json b/custom_components/maintenance_supporter/frontend-src/locales/ko.json index a5843332..d5d7f2d1 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ko.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "기본 사용자 필터", "cal_editor_my_tasks": "내 작업 (현재 사용자)", "cal_editor_show_object_filter": "객체 필터 표시", - "cal_editor_object_hint": "YAML로 객체를 미리 선택: object_filter: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다." + "cal_editor_object_hint": "YAML로 객체를 미리 선택: object_filter: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다.", + "object_history_section": "기록(전체 작업)", + "object_history_all_tasks": "모든 작업", + "object_history_empty": "이 기간에 기록이 없습니다.", + "object_history_cap_note": "기록은 작업당 최대 500건까지 보관됩니다. 아주 오래된 기록은 없을 수 있습니다.", + "service_record_title": "정비 기록", + "service_record_print": "정비 기록(PDF)", + "date": "날짜", + "service_record_entries": "건", + "completed_by": "수행자", + "date_from": "시작", + "date_to": "종료" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nb.json b/custom_components/maintenance_supporter/frontend-src/locales/nb.json index 52f060f6..2725f65a 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nb.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standard brukerfilter", "cal_editor_my_tasks": "Mine oppgaver (gjeldende bruker)", "cal_editor_show_object_filter": "Vis objektfilter", - "cal_editor_object_hint": "Forhåndsvelg et objekt via YAML: object_filter: \"\" — eller en liste med navn for å begrense kortet til flere objekter." + "cal_editor_object_hint": "Forhåndsvelg et objekt via YAML: object_filter: \"\" — eller en liste med navn for å begrense kortet til flere objekter.", + "object_history_section": "Historikk (alle oppgaver)", + "object_history_all_tasks": "Alle oppgaver", + "object_history_empty": "Ingen oppføringer i denne perioden.", + "object_history_cap_note": "Historikken beholder opptil 500 oppføringer per oppgave — svært gamle oppføringer kan mangle.", + "service_record_title": "Servicehefte", + "service_record_print": "Servicehefte (PDF)", + "date": "Dato", + "service_record_entries": "oppføringer", + "completed_by": "Utført av", + "date_from": "Fra", + "date_to": "Til" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nl.json b/custom_components/maintenance_supporter/frontend-src/locales/nl.json index 1094357b..42f58f20 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nl.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standaard gebruikersfilter", "cal_editor_my_tasks": "Mijn taken (huidige gebruiker)", "cal_editor_show_object_filter": "Objectfilter tonen", - "cal_editor_object_hint": "Selecteer een object vooraf via YAML: object_filter: \"\" — of een lijst met namen om de kaart tot meerdere objecten te beperken." + "cal_editor_object_hint": "Selecteer een object vooraf via YAML: object_filter: \"\" — of een lijst met namen om de kaart tot meerdere objecten te beperken.", + "object_history_section": "Geschiedenis (alle taken)", + "object_history_all_tasks": "Alle taken", + "object_history_empty": "Geen items in deze periode.", + "object_history_cap_note": "De geschiedenis bewaart maximaal 500 items per taak — zeer oude items kunnen ontbreken.", + "service_record_title": "Onderhoudsboekje", + "service_record_print": "Onderhoudsboekje (PDF)", + "date": "Datum", + "service_record_entries": "items", + "completed_by": "Voltooid door", + "date_from": "Van", + "date_to": "Tot" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pl.json b/custom_components/maintenance_supporter/frontend-src/locales/pl.json index 1b3ef9bc..59e010e8 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pl.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Domyślny filtr użytkownika", "cal_editor_my_tasks": "Moje zadania (bieżący użytkownik)", "cal_editor_show_object_filter": "Pokaż filtr obiektu", - "cal_editor_object_hint": "Wybierz obiekt w YAML: object_filter: \"\" — lub listę nazw, aby ograniczyć kartę do kilku obiektów." + "cal_editor_object_hint": "Wybierz obiekt w YAML: object_filter: \"\" — lub listę nazw, aby ograniczyć kartę do kilku obiektów.", + "object_history_section": "Historia (wszystkie zadania)", + "object_history_all_tasks": "Wszystkie zadania", + "object_history_empty": "Brak wpisów w tym okresie.", + "object_history_cap_note": "Historia przechowuje do 500 wpisów na zadanie — bardzo stare wpisy mogą brakować.", + "service_record_title": "Książka serwisowa", + "service_record_print": "Książka serwisowa (PDF)", + "date": "Data", + "service_record_entries": "wpisów", + "completed_by": "Wykonane przez", + "date_from": "Od", + "date_to": "Do" } 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 8dec904f..7a6e7891 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro de usuário padrão", "cal_editor_my_tasks": "Minhas tarefas (usuário atual)", "cal_editor_show_object_filter": "Mostrar filtro de objeto", - "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos." + "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos.", + "object_history_section": "Histórico (todas as tarefas)", + "object_history_all_tasks": "Todas as tarefas", + "object_history_empty": "Sem registros neste período.", + "object_history_cap_note": "O histórico guarda até 500 registros por tarefa — registros muito antigos podem faltar.", + "service_record_title": "Registro de manutenção", + "service_record_print": "Registro de manutenção (PDF)", + "date": "Data", + "service_record_entries": "registros", + "completed_by": "Concluído por", + "date_from": "De", + "date_to": "Até" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt.json b/custom_components/maintenance_supporter/frontend-src/locales/pt.json index c21cceb8..5630eb7d 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro de utilizador predefinido", "cal_editor_my_tasks": "As minhas tarefas (utilizador atual)", "cal_editor_show_object_filter": "Mostrar filtro de objeto", - "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos." + "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos.", + "object_history_section": "Histórico (todas as tarefas)", + "object_history_all_tasks": "Todas as tarefas", + "object_history_empty": "Sem registos neste período.", + "object_history_cap_note": "O histórico guarda até 500 registos por tarefa — registos muito antigos podem faltar.", + "service_record_title": "Registo de manutenção", + "service_record_print": "Registo de manutenção (PDF)", + "date": "Data", + "service_record_entries": "registos", + "completed_by": "Concluído por", + "date_from": "De", + "date_to": "Até" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ru.json b/custom_components/maintenance_supporter/frontend-src/locales/ru.json index 5cec30b5..2fd0aafd 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ru.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Фильтр пользователя по умолчанию", "cal_editor_my_tasks": "Мои задачи (текущий пользователь)", "cal_editor_show_object_filter": "Показывать фильтр объекта", - "cal_editor_object_hint": "Предварительный выбор объекта через YAML: object_filter: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами." + "cal_editor_object_hint": "Предварительный выбор объекта через YAML: object_filter: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами.", + "object_history_section": "История (все задачи)", + "object_history_all_tasks": "Все задачи", + "object_history_empty": "Нет записей за этот период.", + "object_history_cap_note": "История хранит до 500 записей на задачу — очень старые записи могут отсутствовать.", + "service_record_title": "Сервисная книжка", + "service_record_print": "Сервисная книжка (PDF)", + "date": "Дата", + "service_record_entries": "записей", + "completed_by": "Выполнил", + "date_from": "С", + "date_to": "По" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/sv.json b/custom_components/maintenance_supporter/frontend-src/locales/sv.json index f0810f3a..3d9a6cb9 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/sv.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standardanvändarfilter", "cal_editor_my_tasks": "Mina uppgifter (aktuell användare)", "cal_editor_show_object_filter": "Visa objektfilter", - "cal_editor_object_hint": "Förvälj ett objekt via YAML: object_filter: \"\" — eller en lista med namn för att begränsa kortet till flera objekt." + "cal_editor_object_hint": "Förvälj ett objekt via YAML: object_filter: \"\" — eller en lista med namn för att begränsa kortet till flera objekt.", + "object_history_section": "Historik (alla uppgifter)", + "object_history_all_tasks": "Alla uppgifter", + "object_history_empty": "Inga poster under denna period.", + "object_history_cap_note": "Historiken sparar upp till 500 poster per uppgift — mycket gamla poster kan saknas.", + "service_record_title": "Servicebok", + "service_record_print": "Servicebok (PDF)", + "date": "Datum", + "service_record_entries": "poster", + "completed_by": "Utförd av", + "date_from": "Från", + "date_to": "Till" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/tr.json b/custom_components/maintenance_supporter/frontend-src/locales/tr.json index cf1f7b05..5baebb85 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/tr.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Varsayılan kullanıcı filtresi", "cal_editor_my_tasks": "Görevlerim (geçerli kullanıcı)", "cal_editor_show_object_filter": "Nesne filtresini göster", - "cal_editor_object_hint": "YAML ile bir nesne önceden seçin: object_filter: \"\" — veya kartı birden çok nesneyle sınırlamak için ad listesi." + "cal_editor_object_hint": "YAML ile bir nesne önceden seçin: object_filter: \"\" — veya kartı birden çok nesneyle sınırlamak için ad listesi.", + "object_history_section": "Geçmiş (tüm görevler)", + "object_history_all_tasks": "Tüm görevler", + "object_history_empty": "Bu aralıkta kayıt yok.", + "object_history_cap_note": "Geçmiş, görev başına en fazla 500 kayıt tutar — çok eski kayıtlar eksik olabilir.", + "service_record_title": "Servis kaydı", + "service_record_print": "Servis kaydı (PDF)", + "date": "Tarih", + "service_record_entries": "kayıt", + "completed_by": "Tamamlayan", + "date_from": "Başlangıç", + "date_to": "Bitiş" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/uk.json b/custom_components/maintenance_supporter/frontend-src/locales/uk.json index 0f903f29..5b36b24b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/uk.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Типовий фільтр користувача", "cal_editor_my_tasks": "Мої завдання (поточний користувач)", "cal_editor_show_object_filter": "Показувати фільтр об'єкта", - "cal_editor_object_hint": "Попередній вибір об'єкта через YAML: object_filter: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами." + "cal_editor_object_hint": "Попередній вибір об'єкта через YAML: object_filter: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами.", + "object_history_section": "Історія (усі завдання)", + "object_history_all_tasks": "Усі завдання", + "object_history_empty": "Немає записів за цей період.", + "object_history_cap_note": "Історія зберігає до 500 записів на завдання — дуже старі записи можуть бути відсутні.", + "service_record_title": "Сервісна книжка", + "service_record_print": "Сервісна книжка (PDF)", + "date": "Дата", + "service_record_entries": "записів", + "completed_by": "Виконав", + "date_from": "Від", + "date_to": "До" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/zh.json b/custom_components/maintenance_supporter/frontend-src/locales/zh.json index a6316daf..2f0088b4 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/zh.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "默认用户筛选", "cal_editor_my_tasks": "我的任务(当前用户)", "cal_editor_show_object_filter": "显示对象筛选", - "cal_editor_object_hint": "通过 YAML 预选对象:object_filter: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。" + "cal_editor_object_hint": "通过 YAML 预选对象:object_filter: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。", + "object_history_section": "历史(全部任务)", + "object_history_all_tasks": "全部任务", + "object_history_empty": "此时间段内没有记录。", + "object_history_cap_note": "每个任务的历史最多保留 500 条记录,很早的记录可能缺失。", + "service_record_title": "维护记录", + "service_record_print": "维护记录(PDF)", + "date": "日期", + "service_record_entries": "条记录", + "completed_by": "完成人", + "date_from": "从", + "date_to": "至" } diff --git a/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts b/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts index 1ad2dc90..9f8d5f56 100644 --- a/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/maintenance-card.ts @@ -429,7 +429,7 @@ export class MaintenanceSupporterCard extends LitElement { openCreateTaskDialog()} + @click=${() => openCreateTaskDialog("", this._objects)} > diff --git a/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts b/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts index 91e3e5cd..00c34581 100644 --- a/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts +++ b/custom_components/maintenance_supporter/frontend-src/maintenance-panel.ts @@ -44,6 +44,7 @@ import { UserService } from "./user-service"; import type { MaintenanceObjectDialog } from "./components/object-dialog"; import "./components/documents-section"; import "./components/parts-section"; +import "./components/object-history-section"; import "./components/task-documents"; import type { MaintenanceTaskDialog } from "./components/task-dialog"; import type { MaintenanceCompleteDialog } from "./components/complete-dialog"; @@ -3078,7 +3079,8 @@ export class MaintenanceSupporterPanel extends LitElement { const color = pct >= 100 ? "var(--error-color, #f44336)" : pct >= b.alert_threshold_pct ? "var(--warning-color, #ff9800)" : "var(--success-color, #4caf50)"; return html`
- ${spent.toFixed(2)} / ${budget.toFixed(0)} ${cs} + ${spent.toFixed(2)} ${cs} + / ${budget.toFixed(0)} ${cs}
${label}
@@ -3341,6 +3343,16 @@ export class MaintenanceSupporterPanel extends LitElement { .currencySymbol=${this._currencySymbol} @parts-changed=${() => this._loadData()} > + + this._userService?.getUserName(id) ?? null} + @open-task=${(e: CustomEvent<{ taskId: string }>) => this._showTask(obj.entry_id, e.detail.taskId)} + > `; } diff --git a/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts b/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts index 44e3422d..178b4cc0 100644 --- a/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts +++ b/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts @@ -151,6 +151,13 @@ function progressSpec(task: MaintenanceTask, unit: string, ctx: SparklineContext case "counter": { const target = tc.trigger_target_value; if (target == null || target <= 0) return null; + if (!tc.trigger_delta_mode) { + // Non-delta counters count from zero since the last reset — the raw + // value IS the progress. Subtracting a baseline here showed a fresh + // cycle as stuck at 0 (progress.ts branches the same way; bug audit + // 2026-08-22). + return { progress: Math.max(0, cur), target, unit, meter: null }; + } const base = counterBaseline(task, rawStatsPoints(task, ctx)); return { progress: Math.max(0, cur - (base?.value ?? cur)), target, unit, meter: cur }; } @@ -296,13 +303,17 @@ function renderChart(task: MaintenanceTask, unit: string, ctx: SparklineContext) let forceZero = false; if (triggerType === "counter" && tc.trigger_target_value != null && points.length) { // Progress domain: cumulative since the last service, never negative. - const base = counterBaseline(task, points); - if (base) { - if (base.ts != null) { - const kept = points.filter((p) => p.ts >= base.ts!); - if (kept.length >= 2) points = kept; + // Baseline subtraction is a DELTA-mode concept — a non-delta counter's + // raw value already is the cycle progress (bug audit 2026-08-22). + if (tc.trigger_delta_mode) { + const base = counterBaseline(task, points); + if (base) { + if (base.ts != null) { + const kept = points.filter((p) => p.ts >= base.ts!); + if (kept.length >= 2) points = kept; + } + points = points.map((p) => ({ ...p, val: Math.max(0, p.val - base.value) })); } - points = points.map((p) => ({ ...p, val: Math.max(0, p.val - base.value) })); } targetValue = tc.trigger_target_value; forceZero = true; diff --git a/custom_components/maintenance_supporter/frontend-src/styles.ts b/custom_components/maintenance_supporter/frontend-src/styles.ts index 9d1ff912..cafaf2fb 100644 --- a/custom_components/maintenance_supporter/frontend-src/styles.ts +++ b/custom_components/maintenance_supporter/frontend-src/styles.ts @@ -954,10 +954,20 @@ export const sharedStyles = css` } /* Budget KPI tiles in the stats strip (#125) — replaced the full-width - budget-bars row. */ + budget-bars row. The spent amount inherits .stat-value's full 24px bold + so the budget tiles read exactly like the other KPI chips (user report + 2026-08-24: the old 15px override made them visibly smaller); only the + "/ max" suffix stays secondary. */ .stat-item.budget-tile .budget-tile-value { - font-size: 15px; - padding-top: 5px; + white-space: nowrap; + } + /* The "/ max" ratio is its OWN small line between value and bar — inline + it overflowed the ~150px grid cell into the neighbouring tile once the + value took the full 24px. */ + .budget-tile-max { + font-size: 11px; + line-height: 1.2; + color: var(--secondary-text-color); white-space: nowrap; } .budget-tile-bar { @@ -1440,9 +1450,10 @@ export const sharedStyles = css` .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; } + /* Budget tiles on narrow screens (#125): the spent amount keeps the + full chip size (consistency, user report 2026-08-24); the "/ max" + suffix is hidden instead — the bar and the title carry the ratio. */ + .budget-tile-max { display: none; } .group-card { min-width: 0; max-width: 100%; } diff --git a/custom_components/maintenance_supporter/frontend-src/ws-errors.ts b/custom_components/maintenance_supporter/frontend-src/ws-errors.ts index d72e9d18..f9ee0583 100644 --- a/custom_components/maintenance_supporter/frontend-src/ws-errors.ts +++ b/custom_components/maintenance_supporter/frontend-src/ws-errors.ts @@ -16,6 +16,7 @@ import { t } from "./styles"; * the raw key as-is so the user can still tell which input was rejected. */ const FIELD_LABEL_KEYS: Record = { + entry_id: "object", name: "name", task_type: "maintenance_type", schedule_type: "schedule_type", diff --git a/custom_components/maintenance_supporter/frontend/locales/cs.json b/custom_components/maintenance_supporter/frontend/locales/cs.json index d1f39ab2..da868733 100644 --- a/custom_components/maintenance_supporter/frontend/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend/locales/cs.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Výchozí filtr uživatele", "cal_editor_my_tasks": "Moje úkoly (aktuální uživatel)", "cal_editor_show_object_filter": "Zobrazit filtr objektu", - "cal_editor_object_hint": "Předvyberte objekt přes YAML: object_filter: \"\" — nebo seznam názvů pro omezení karty na více objektů." + "cal_editor_object_hint": "Předvyberte objekt přes YAML: object_filter: \"\" — nebo seznam názvů pro omezení karty na více objektů.", + "object_history_section": "Historie (všechny úkoly)", + "object_history_all_tasks": "Všechny úkoly", + "object_history_empty": "V tomto období nejsou žádné záznamy.", + "object_history_cap_note": "Historie uchovává až 500 záznamů na úkol — velmi staré záznamy mohou chybět.", + "service_record_title": "Servisní knížka", + "service_record_print": "Servisní knížka (PDF)", + "date": "Datum", + "service_record_entries": "záznamů", + "completed_by": "Dokončil", + "date_from": "Od", + "date_to": "Do" } diff --git a/custom_components/maintenance_supporter/frontend/locales/da.json b/custom_components/maintenance_supporter/frontend/locales/da.json index 2ea7c6fc..c89c96ee 100644 --- a/custom_components/maintenance_supporter/frontend/locales/da.json +++ b/custom_components/maintenance_supporter/frontend/locales/da.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standard brugerfilter", "cal_editor_my_tasks": "Mine opgaver (aktuel bruger)", "cal_editor_show_object_filter": "Vis objektfilter", - "cal_editor_object_hint": "Forvælg et objekt via YAML: object_filter: \"\" — eller en liste af navne for at begrænse kortet til flere objekter." + "cal_editor_object_hint": "Forvælg et objekt via YAML: object_filter: \"\" — eller en liste af navne for at begrænse kortet til flere objekter.", + "object_history_section": "Historik (alle opgaver)", + "object_history_all_tasks": "Alle opgaver", + "object_history_empty": "Ingen poster i denne periode.", + "object_history_cap_note": "Historikken gemmer op til 500 poster pr. opgave — meget gamle poster kan mangle.", + "service_record_title": "Servicebog", + "service_record_print": "Servicebog (PDF)", + "date": "Dato", + "service_record_entries": "poster", + "completed_by": "Udført af", + "date_from": "Fra", + "date_to": "Til" } diff --git a/custom_components/maintenance_supporter/frontend/locales/de.json b/custom_components/maintenance_supporter/frontend/locales/de.json index 1415a8b8..ed01d558 100644 --- a/custom_components/maintenance_supporter/frontend/locales/de.json +++ b/custom_components/maintenance_supporter/frontend/locales/de.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standard-Benutzerfilter", "cal_editor_my_tasks": "Meine Aufgaben (aktueller Benutzer)", "cal_editor_show_object_filter": "Objektfilter-Dropdown anzeigen", - "cal_editor_object_hint": "Ein Objekt per YAML vorauswählen: object_filter: \"\" — oder eine Namensliste, um die Karte auf mehrere Objekte zu beschränken." + "cal_editor_object_hint": "Ein Objekt per YAML vorauswählen: object_filter: \"\" — oder eine Namensliste, um die Karte auf mehrere Objekte zu beschränken.", + "object_history_section": "Verlauf (alle Aufgaben)", + "object_history_all_tasks": "Alle Aufgaben", + "object_history_empty": "Keine Einträge in diesem Zeitraum.", + "object_history_cap_note": "Der Verlauf umfasst bis zu 500 Einträge pro Aufgabe — sehr alte Einträge können fehlen.", + "service_record_title": "Serviceheft", + "service_record_print": "Serviceheft (PDF)", + "date": "Datum", + "service_record_entries": "Einträge", + "completed_by": "Erledigt von", + "date_from": "Von", + "date_to": "Bis" } diff --git a/custom_components/maintenance_supporter/frontend/locales/en.json b/custom_components/maintenance_supporter/frontend/locales/en.json index 2f644f29..9de226a5 100644 --- a/custom_components/maintenance_supporter/frontend/locales/en.json +++ b/custom_components/maintenance_supporter/frontend/locales/en.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Default user filter", "cal_editor_my_tasks": "My tasks (current user)", "cal_editor_show_object_filter": "Show object filter dropdown", - "cal_editor_object_hint": "Pre-select one object via YAML: object_filter: \"\" — or a list of names to restrict the card to several objects." + "cal_editor_object_hint": "Pre-select one object via YAML: object_filter: \"\" — or a list of names to restrict the card to several objects.", + "object_history_section": "History (all tasks)", + "object_history_all_tasks": "All tasks", + "object_history_empty": "No entries in this range.", + "object_history_cap_note": "History keeps up to 500 entries per task — very old entries may be missing.", + "service_record_title": "Service record", + "service_record_print": "Service record (PDF)", + "date": "Date", + "service_record_entries": "entries", + "completed_by": "Completed by", + "date_from": "From", + "date_to": "To" } diff --git a/custom_components/maintenance_supporter/frontend/locales/es.json b/custom_components/maintenance_supporter/frontend/locales/es.json index 3ec66893..534eac42 100644 --- a/custom_components/maintenance_supporter/frontend/locales/es.json +++ b/custom_components/maintenance_supporter/frontend/locales/es.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro de usuario predeterminado", "cal_editor_my_tasks": "Mis tareas (usuario actual)", "cal_editor_show_object_filter": "Mostrar filtro de objeto", - "cal_editor_object_hint": "Preselecciona un objeto por YAML: object_filter: \"\" — o una lista de nombres para limitar la tarjeta a varios objetos." + "cal_editor_object_hint": "Preselecciona un objeto por YAML: object_filter: \"\" — o una lista de nombres para limitar la tarjeta a varios objetos.", + "object_history_section": "Historial (todas las tareas)", + "object_history_all_tasks": "Todas las tareas", + "object_history_empty": "No hay entradas en este periodo.", + "object_history_cap_note": "El historial conserva hasta 500 entradas por tarea; las entradas muy antiguas pueden faltar.", + "service_record_title": "Registro de mantenimiento", + "service_record_print": "Registro de mantenimiento (PDF)", + "date": "Fecha", + "service_record_entries": "entradas", + "completed_by": "Realizado por", + "date_from": "Desde", + "date_to": "Hasta" } diff --git a/custom_components/maintenance_supporter/frontend/locales/fi.json b/custom_components/maintenance_supporter/frontend/locales/fi.json index 7626cc42..a47f3652 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend/locales/fi.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Oletuskäyttäjäsuodatin", "cal_editor_my_tasks": "Omat tehtävät (nykyinen käyttäjä)", "cal_editor_show_object_filter": "Näytä kohdesuodatin", - "cal_editor_object_hint": "Esivalitse kohde YAML:lla: object_filter: \"\" — tai nimilista rajataksesi kortin useisiin kohteisiin." + "cal_editor_object_hint": "Esivalitse kohde YAML:lla: object_filter: \"\" — tai nimilista rajataksesi kortin useisiin kohteisiin.", + "object_history_section": "Historia (kaikki tehtävät)", + "object_history_all_tasks": "Kaikki tehtävät", + "object_history_empty": "Ei merkintöjä tällä aikavälillä.", + "object_history_cap_note": "Historia säilyttää enintään 500 merkintää tehtävää kohden — hyvin vanhat merkinnät voivat puuttua.", + "service_record_title": "Huoltokirja", + "service_record_print": "Huoltokirja (PDF)", + "date": "Päivämäärä", + "service_record_entries": "merkintää", + "completed_by": "Suorittanut", + "date_from": "Alkaen", + "date_to": "Asti" } diff --git a/custom_components/maintenance_supporter/frontend/locales/fr.json b/custom_components/maintenance_supporter/frontend/locales/fr.json index 2d66ef54..dea343cd 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend/locales/fr.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtre utilisateur par défaut", "cal_editor_my_tasks": "Mes tâches (utilisateur actuel)", "cal_editor_show_object_filter": "Afficher le filtre d'objet", - "cal_editor_object_hint": "Présélectionnez un objet via YAML : object_filter : \"\" — ou une liste de noms pour limiter la carte à plusieurs objets." + "cal_editor_object_hint": "Présélectionnez un objet via YAML : object_filter : \"\" — ou une liste de noms pour limiter la carte à plusieurs objets.", + "object_history_section": "Historique (toutes les tâches)", + "object_history_all_tasks": "Toutes les tâches", + "object_history_empty": "Aucune entrée sur cette période.", + "object_history_cap_note": "L'historique conserve jusqu'à 500 entrées par tâche — les entrées très anciennes peuvent manquer.", + "service_record_title": "Carnet d'entretien", + "service_record_print": "Carnet d'entretien (PDF)", + "date": "Date", + "service_record_entries": "entrées", + "completed_by": "Réalisé par", + "date_from": "Du", + "date_to": "Au" } diff --git a/custom_components/maintenance_supporter/frontend/locales/hi.json b/custom_components/maintenance_supporter/frontend/locales/hi.json index 569983e8..2109fbf5 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend/locales/hi.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "डिफ़ॉल्ट उपयोगकर्ता फ़िल्टर", "cal_editor_my_tasks": "मेरे कार्य (वर्तमान उपयोगकर्ता)", "cal_editor_show_object_filter": "ऑब्जेक्ट फ़िल्टर दिखाएँ", - "cal_editor_object_hint": "YAML से एक ऑब्जेक्ट पहले से चुनें: object_filter: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।" + "cal_editor_object_hint": "YAML से एक ऑब्जेक्ट पहले से चुनें: object_filter: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।", + "object_history_section": "इतिहास (सभी कार्य)", + "object_history_all_tasks": "सभी कार्य", + "object_history_empty": "इस अवधि में कोई प्रविष्टि नहीं है।", + "object_history_cap_note": "इतिहास प्रति कार्य अधिकतम 500 प्रविष्टियाँ रखता है — बहुत पुरानी प्रविष्टियाँ अनुपस्थित हो सकती हैं।", + "service_record_title": "सेवा रिकॉर्ड", + "service_record_print": "सेवा रिकॉर्ड (PDF)", + "date": "दिनांक", + "service_record_entries": "प्रविष्टियाँ", + "completed_by": "द्वारा पूर्ण", + "date_from": "से", + "date_to": "तक" } diff --git a/custom_components/maintenance_supporter/frontend/locales/hu.json b/custom_components/maintenance_supporter/frontend/locales/hu.json index e111ee8e..5abfabcc 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend/locales/hu.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Alapértelmezett felhasználószűrő", "cal_editor_my_tasks": "Saját feladatok (aktuális felhasználó)", "cal_editor_show_object_filter": "Objektumszűrő megjelenítése", - "cal_editor_object_hint": "Előválasztás YAML-lel: object_filter: \"\" — vagy névlista, hogy a kártya több objektumra korlátozódjon." + "cal_editor_object_hint": "Előválasztás YAML-lel: object_filter: \"\" — vagy névlista, hogy a kártya több objektumra korlátozódjon.", + "object_history_section": "Előzmények (összes feladat)", + "object_history_all_tasks": "Összes feladat", + "object_history_empty": "Nincs bejegyzés ebben az időszakban.", + "object_history_cap_note": "Az előzmények feladatonként legfeljebb 500 bejegyzést őriznek meg — a nagyon régiek hiányozhatnak.", + "service_record_title": "Szervizkönyv", + "service_record_print": "Szervizkönyv (PDF)", + "date": "Dátum", + "service_record_entries": "bejegyzés", + "completed_by": "Elvégezte", + "date_from": "Ettől", + "date_to": "Eddig" } diff --git a/custom_components/maintenance_supporter/frontend/locales/it.json b/custom_components/maintenance_supporter/frontend/locales/it.json index e63b8634..d6c95703 100644 --- a/custom_components/maintenance_supporter/frontend/locales/it.json +++ b/custom_components/maintenance_supporter/frontend/locales/it.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro utente predefinito", "cal_editor_my_tasks": "Le mie attività (utente attuale)", "cal_editor_show_object_filter": "Mostra il filtro oggetto", - "cal_editor_object_hint": "Preseleziona un oggetto via YAML: object_filter: \"\" — o un elenco di nomi per limitare la scheda a più oggetti." + "cal_editor_object_hint": "Preseleziona un oggetto via YAML: object_filter: \"\" — o un elenco di nomi per limitare la scheda a più oggetti.", + "object_history_section": "Cronologia (tutte le attività)", + "object_history_all_tasks": "Tutte le attività", + "object_history_empty": "Nessuna voce in questo periodo.", + "object_history_cap_note": "La cronologia conserva fino a 500 voci per attività — le voci molto vecchie potrebbero mancare.", + "service_record_title": "Libretto di manutenzione", + "service_record_print": "Libretto di manutenzione (PDF)", + "date": "Data", + "service_record_entries": "voci", + "completed_by": "Completato da", + "date_from": "Dal", + "date_to": "Al" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ja.json b/custom_components/maintenance_supporter/frontend/locales/ja.json index 0d246858..52e5c56b 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend/locales/ja.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "既定のユーザーフィルター", "cal_editor_my_tasks": "自分のタスク(現在のユーザー)", "cal_editor_show_object_filter": "オブジェクトフィルターを表示", - "cal_editor_object_hint": "YAML でオブジェクトを事前選択:object_filter: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。" + "cal_editor_object_hint": "YAML でオブジェクトを事前選択:object_filter: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。", + "object_history_section": "履歴(全タスク)", + "object_history_all_tasks": "すべてのタスク", + "object_history_empty": "この期間の記録はありません。", + "object_history_cap_note": "履歴はタスクごとに最大500件まで保持されます。非常に古い記録は含まれない場合があります。", + "service_record_title": "整備記録", + "service_record_print": "整備記録(PDF)", + "date": "日付", + "service_record_entries": "件", + "completed_by": "実施者", + "date_from": "開始", + "date_to": "終了" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ko.json b/custom_components/maintenance_supporter/frontend/locales/ko.json index a5843332..d5d7f2d1 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend/locales/ko.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "기본 사용자 필터", "cal_editor_my_tasks": "내 작업 (현재 사용자)", "cal_editor_show_object_filter": "객체 필터 표시", - "cal_editor_object_hint": "YAML로 객체를 미리 선택: object_filter: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다." + "cal_editor_object_hint": "YAML로 객체를 미리 선택: object_filter: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다.", + "object_history_section": "기록(전체 작업)", + "object_history_all_tasks": "모든 작업", + "object_history_empty": "이 기간에 기록이 없습니다.", + "object_history_cap_note": "기록은 작업당 최대 500건까지 보관됩니다. 아주 오래된 기록은 없을 수 있습니다.", + "service_record_title": "정비 기록", + "service_record_print": "정비 기록(PDF)", + "date": "날짜", + "service_record_entries": "건", + "completed_by": "수행자", + "date_from": "시작", + "date_to": "종료" } diff --git a/custom_components/maintenance_supporter/frontend/locales/nb.json b/custom_components/maintenance_supporter/frontend/locales/nb.json index 52f060f6..2725f65a 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend/locales/nb.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standard brukerfilter", "cal_editor_my_tasks": "Mine oppgaver (gjeldende bruker)", "cal_editor_show_object_filter": "Vis objektfilter", - "cal_editor_object_hint": "Forhåndsvelg et objekt via YAML: object_filter: \"\" — eller en liste med navn for å begrense kortet til flere objekter." + "cal_editor_object_hint": "Forhåndsvelg et objekt via YAML: object_filter: \"\" — eller en liste med navn for å begrense kortet til flere objekter.", + "object_history_section": "Historikk (alle oppgaver)", + "object_history_all_tasks": "Alle oppgaver", + "object_history_empty": "Ingen oppføringer i denne perioden.", + "object_history_cap_note": "Historikken beholder opptil 500 oppføringer per oppgave — svært gamle oppføringer kan mangle.", + "service_record_title": "Servicehefte", + "service_record_print": "Servicehefte (PDF)", + "date": "Dato", + "service_record_entries": "oppføringer", + "completed_by": "Utført av", + "date_from": "Fra", + "date_to": "Til" } diff --git a/custom_components/maintenance_supporter/frontend/locales/nl.json b/custom_components/maintenance_supporter/frontend/locales/nl.json index 1094357b..42f58f20 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend/locales/nl.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standaard gebruikersfilter", "cal_editor_my_tasks": "Mijn taken (huidige gebruiker)", "cal_editor_show_object_filter": "Objectfilter tonen", - "cal_editor_object_hint": "Selecteer een object vooraf via YAML: object_filter: \"\" — of een lijst met namen om de kaart tot meerdere objecten te beperken." + "cal_editor_object_hint": "Selecteer een object vooraf via YAML: object_filter: \"\" — of een lijst met namen om de kaart tot meerdere objecten te beperken.", + "object_history_section": "Geschiedenis (alle taken)", + "object_history_all_tasks": "Alle taken", + "object_history_empty": "Geen items in deze periode.", + "object_history_cap_note": "De geschiedenis bewaart maximaal 500 items per taak — zeer oude items kunnen ontbreken.", + "service_record_title": "Onderhoudsboekje", + "service_record_print": "Onderhoudsboekje (PDF)", + "date": "Datum", + "service_record_entries": "items", + "completed_by": "Voltooid door", + "date_from": "Van", + "date_to": "Tot" } diff --git a/custom_components/maintenance_supporter/frontend/locales/pl.json b/custom_components/maintenance_supporter/frontend/locales/pl.json index 1b3ef9bc..59e010e8 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend/locales/pl.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Domyślny filtr użytkownika", "cal_editor_my_tasks": "Moje zadania (bieżący użytkownik)", "cal_editor_show_object_filter": "Pokaż filtr obiektu", - "cal_editor_object_hint": "Wybierz obiekt w YAML: object_filter: \"\" — lub listę nazw, aby ograniczyć kartę do kilku obiektów." + "cal_editor_object_hint": "Wybierz obiekt w YAML: object_filter: \"\" — lub listę nazw, aby ograniczyć kartę do kilku obiektów.", + "object_history_section": "Historia (wszystkie zadania)", + "object_history_all_tasks": "Wszystkie zadania", + "object_history_empty": "Brak wpisów w tym okresie.", + "object_history_cap_note": "Historia przechowuje do 500 wpisów na zadanie — bardzo stare wpisy mogą brakować.", + "service_record_title": "Książka serwisowa", + "service_record_print": "Książka serwisowa (PDF)", + "date": "Data", + "service_record_entries": "wpisów", + "completed_by": "Wykonane przez", + "date_from": "Od", + "date_to": "Do" } diff --git a/custom_components/maintenance_supporter/frontend/locales/pt-br.json b/custom_components/maintenance_supporter/frontend/locales/pt-br.json index 8dec904f..7a6e7891 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt-br.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro de usuário padrão", "cal_editor_my_tasks": "Minhas tarefas (usuário atual)", "cal_editor_show_object_filter": "Mostrar filtro de objeto", - "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos." + "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos.", + "object_history_section": "Histórico (todas as tarefas)", + "object_history_all_tasks": "Todas as tarefas", + "object_history_empty": "Sem registros neste período.", + "object_history_cap_note": "O histórico guarda até 500 registros por tarefa — registros muito antigos podem faltar.", + "service_record_title": "Registro de manutenção", + "service_record_print": "Registro de manutenção (PDF)", + "date": "Data", + "service_record_entries": "registros", + "completed_by": "Concluído por", + "date_from": "De", + "date_to": "Até" } diff --git a/custom_components/maintenance_supporter/frontend/locales/pt.json b/custom_components/maintenance_supporter/frontend/locales/pt.json index c21cceb8..5630eb7d 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Filtro de utilizador predefinido", "cal_editor_my_tasks": "As minhas tarefas (utilizador atual)", "cal_editor_show_object_filter": "Mostrar filtro de objeto", - "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos." + "cal_editor_object_hint": "Pré-selecione um objeto via YAML: object_filter: \"\" — ou uma lista de nomes para limitar o cartão a vários objetos.", + "object_history_section": "Histórico (todas as tarefas)", + "object_history_all_tasks": "Todas as tarefas", + "object_history_empty": "Sem registos neste período.", + "object_history_cap_note": "O histórico guarda até 500 registos por tarefa — registos muito antigos podem faltar.", + "service_record_title": "Registo de manutenção", + "service_record_print": "Registo de manutenção (PDF)", + "date": "Data", + "service_record_entries": "registos", + "completed_by": "Concluído por", + "date_from": "De", + "date_to": "Até" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ru.json b/custom_components/maintenance_supporter/frontend/locales/ru.json index 5cec30b5..2fd0aafd 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend/locales/ru.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Фильтр пользователя по умолчанию", "cal_editor_my_tasks": "Мои задачи (текущий пользователь)", "cal_editor_show_object_filter": "Показывать фильтр объекта", - "cal_editor_object_hint": "Предварительный выбор объекта через YAML: object_filter: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами." + "cal_editor_object_hint": "Предварительный выбор объекта через YAML: object_filter: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами.", + "object_history_section": "История (все задачи)", + "object_history_all_tasks": "Все задачи", + "object_history_empty": "Нет записей за этот период.", + "object_history_cap_note": "История хранит до 500 записей на задачу — очень старые записи могут отсутствовать.", + "service_record_title": "Сервисная книжка", + "service_record_print": "Сервисная книжка (PDF)", + "date": "Дата", + "service_record_entries": "записей", + "completed_by": "Выполнил", + "date_from": "С", + "date_to": "По" } diff --git a/custom_components/maintenance_supporter/frontend/locales/sv.json b/custom_components/maintenance_supporter/frontend/locales/sv.json index f0810f3a..3d9a6cb9 100644 --- a/custom_components/maintenance_supporter/frontend/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend/locales/sv.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Standardanvändarfilter", "cal_editor_my_tasks": "Mina uppgifter (aktuell användare)", "cal_editor_show_object_filter": "Visa objektfilter", - "cal_editor_object_hint": "Förvälj ett objekt via YAML: object_filter: \"\" — eller en lista med namn för att begränsa kortet till flera objekt." + "cal_editor_object_hint": "Förvälj ett objekt via YAML: object_filter: \"\" — eller en lista med namn för att begränsa kortet till flera objekt.", + "object_history_section": "Historik (alla uppgifter)", + "object_history_all_tasks": "Alla uppgifter", + "object_history_empty": "Inga poster under denna period.", + "object_history_cap_note": "Historiken sparar upp till 500 poster per uppgift — mycket gamla poster kan saknas.", + "service_record_title": "Servicebok", + "service_record_print": "Servicebok (PDF)", + "date": "Datum", + "service_record_entries": "poster", + "completed_by": "Utförd av", + "date_from": "Från", + "date_to": "Till" } diff --git a/custom_components/maintenance_supporter/frontend/locales/tr.json b/custom_components/maintenance_supporter/frontend/locales/tr.json index cf1f7b05..5baebb85 100644 --- a/custom_components/maintenance_supporter/frontend/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend/locales/tr.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Varsayılan kullanıcı filtresi", "cal_editor_my_tasks": "Görevlerim (geçerli kullanıcı)", "cal_editor_show_object_filter": "Nesne filtresini göster", - "cal_editor_object_hint": "YAML ile bir nesne önceden seçin: object_filter: \"\" — veya kartı birden çok nesneyle sınırlamak için ad listesi." + "cal_editor_object_hint": "YAML ile bir nesne önceden seçin: object_filter: \"\" — veya kartı birden çok nesneyle sınırlamak için ad listesi.", + "object_history_section": "Geçmiş (tüm görevler)", + "object_history_all_tasks": "Tüm görevler", + "object_history_empty": "Bu aralıkta kayıt yok.", + "object_history_cap_note": "Geçmiş, görev başına en fazla 500 kayıt tutar — çok eski kayıtlar eksik olabilir.", + "service_record_title": "Servis kaydı", + "service_record_print": "Servis kaydı (PDF)", + "date": "Tarih", + "service_record_entries": "kayıt", + "completed_by": "Tamamlayan", + "date_from": "Başlangıç", + "date_to": "Bitiş" } diff --git a/custom_components/maintenance_supporter/frontend/locales/uk.json b/custom_components/maintenance_supporter/frontend/locales/uk.json index 0f903f29..5b36b24b 100644 --- a/custom_components/maintenance_supporter/frontend/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend/locales/uk.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "Типовий фільтр користувача", "cal_editor_my_tasks": "Мої завдання (поточний користувач)", "cal_editor_show_object_filter": "Показувати фільтр об'єкта", - "cal_editor_object_hint": "Попередній вибір об'єкта через YAML: object_filter: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами." + "cal_editor_object_hint": "Попередній вибір об'єкта через YAML: object_filter: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами.", + "object_history_section": "Історія (усі завдання)", + "object_history_all_tasks": "Усі завдання", + "object_history_empty": "Немає записів за цей період.", + "object_history_cap_note": "Історія зберігає до 500 записів на завдання — дуже старі записи можуть бути відсутні.", + "service_record_title": "Сервісна книжка", + "service_record_print": "Сервісна книжка (PDF)", + "date": "Дата", + "service_record_entries": "записів", + "completed_by": "Виконав", + "date_from": "Від", + "date_to": "До" } diff --git a/custom_components/maintenance_supporter/frontend/locales/zh.json b/custom_components/maintenance_supporter/frontend/locales/zh.json index a6316daf..2f0088b4 100644 --- a/custom_components/maintenance_supporter/frontend/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend/locales/zh.json @@ -878,5 +878,16 @@ "cal_editor_default_user": "默认用户筛选", "cal_editor_my_tasks": "我的任务(当前用户)", "cal_editor_show_object_filter": "显示对象筛选", - "cal_editor_object_hint": "通过 YAML 预选对象:object_filter: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。" + "cal_editor_object_hint": "通过 YAML 预选对象:object_filter: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。", + "object_history_section": "历史(全部任务)", + "object_history_all_tasks": "全部任务", + "object_history_empty": "此时间段内没有记录。", + "object_history_cap_note": "每个任务的历史最多保留 500 条记录,很早的记录可能缺失。", + "service_record_title": "维护记录", + "service_record_print": "维护记录(PDF)", + "date": "日期", + "service_record_entries": "条记录", + "completed_by": "完成人", + "date_from": "从", + "date_to": "至" } diff --git a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js index 49b1cc15..d63af4d3 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js @@ -1,9 +1,9 @@ -/*! maintenance_supporter frontend 2.63.1 */ -var bt=Object.defineProperty;var Yi=Object.getOwnPropertyDescriptor;var w=(a,r,e)=>()=>{if(e)throw e[0];try{return a&&(r=a(a=0)),r}catch(t){throw e=[t],t}};var Qi=(a,r)=>{for(var e in r)bt(a,e,{get:r[e],enumerable:!0})};var d=(a,r,e,t)=>{for(var i=t>1?void 0:t?Yi(r,e):r,n=a.length-1,o;n>=0;n--)(o=a[n])&&(i=(t?o(r,e,i):o(i))||i);return t&&i&&bt(r,e,i),i};var je,He,Je,xt,ye,wt,S,$t,Ze,Xe=w(()=>{je=globalThis,He=je.ShadowRoot&&(je.ShadyCSS===void 0||je.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Je=Symbol(),xt=new WeakMap,ye=class{constructor(r,e,t){if(this._$cssResult$=!0,t!==Je)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=r,this.t=e}get styleSheet(){let r=this.o,e=this.t;if(He&&r===void 0){let t=e!==void 0&&e.length===1;t&&(r=xt.get(e)),r===void 0&&((this.o=r=new CSSStyleSheet).replaceSync(this.cssText),t&&xt.set(e,r))}return r}toString(){return this.cssText}},wt=a=>new ye(typeof a=="string"?a:a+"",void 0,Je),S=(a,...r)=>{let e=a.length===1?a[0]:r.reduce((t,i,n)=>t+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new ye(e,a,Je)},$t=(a,r)=>{if(He)a.adoptedStyleSheets=r.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of r){let t=document.createElement("style"),i=je.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Ze=He?a=>a:a=>a instanceof CSSStyleSheet?(r=>{let e="";for(let t of r.cssRules)e+=t.cssText;return wt(e)})(a):a});var Ji,Zi,Xi,er,tr,ir,qe,kt,rr,sr,be,xe,Me,Et,K,we=w(()=>{Xe();Xe();({is:Ji,defineProperty:Zi,getOwnPropertyDescriptor:Xi,getOwnPropertyNames:er,getOwnPropertySymbols:tr,getPrototypeOf:ir}=Object),qe=globalThis,kt=qe.trustedTypes,rr=kt?kt.emptyScript:"",sr=qe.reactiveElementPolyfillSupport,be=(a,r)=>a,xe={toAttribute(a,r){switch(r){case Boolean:a=a?rr:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,r){let e=a;switch(r){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},Me=(a,r)=>!Ji(a,r),Et={attribute:!0,type:String,converter:xe,reflect:!1,useDefault:!1,hasChanged:Me};Symbol.metadata??=Symbol("metadata"),qe.litPropertyMetadata??=new WeakMap;K=class extends HTMLElement{static addInitializer(r){this._$Ei(),(this.l??=[]).push(r)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(r,e=Et){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(r)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(r,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(r,t,e);i!==void 0&&Zi(this.prototype,r,i)}}static getPropertyDescriptor(r,e,t){let{get:i,set:n}=Xi(this.prototype,r)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let p=i?.call(this);n?.call(this,o),this.requestUpdate(r,p,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(r){return this.elementProperties.get(r)??Et}static _$Ei(){if(this.hasOwnProperty(be("elementProperties")))return;let r=ir(this);r.finalize(),r.l!==void 0&&(this.l=[...r.l]),this.elementProperties=new Map(r.elementProperties)}static finalize(){if(this.hasOwnProperty(be("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(be("properties"))){let e=this.properties,t=[...er(e),...tr(e)];for(let i of t)this.createProperty(i,e[i])}let r=this[Symbol.metadata];if(r!==null){let e=litPropertyMetadata.get(r);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(r){let e=[];if(Array.isArray(r)){let t=new Set(r.flat(1/0).reverse());for(let i of t)e.unshift(Ze(i))}else r!==void 0&&e.push(Ze(r));return e}static _$Eu(r,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof r=="string"?r.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(r=>this.enableUpdating=r),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(r=>r(this))}addController(r){(this._$EO??=new Set).add(r),this.renderRoot!==void 0&&this.isConnected&&r.hostConnected?.()}removeController(r){this._$EO?.delete(r)}_$E_(){let r=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(r.set(t,this[t]),delete this[t]);r.size>0&&(this._$Ep=r)}createRenderRoot(){let r=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return $t(r,this.constructor.elementStyles),r}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(r=>r.hostConnected?.())}enableUpdating(r){}disconnectedCallback(){this._$EO?.forEach(r=>r.hostDisconnected?.())}attributeChangedCallback(r,e,t){this._$AK(r,t)}_$ET(r,e){let t=this.constructor.elementProperties.get(r),i=this.constructor._$Eu(r,t);if(i!==void 0&&t.reflect===!0){let n=(t.converter?.toAttribute!==void 0?t.converter:xe).toAttribute(e,t.type);this._$Em=r,n==null?this.removeAttribute(i):this.setAttribute(i,n),this._$Em=null}}_$AK(r,e){let t=this.constructor,i=t._$Eh.get(r);if(i!==void 0&&this._$Em!==i){let n=t.getPropertyOptions(i),o=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:xe;this._$Em=i;let p=o.fromAttribute(e,n.type);this[i]=p??this._$Ej?.get(i)??p,this._$Em=null}}requestUpdate(r,e,t,i=!1,n){if(r!==void 0){let o=this.constructor;if(i===!1&&(n=this[r]),t??=o.getPropertyOptions(r),!((t.hasChanged??Me)(n,e)||t.useDefault&&t.reflect&&n===this._$Ej?.get(r)&&!this.hasAttribute(o._$Eu(r,t))))return;this.C(r,e,t)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(r,e,{useDefault:t,reflect:i,wrapped:n},o){t&&!(this._$Ej??=new Map).has(r)&&(this._$Ej.set(r,o??e??this[r]),n!==!0||o!==void 0)||(this._$AL.has(r)||(this.hasUpdated||t||(e=void 0),this._$AL.set(r,e)),i===!0&&this._$Em!==r&&(this._$Eq??=new Set).add(r))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let r=this.scheduleUpdate();return r!=null&&await r,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,n]of this._$Ep)this[i]=n;this._$Ep=void 0}let t=this.constructor.elementProperties;if(t.size>0)for(let[i,n]of t){let{wrapped:o}=n,p=this[i];o!==!0||this._$AL.has(i)||p===void 0||this.C(i,void 0,n,p)}}let r=!1,e=this._$AL;try{r=this.shouldUpdate(e),r?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(t){throw r=!1,this._$EM(),t}r&&this._$AE(e)}willUpdate(r){}_$AE(r){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(r)),this.updated(r)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(r){return!0}update(r){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(r){}firstUpdated(r){}};K.elementStyles=[],K.shadowRootOptions={mode:"open"},K[be("elementProperties")]=new Map,K[be("finalized")]=new Map,sr?.({ReactiveElement:K}),(qe.reactiveElementVersions??=[]).push("2.1.2")});function Ht(a,r){if(!ot(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return At!==void 0?At.createHTML(r):r}function pe(a,r,e=a,t){if(r===ae)return r;let i=t!==void 0?e._$Co?.[t]:e._$Cl,n=Ee(r)?void 0:r._$litDirective$;return i?.constructor!==n&&(i?._$AO?.(!1),n===void 0?i=void 0:(i=new n(a),i._$AT(a,e,t)),t!==void 0?(e._$Co??=[])[t]=i:e._$Cl=i),i!==void 0&&(r=pe(a,i._$AS(a,r.values),i,t)),r}var nt,St,Oe,At,Rt,X,Nt,ar,se,ke,Ee,ot,nr,et,$e,Tt,Ct,ie,It,Lt,jt,lt,l,_e,ds,ae,h,Pt,re,or,Se,tt,Ae,ue,it,rt,st,at,lr,qt,De=w(()=>{nt=globalThis,St=a=>a,Oe=nt.trustedTypes,At=Oe?Oe.createPolicy("lit-html",{createHTML:a=>a}):void 0,Rt="$lit$",X=`lit$${Math.random().toFixed(9).slice(2)}$`,Nt="?"+X,ar=`<${Nt}>`,se=document,ke=()=>se.createComment(""),Ee=a=>a===null||typeof a!="object"&&typeof a!="function",ot=Array.isArray,nr=a=>ot(a)||typeof a?.[Symbol.iterator]=="function",et=`[ -\f\r]`,$e=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Tt=/-->/g,Ct=/>/g,ie=RegExp(`>|${et}(?:([^\\s"'>=/]+)(${et}*=${et}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),It=/'/g,Lt=/"/g,jt=/^(?:script|style|textarea|title)$/i,lt=a=>(r,...e)=>({_$litType$:a,strings:r,values:e}),l=lt(1),_e=lt(2),ds=lt(3),ae=Symbol.for("lit-noChange"),h=Symbol.for("lit-nothing"),Pt=new WeakMap,re=se.createTreeWalker(se,129);or=(a,r)=>{let e=a.length-1,t=[],i,n=r===2?"":r===3?"":"",o=$e;for(let p=0;p"?(o=i??$e,m=-1):f[1]===void 0?m=-2:(m=o.lastIndex-f[2].length,u=f[1],o=f[3]===void 0?ie:f[3]==='"'?Lt:It):o===Lt||o===It?o=ie:o===Tt||o===Ct?o=$e:(o=ie,i=void 0);let b=o===ie&&a[p+1].startsWith("/>")?" ":"";n+=o===$e?c+ar:m>=0?(t.push(u),c.slice(0,m)+Rt+c.slice(m)+X+b):c+X+(m===-2?p:b)}return[Ht(a,n+(a[e]||"")+(r===2?"":r===3?"":"")),t]},Se=class a{constructor({strings:r,_$litType$:e},t){let i;this.parts=[];let n=0,o=0,p=r.length-1,c=this.parts,[u,f]=or(r,e);if(this.el=a.createElement(u,t),re.currentNode=this.el.content,e===2||e===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes)}for(;(i=re.nextNode())!==null&&c.length0){i.textContent=Oe?Oe.emptyScript:"";for(let b=0;b2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=h}_$AI(r,e=this,t,i){let n=this.strings,o=!1;if(n===void 0)r=pe(this,r,e,0),o=!Ee(r)||r!==this._$AH&&r!==ae,o&&(this._$AH=r);else{let p=r,c,u;for(r=n[0],c=0;c{let t=e?.renderBefore??r,i=t._$litPart$;if(i===void 0){let n=e?.renderBefore??null;t._$litPart$=i=new Ae(r.insertBefore(ke(),n),n,void 0,e??{})}return i._$AI(a),i}});var dt,A,dr,Mt=w(()=>{we();we();De();De();dt=globalThis,A=class extends K{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let r=super.createRenderRoot();return this.renderOptions.renderBefore??=r.firstChild,r}update(r){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(r),this._$Do=qt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ae}};A._$litElement$=!0,A.finalized=!0,dt.litElementHydrateSupport?.({LitElement:A});dr=dt.litElementPolyfillSupport;dr?.({LitElement:A});(dt.litElementVersions??=[]).push("4.2.2")});var Ot=w(()=>{});var P=w(()=>{we();De();Mt();Ot()});var Dt=w(()=>{});function x(a){return(r,e)=>typeof e=="object"?pr(a,r,e):((t,i,n)=>{let o=i.hasOwnProperty(n);return i.constructor.createProperty(n,t),o?Object.getOwnPropertyDescriptor(i,n):void 0})(a,r,e)}var cr,pr,ct=w(()=>{we();cr={attribute:!0,type:String,converter:xe,reflect:!1,hasChanged:Me},pr=(a=cr,r,e)=>{let{kind:t,metadata:i}=e,n=globalThis.litPropertyMetadata.get(i);if(n===void 0&&globalThis.litPropertyMetadata.set(i,n=new Map),t==="setter"&&((a=Object.create(a)).wrapped=!0),n.set(e.name,a),t==="accessor"){let{name:o}=e;return{set(p){let c=r.get.call(this);r.set.call(this,p),this.requestUpdate(o,c,a,!0,p)},init(p){return p!==void 0&&this.C(o,void 0,a,p),p}}}if(t==="setter"){let{name:o}=e;return function(p){let c=this[o];r.call(this,p),this.requestUpdate(o,c,a,!0,p)}}throw Error("Unsupported decorator location: "+t)}});function _(a){return x({...a,state:!0,attribute:!1})}var zt=w(()=>{ct();});var Ft=w(()=>{});var he=w(()=>{});var Ut=w(()=>{he();});var Bt=w(()=>{he();});var Wt=w(()=>{he();});var Vt=w(()=>{he();});var Kt=w(()=>{he();});var U=w(()=>{Dt();ct();zt();Ft();Ut();Bt();Wt();Vt();Kt()});var ti,ei=w(()=>{ti={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",trigger_removed:"Trigger removed",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter +/*! maintenance_supporter frontend 2.64.0 */ +var xt=Object.defineProperty;var Zi=Object.getOwnPropertyDescriptor;var w=(a,r,e)=>()=>{if(e)throw e[0];try{return a&&(r=a(a=0)),r}catch(t){throw e=[t],t}};var Xi=(a,r)=>{for(var e in r)xt(a,e,{get:r[e],enumerable:!0})};var d=(a,r,e,t)=>{for(var i=t>1?void 0:t?Zi(r,e):r,n=a.length-1,o;n>=0;n--)(o=a[n])&&(i=(t?o(r,e,i):o(i))||i);return t&&i&&xt(r,e,i),i};var Ne,He,Ze,wt,be,$t,S,kt,Xe,et=w(()=>{Ne=globalThis,He=Ne.ShadowRoot&&(Ne.ShadyCSS===void 0||Ne.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Ze=Symbol(),wt=new WeakMap,be=class{constructor(r,e,t){if(this._$cssResult$=!0,t!==Ze)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=r,this.t=e}get styleSheet(){let r=this.o,e=this.t;if(He&&r===void 0){let t=e!==void 0&&e.length===1;t&&(r=wt.get(e)),r===void 0&&((this.o=r=new CSSStyleSheet).replaceSync(this.cssText),t&&wt.set(e,r))}return r}toString(){return this.cssText}},$t=a=>new be(typeof a=="string"?a:a+"",void 0,Ze),S=(a,...r)=>{let e=a.length===1?a[0]:r.reduce((t,i,n)=>t+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+a[n+1],a[0]);return new be(e,a,Ze)},kt=(a,r)=>{if(He)a.adoptedStyleSheets=r.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of r){let t=document.createElement("style"),i=Ne.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,a.appendChild(t)}},Xe=He?a=>a:a=>a instanceof CSSStyleSheet?(r=>{let e="";for(let t of r.cssRules)e+=t.cssText;return $t(e)})(a):a});var er,tr,ir,rr,sr,ar,qe,Et,nr,or,xe,we,Me,St,G,$e=w(()=>{et();et();({is:er,defineProperty:tr,getOwnPropertyDescriptor:ir,getOwnPropertyNames:rr,getOwnPropertySymbols:sr,getPrototypeOf:ar}=Object),qe=globalThis,Et=qe.trustedTypes,nr=Et?Et.emptyScript:"",or=qe.reactiveElementPolyfillSupport,xe=(a,r)=>a,we={toAttribute(a,r){switch(r){case Boolean:a=a?nr:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,r){let e=a;switch(r){case Boolean:e=a!==null;break;case Number:e=a===null?null:Number(a);break;case Object:case Array:try{e=JSON.parse(a)}catch{e=null}}return e}},Me=(a,r)=>!er(a,r),St={attribute:!0,type:String,converter:we,reflect:!1,useDefault:!1,hasChanged:Me};Symbol.metadata??=Symbol("metadata"),qe.litPropertyMetadata??=new WeakMap;G=class extends HTMLElement{static addInitializer(r){this._$Ei(),(this.l??=[]).push(r)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(r,e=St){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(r)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(r,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(r,t,e);i!==void 0&&tr(this.prototype,r,i)}}static getPropertyDescriptor(r,e,t){let{get:i,set:n}=ir(this.prototype,r)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let p=i?.call(this);n?.call(this,o),this.requestUpdate(r,p,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(r){return this.elementProperties.get(r)??St}static _$Ei(){if(this.hasOwnProperty(xe("elementProperties")))return;let r=ar(this);r.finalize(),r.l!==void 0&&(this.l=[...r.l]),this.elementProperties=new Map(r.elementProperties)}static finalize(){if(this.hasOwnProperty(xe("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(xe("properties"))){let e=this.properties,t=[...rr(e),...sr(e)];for(let i of t)this.createProperty(i,e[i])}let r=this[Symbol.metadata];if(r!==null){let e=litPropertyMetadata.get(r);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(r){let e=[];if(Array.isArray(r)){let t=new Set(r.flat(1/0).reverse());for(let i of t)e.unshift(Xe(i))}else r!==void 0&&e.push(Xe(r));return e}static _$Eu(r,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof r=="string"?r.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(r=>this.enableUpdating=r),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(r=>r(this))}addController(r){(this._$EO??=new Set).add(r),this.renderRoot!==void 0&&this.isConnected&&r.hostConnected?.()}removeController(r){this._$EO?.delete(r)}_$E_(){let r=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(r.set(t,this[t]),delete this[t]);r.size>0&&(this._$Ep=r)}createRenderRoot(){let r=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return kt(r,this.constructor.elementStyles),r}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(r=>r.hostConnected?.())}enableUpdating(r){}disconnectedCallback(){this._$EO?.forEach(r=>r.hostDisconnected?.())}attributeChangedCallback(r,e,t){this._$AK(r,t)}_$ET(r,e){let t=this.constructor.elementProperties.get(r),i=this.constructor._$Eu(r,t);if(i!==void 0&&t.reflect===!0){let n=(t.converter?.toAttribute!==void 0?t.converter:we).toAttribute(e,t.type);this._$Em=r,n==null?this.removeAttribute(i):this.setAttribute(i,n),this._$Em=null}}_$AK(r,e){let t=this.constructor,i=t._$Eh.get(r);if(i!==void 0&&this._$Em!==i){let n=t.getPropertyOptions(i),o=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:we;this._$Em=i;let p=o.fromAttribute(e,n.type);this[i]=p??this._$Ej?.get(i)??p,this._$Em=null}}requestUpdate(r,e,t,i=!1,n){if(r!==void 0){let o=this.constructor;if(i===!1&&(n=this[r]),t??=o.getPropertyOptions(r),!((t.hasChanged??Me)(n,e)||t.useDefault&&t.reflect&&n===this._$Ej?.get(r)&&!this.hasAttribute(o._$Eu(r,t))))return;this.C(r,e,t)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(r,e,{useDefault:t,reflect:i,wrapped:n},o){t&&!(this._$Ej??=new Map).has(r)&&(this._$Ej.set(r,o??e??this[r]),n!==!0||o!==void 0)||(this._$AL.has(r)||(this.hasUpdated||t||(e=void 0),this._$AL.set(r,e)),i===!0&&this._$Em!==r&&(this._$Eq??=new Set).add(r))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(e){Promise.reject(e)}let r=this.scheduleUpdate();return r!=null&&await r,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[i,n]of this._$Ep)this[i]=n;this._$Ep=void 0}let t=this.constructor.elementProperties;if(t.size>0)for(let[i,n]of t){let{wrapped:o}=n,p=this[i];o!==!0||this._$AL.has(i)||p===void 0||this.C(i,void 0,n,p)}}let r=!1,e=this._$AL;try{r=this.shouldUpdate(e),r?(this.willUpdate(e),this._$EO?.forEach(t=>t.hostUpdate?.()),this.update(e)):this._$EM()}catch(t){throw r=!1,this._$EM(),t}r&&this._$AE(e)}willUpdate(r){}_$AE(r){this._$EO?.forEach(e=>e.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(r)),this.updated(r)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(r){return!0}update(r){this._$Eq&&=this._$Eq.forEach(e=>this._$ET(e,this[e])),this._$EM()}updated(r){}firstUpdated(r){}};G.elementStyles=[],G.shadowRootOptions={mode:"open"},G[xe("elementProperties")]=new Map,G[xe("finalized")]=new Map,or?.({ReactiveElement:G}),(qe.reactiveElementVersions??=[]).push("2.1.2")});function qt(a,r){if(!lt(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return Tt!==void 0?Tt.createHTML(r):r}function _e(a,r,e=a,t){if(r===ae)return r;let i=t!==void 0?e._$Co?.[t]:e._$Cl,n=Se(r)?void 0:r._$litDirective$;return i?.constructor!==n&&(i?._$AO?.(!1),n===void 0?i=void 0:(i=new n(a),i._$AT(a,e,t)),t!==void 0?(e._$Co??=[])[t]=i:e._$Cl=i),i!==void 0&&(r=_e(a,i._$AS(a,r.values),i,t)),r}var ot,At,Oe,Tt,jt,ee,Nt,lr,se,Ee,Se,lt,dr,tt,ke,Ct,It,ie,Lt,Pt,Ht,dt,l,he,us,ae,h,Rt,re,cr,Ae,it,Te,ue,rt,st,at,nt,pr,Mt,De=w(()=>{ot=globalThis,At=a=>a,Oe=ot.trustedTypes,Tt=Oe?Oe.createPolicy("lit-html",{createHTML:a=>a}):void 0,jt="$lit$",ee=`lit$${Math.random().toFixed(9).slice(2)}$`,Nt="?"+ee,lr=`<${Nt}>`,se=document,Ee=()=>se.createComment(""),Se=a=>a===null||typeof a!="object"&&typeof a!="function",lt=Array.isArray,dr=a=>lt(a)||typeof a?.[Symbol.iterator]=="function",tt=`[ +\f\r]`,ke=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Ct=/-->/g,It=/>/g,ie=RegExp(`>|${tt}(?:([^\\s"'>=/]+)(${tt}*=${tt}*(?:[^ +\f\r"'\`<>=]|("|')|))|$)`,"g"),Lt=/'/g,Pt=/"/g,Ht=/^(?:script|style|textarea|title)$/i,dt=a=>(r,...e)=>({_$litType$:a,strings:r,values:e}),l=dt(1),he=dt(2),us=dt(3),ae=Symbol.for("lit-noChange"),h=Symbol.for("lit-nothing"),Rt=new WeakMap,re=se.createTreeWalker(se,129);cr=(a,r)=>{let e=a.length-1,t=[],i,n=r===2?"":r===3?"":"",o=ke;for(let p=0;p"?(o=i??ke,m=-1):f[1]===void 0?m=-2:(m=o.lastIndex-f[2].length,_=f[1],o=f[3]===void 0?ie:f[3]==='"'?Pt:Lt):o===Pt||o===Lt?o=ie:o===Ct||o===It?o=ke:(o=ie,i=void 0);let b=o===ie&&a[p+1].startsWith("/>")?" ":"";n+=o===ke?c+lr:m>=0?(t.push(_),c.slice(0,m)+jt+c.slice(m)+ee+b):c+ee+(m===-2?p:b)}return[qt(a,n+(a[e]||"")+(r===2?"":r===3?"":"")),t]},Ae=class a{constructor({strings:r,_$litType$:e},t){let i;this.parts=[];let n=0,o=0,p=r.length-1,c=this.parts,[_,f]=cr(r,e);if(this.el=a.createElement(_,t),re.currentNode=this.el.content,e===2||e===3){let m=this.el.content.firstChild;m.replaceWith(...m.childNodes)}for(;(i=re.nextNode())!==null&&c.length0){i.textContent=Oe?Oe.emptyScript:"";for(let b=0;b2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=h}_$AI(r,e=this,t,i){let n=this.strings,o=!1;if(n===void 0)r=_e(this,r,e,0),o=!Se(r)||r!==this._$AH&&r!==ae,o&&(this._$AH=r);else{let p=r,c,_;for(r=n[0],c=0;c{let t=e?.renderBefore??r,i=t._$litPart$;if(i===void 0){let n=e?.renderBefore??null;t._$litPart$=i=new Te(r.insertBefore(Ee(),n),n,void 0,e??{})}return i._$AI(a),i}});var ct,A,_r,Ot=w(()=>{$e();$e();De();De();ct=globalThis,A=class extends G{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let r=super.createRenderRoot();return this.renderOptions.renderBefore??=r.firstChild,r}update(r){let e=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(r),this._$Do=Mt(e,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return ae}};A._$litElement$=!0,A.finalized=!0,ct.litElementHydrateSupport?.({LitElement:A});_r=ct.litElementPolyfillSupport;_r?.({LitElement:A});(ct.litElementVersions??=[]).push("4.2.2")});var Dt=w(()=>{});var P=w(()=>{$e();De();Ot();Dt()});var Ft=w(()=>{});function x(a){return(r,e)=>typeof e=="object"?hr(a,r,e):((t,i,n)=>{let o=i.hasOwnProperty(n);return i.constructor.createProperty(n,t),o?Object.getOwnPropertyDescriptor(i,n):void 0})(a,r,e)}var ur,hr,pt=w(()=>{$e();ur={attribute:!0,type:String,converter:we,reflect:!1,hasChanged:Me},hr=(a=ur,r,e)=>{let{kind:t,metadata:i}=e,n=globalThis.litPropertyMetadata.get(i);if(n===void 0&&globalThis.litPropertyMetadata.set(i,n=new Map),t==="setter"&&((a=Object.create(a)).wrapped=!0),n.set(e.name,a),t==="accessor"){let{name:o}=e;return{set(p){let c=r.get.call(this);r.set.call(this,p),this.requestUpdate(o,c,a,!0,p)},init(p){return p!==void 0&&this.C(o,void 0,a,p),p}}}if(t==="setter"){let{name:o}=e;return function(p){let c=this[o];r.call(this,p),this.requestUpdate(o,c,a,!0,p)}}throw Error("Unsupported decorator location: "+t)}});function u(a){return x({...a,state:!0,attribute:!1})}var zt=w(()=>{pt();});var Ut=w(()=>{});var ge=w(()=>{});var Bt=w(()=>{ge();});var Wt=w(()=>{ge();});var Vt=w(()=>{ge();});var Kt=w(()=>{ge();});var Gt=w(()=>{ge();});var U=w(()=>{Ft();pt();zt();Ut();Bt();Wt();Vt();Kt();Gt()});function Yt(a,r){return!a||a<=0?0:a*(gr[r||"days"]??1)}var gr,Qt=w(()=>{"use strict";gr={days:1,weeks:7,months:30.4368,years:365.25}});function ne(a){let r=a.getFullYear(),e=String(a.getMonth()+1).padStart(2,"0"),t=String(a.getDate()).padStart(2,"0");return`${r}-${e}-${t}`}function mr(a,r){let e=[];for(let t=0;te.cost).filter(e=>typeof e=="number");return r.length===0?null:r.reduce((e,t)=>e+t,0)/r.length}function vr(a){let{windowStart:r,windowEnd:e,task:t,entryId:i,objectName:n}=a,o=[],p=(m,y)=>({date:m,entry_id:i,task_id:t.id,task_name:t.name,object_name:n,status:y&&(t.status==="overdue"||t.status==="triggered")?"ok":t.status,days_until_due:y?null:t.days_until_due??null,projected:y,schedule_type:t.schedule_type,interval_days:t.interval_days??null,interval_unit:t.interval_unit??null,responsible_user_id:t.responsible_user_id??null,avg_cost:fr(t.history),adaptive_enabled:!!t.adaptive_config?.enabled,prediction_confidence:t.threshold_prediction_confidence??null}),c=Math.max(1,Math.round(Yt(t.interval_days,t.interval_unit)));if(t.status==="overdue"||t.status==="triggered"){if(o.push(p(r,!1)),t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0){let m=ze(r,c),y=1;for(;m<=e&&y=r&&f<=e)o.push(p(f,!1));else if(f>e)return o;if(t.schedule_type==="time_based"&&t.interval_days&&t.interval_days>0){let m=ze(f,c),y=o.length;for(;m<=e&&y=r&&(o.push(p(m,!0)),y++),m=ze(m,c)}return o}function Xt(a,r,e,t=null){let i=mr(r,e),n=i[0],o=i[i.length-1],p=[];for(let _ of a){let f=_.object?.name||"",m=_.entry_id,y=_.tasks||[];for(let b of y){if(t&&b.responsible_user_id!==t||b.enabled===!1)continue;let k=vr({windowStart:n,windowEnd:o,task:b,entryId:m,objectName:f});p.push(...k)}}let c=new Map;for(let _ of i)c.set(_,[]);for(let _ of p){let f=c.get(_.date);f&&f.push(_)}for(let[,_]of c)_.sort((f,m)=>{let y=Zt[f.status]??99,b=Zt[m.status]??99;if(y!==b)return y-b;if(f.projected!==m.projected)return f.projected?1:-1;let k=f.object_name.localeCompare(m.object_name);return k!==0?k:f.task_name.localeCompare(m.task_name)});return i.map(_=>({date:_,events:c.get(_)??[]}))}function br(a,r){let e=[];for(let t=r-1;t>=0;t--){let i=new Date(a);i.setDate(i.getDate()-t),i.setHours(0,0,0,0),e.push(ne(i))}return e}function ei(a,r,e,t=null){let i=br(r,e),n=i[0],o=i[i.length-1],p=new Map;for(let _ of i)p.set(_,[]);for(let _ of a){let f=_.object?.name||"",m=_.entry_id,y=_.tasks||[];for(let b of y){if(t&&b.responsible_user_id!==t)continue;let k=b.history||[];for(let E of k){if(typeof E?.timestamp!="string")continue;let D=E.timestamp.slice(0,10);if(Do)continue;let K=p.get(D);if(!K)continue;let M=E.type??"completed";K.push({date:D,entry_id:m,task_id:b.id,task_name:b.name,object_name:f,status:yr[M]??"ok",days_until_due:null,projected:!1,schedule_type:b.schedule_type,interval_days:b.interval_days??null,responsible_user_id:b.responsible_user_id??null,avg_cost:typeof E.cost=="number"?E.cost:null,adaptive_enabled:!!b.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:E.timestamp,history_type:M,history_cost:typeof E.cost=="number"?E.cost:null,history_notes:typeof E.notes=="string"?E.notes:null,history_duration:typeof E.duration=="number"?E.duration:null})}}}let c={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,_]of p)_.sort((f,m)=>{let y=c[f.history_type??""]??99,b=c[m.history_type??""]??99;if(y!==b)return y-b;let k=f.object_name.localeCompare(m.object_name);return k!==0?k:f.task_name.localeCompare(m.task_name)});return i.map(_=>({date:_,events:p.get(_)??[]}))}var Jt,Zt,yr,_t=w(()=>{"use strict";Qt();Jt=5;Zt={overdue:0,triggered:1,due_soon:2,ok:3};yr={completed:"ok",reset:"ok",skipped:"due_soon",missed:"overdue",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"}});var ri,ii=w(()=>{ri={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",trigger_removed:"Trigger removed",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter Replace seal -Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_future_error:"The completion date cannot be in the future.",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_window_7:"7 days",cal_window_14:"14 days",cal_window_30:"30 days",cal_window_365:"1 year",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",qr_print:"Print",qr_download:"Download SVG",qr_action:"Action on scan",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",analysis:"Analysis",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",no_advanced_features:"No advanced features enabled",no_advanced_features_hint:"Enable \u201CAdaptive Intervals\u201D or \u201CSeasonal Patterns\u201D in the integration settings to see analysis data here.",analysis_not_enough_data:"Not enough data for analysis yet.",analysis_not_enough_data_hint:"Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",analysis_manual_task_hint:"Manual tasks without an interval do not generate analysis data.",completions:"completions",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_priority_help:"Empty = show all priorities. Tasks without an explicit priority count as Normal.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects and tasks\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_export_settings:"Export settings (JSON)",settings_import_csv:"Import CSV",settings_import_placeholder:"Paste JSON or CSV content here\u2026",settings_import_btn:"Import",settings_import_success:"{count} objects imported successfully.",settings_export_success:"Export downloaded.",settings_saved:"Setting saved.",settings_include_history:"Include history",settings_export_selection:"Limit to selected objects (optional)",settings_docs_archive:"Documents archive (with files)",settings_docs_archive_hint:"The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",settings_docs_export_btn:"Download documents ZIP",settings_docs_import_btn:"Restore documents ZIP",settings_docs_import_success:"Restored: {blobs} files, {docs} documents",sort_alphabetical:"Alphabetical",sort_due_soonest:"Due soonest",sort_task_count:"Task count",sort_area:"Area",sort_assigned_user:"Assigned user",sort_group:"Group",groupby_none:"No grouping",groupby_area:"By area",groupby_group:"By group",groupby_user:"By user",filter_label:"Filter",user_label:"User",photo_label:"Photo",sort_label:"Sort",group_by_label:"Group by",state_value_help:'Use the HA state value (usually lowercase, e.g. "on"/"off"). Case is normalised on save.',target_changes_help:"Number of matching transitions before the trigger fires (default: 1).",for_minutes_state_help:"0 counts every change immediately. Set minutes and the new state must hold that long first \u2014 brief flickers then neither trigger nor count.",qr_print_title:"Print QR codes",qr_print_desc:"Generate a printable page of QR codes to cut out and stick on your equipment.",qr_print_load:"Load objects",qr_print_filter:"Filter",qr_print_objects:"Objects",qr_print_actions:"Actions",qr_print_url_mode:"Link type",qr_print_estimate:"Estimated QR codes",qr_print_over_limit:"cap is 200, narrow the filter",qr_print_generate:"Generate QR codes",qr_print_generating:"Generating\u2026",qr_print_ready:"QR codes ready",qr_print_print_button:"Print",qr_print_empty:"Nothing to generate",qr_action_skip:"Skip",vacation_title:"Vacation mode",vacation_active:"active",vacation_ended:"ended",vacation_desc:"Plan a vacation: notifications are paused during the period plus a buffer of days. You can opt specific tasks back in.",vacation_enable:"Enable vacation mode",vacation_start:"Start",vacation_end:"End",vacation_buffer:"Buffer (days)",vacation_exempt_title:"Notify anyway during vacation",vacation_exempt_desc:"Pick tasks that should still notify during vacation (e.g. critical pool chemistry).",vacation_load_tasks:"Load tasks",vacation_preview_btn:"Show preview",vacation_preview_affected:"tasks affected",vacation_event_due_soon:"becomes due soon",vacation_event_overdue:"becomes overdue",vacation_event_triggered_est:"sensor trigger possible",vacation_sensor_based:"(sensor-based)",vacation_action_notify:"Notify anyway",vacation_action_unsilence:"Silence again",vacation_marked_complete:"Marked complete",vacation_marked_skip:"Skipped",vacation_end_now:"End vacation now",add:"Add",show_stats:"Show stats + graphs",hide_stats:"Hide stats",adaptive_no_data:"Not enough completion history yet for adaptive analysis. Complete this task a few more times to unlock interval recommendations and reliability charts.",suggestion_applied:"Suggested interval applied",vacation_mode:"Vacation mode",vacation_status_active:"Active now",vacation_status_scheduled:"Scheduled",vacation_status_inactive:"Inactive",vacation_end_now_confirm:"End vacation immediately?",vacation_exempt_count:"exempt",vacation_advanced:"Advanced\u2026",vacation_open_panel:"Open in panel",enable:"Enable",saved:"Saved",budget_monthly_set:"Set monthly",budget_yearly_set:"Set yearly",budget_advanced:"Currency, alerts\u2026",budget_open_panel:"Open in panel",groups_empty:"No groups yet.",group_new_placeholder:"Add group\u2026",group_delete_confirm:'Delete group "{name}"?',groups_manage_tasks:"Manage task assignments\u2026",groups_open_panel:"Open in panel",unassigned:"Unassigned",no_area:"No area",has_overdue:"Has overdue tasks",object:"Object",settings_panel_access:"Panel access",settings_panel_access_desc:"Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below \u2014 everyone else sees only Complete and Skip.",settings_operator_write:"Allow selected users to create, edit & delete",settings_operator_write_desc:"Off: only admins can change content. On: the selected users below get full access too.",no_non_admin_users:"No non-admin users found. Add some in Settings \u2192 People.",owner_label:"Owner",feat_completion_actions:"Completion actions",feat_completion_actions_desc:"Per-task HA action on complete + quick-complete QR with pre-set values.",on_complete_action_title:"On complete: trigger HA action (optional)",on_complete_action_desc:"Calls an HA service when the task is completed \u2014 e.g. reset a counter on the device.",on_complete_action_service:"Service",on_complete_action_target:"Target entity",on_complete_action_target_hint:"Note: the entity domain must match the service \u2014 e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",on_complete_action_data:"Data (JSON, optional)",on_complete_action_test:"Validate configuration",on_complete_action_test_success:"\u2713 Configuration valid (action will fire only on task completion)",on_complete_action_test_failed:"Failed",quick_complete_defaults_title:"Quick-complete defaults (for QR scans, optional)",quick_complete_defaults_desc:"Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",quick_complete_defaults_notes:"Notes",quick_complete_defaults_cost:"Cost",quick_complete_defaults_duration:"Duration (minutes)",quick_complete_defaults_feedback_none:"No feedback",quick_complete_defaults_feedback_needed:"Was needed",quick_complete_defaults_feedback_not_needed:"Not needed",quick_complete_success:"Quickly marked complete",show_all_objects:"Show all objects",show_all_tasks:"Clear filter \u2014 show all tasks",filter_to_overdue:"Filter task list to overdue only",filter_to_due_soon:"Filter task list to due-soon only",filter_to_triggered:"Filter task list to triggered only",open_task:"Open task",show_details:"Show history + stats",hide_details:"Hide details",history_empty:"No history yet.",history_edit_button:"Edit entry",total_cost:"Total cost",times_performed:"Performed",older_entries:"older",open_in_panel:"Open in Maintenance panel",skip_reason:"Skip reason (optional)",reset_to_date:"Reset last_performed to",delete_task_confirm:"Delete this task and its history?",delete_object_confirm:"Delete this object and all its tasks?",loading:"Loading\u2026",archive:"Archive",undo:"Undo",task_archived:"Task archived",object_archived:"Object archived",unarchive:"Unarchive",archived:"Archived",show_archived:"Show archived",hide_archived:"Hide archived",confirm_archive_object:"Archive this object and its tasks? They keep their history and can be unarchived later.",settings_archive:"Archive & Retention",settings_archive_desc:"Retire completed one-off tasks without deleting them. Archived items are hidden and inert but keep their history and cost.",settings_archive_oneoff_days:"Auto-archive completed one-off tasks after (days, 0 = off)",settings_delete_archived_oneoff_days:"Auto-delete archived one-off tasks after (days, 0 = never)",archive_object:"Archive object",unarchive_object:"Unarchive object",documents:"Documents",documents_empty:"No documents yet.",doc_upload:"Upload file",doc_uploading:"Uploading\u2026",doc_add_link:"Add link",doc_link_url:"URL (https://\u2026)",doc_link_title:"Title (optional)",doc_open:"Open",doc_delete_confirm:'Delete "{name}"?',doc_too_large:"File is too large (max 25 MB).",doc_upload_failed:"Upload failed.",completion_photo_optional:"Completion photo (optional)",add_photo:"Add photo",uploading:"Uploading\u2026",remove:"Remove",doc_deduped:"Already stored elsewhere \u2014 shared, no extra space used.",doc_dup_in_object:"This file is already attached to this object.",doc_link_invalid:"Only http/https links are allowed.",doc_cat_manual:"Manual",doc_cat_warranty:"Warranty",doc_cat_invoice:"Invoice",doc_cat_spare_parts:"Spare parts",doc_cat_photo:"Photo",doc_cat_other:"Other",doc_link_badge:"Link",doc_storage_title:"Document storage",doc_storage_saved:"Saved via deduplication",doc_storage_refresh:"Refresh",doc_download:"Download",doc_close:"Close",doc_camera:"Take photo",doc_drop_hint:"Drop files here",doc_task_none:"No documents linked to this task.",doc_link_existing:"Link a document\u2026",doc_attach:"Link",doc_unlink:"Unlink",doc_page:"Page",chart_range_7d:"7d",chart_range_30d:"30d",chart_range_90d:"90d",chart_range_1y:"1y",chart_since_service:"since last service",chart_no_stats:"No long-term statistics for this entity \u2014 showing maintenance-event values only",auto_complete_on_recovery:"Auto-complete when the sensor recovers",auto_complete_on_recovery_help:"Records a completion (sets last performed) when the trigger clears itself \u2014 e.g. salt refilled, filter replaced.",doc_search:"Search documents\u2026",doc_search_none:"No matching documents",link_device_optional:"Link to existing device (optional)",parent_object_optional:"Parent object (optional)",parent_none:"(No parent)",paused:"Paused",pause_object:"Pause",resume_object:"Resume",pause_until_prompt:"Freeze this object's schedules \u2014 nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",pause_until_label:"Resume on (optional)",object_paused:"Object paused",object_resumed:"Object resumed \u2014 schedules restarted",object_paused_badge:"Paused",paused_until_label:"until",replace_object:"Replace\u2026",replace_object_prompt:"Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",replace_name_label:"Successor name",object_replaced:"Object replaced \u2014 successor created",reading_unit_label:"Reading unit (e.g. kWh, m\xB3)",reading_unit_help:"Shown next to the recorded value when completing this task.",reading_value_label:"Reading value",reading_label:"Reading",settings_templates_label:"Template gallery",settings_templates_hint:`Untick templates you'll never need \u2014 they disappear from the "From template" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.`,worksheet:"Work sheet",worksheet_scan_view:"Scan to open the task",worksheet_scan_complete:"Scan to complete",worksheet_manual_excerpt:"Manual excerpt",worksheet_pages:"pages",worksheet_printed:"Printed",worksheet_never:"Never",card_all_caught_up:"All caught up \u2014 nothing needs attention",postpone:"Postpone",postpone_date_prompt:"Postpone this occurrence to which date?",postpone_date_label:"New due date",postponed:"Postponed",postponed_to:"Postponed to",season_window_label:"Seasonal window (months)",season_window_hint:"Only due in the selected months; off-season dates roll to the next active month. None = all year.",series_end_label:"Ends",series_end_never:"Never (repeats indefinitely)",series_end_after_count:"After a number of times",series_end_until:"On a date",series_end_count_label:"Number of times",series_end_until_label:"End date",parts_section:"Parts & consumables",parts_inventory_value:"Inventory value",part_add:"Add part",part_name:"Name",part_vendor:"Manufacturer",part_storage_location:"Storage location",part_product_url:"Product URL",part_unit:"Unit",part_cost:"Unit price",part_stock:"Stock",part_reorder_threshold:"Reorder at",part_restock_quantity:"Restock quantity",part_auto_buy:"Auto-create buy task when low",part_restock:"Adjust stock",parts_used_by:"Used by",restock_quantity_label:"Quantity bought",consumes_parts_label:"Consumes parts",shared_parts_other_objects:"Parts from other objects",shared_parts_help:"Several objects can share one stock. Completing this task takes from the owning object.",shared_part_unknown:"Unknown part",parts_load_failed:"Couldn't load this object's parts \u2014 the consumes-parts options are unavailable right now.",adopt_problem_button:"Adopt problem sensors",adopt_problem_title:"Adopt problem sensors",adopt_problem_hint:"Turn HA problem sensors (printer errors, filter warnings, low battery) into maintenance tasks that trigger while the problem is active and clear themselves when it resolves.",adopt_problem_none:"No problem sensors found that aren't already tracked.",adopt_problem_active:"active",adopt_problem_ok:"ok",adopt_problem_new_object:"(new)",adopt_problem_adopt:"Adopt selected",adopt_problem_done:"Adopted {tasks} problem sensor(s)",views_label:"Views",views_none:"\u2014 No view \u2014",views_manage:"Save / manage views",views_dialog_title:"Saved views",views_dialog_hint:"Save the current filters as a named view everyone can reuse.",views_name_placeholder:"View name",views_save_current:"Save current filters",views_none_yet:"No saved views yet.",close:"Close",trigger_hint_now:"The sensor reads {value} right now.",trigger_hint_above:"The task triggers once it rises above {target}.",trigger_hint_below:"It triggers once it falls below {target}.",trigger_hint_counter_delta:"Counts from the current reading ({value}): due at {due} (+{target}), and the count restarts after each completion.",trigger_hint_counter_delta_edit:"Counts usage since the last completion: due after +{target}; the count restarts after each completion.",trigger_hint_counter_abs:"The task becomes due once the sensor reaches {target}.",trigger_hint_runtime:"The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",trigger_hint_state_change:"The task becomes due after {count} state change(s).",trigger_hint_state_change_to:"The task becomes due after {count} change(s) to \u201C{state}\u201D.",trigger_hint_state_now:"Current state: {value}.",adopt_problem_part:"Uses part: {name}",label_filter:"Label",all_labels:"All labels",settings_notify_scope:"Notify only for view",settings_notify_scope_all:"All tasks",settings_notify_scope_hint:"Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",card_saved_view:"Saved view",card_saved_view_none:"None",card_saved_view_help:"Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",doc_part_none:"No documents linked to this part.",settings_templates_toggle_group:"Enable or disable all templates in this group",setups_button:"Suggested setups",setups_title:"Suggested setups (Beta)",setups_hint:"Devices of supported integrations whose consumable sensors can drive maintenance tasks. Adopting creates the object and wires each task to its sensor \u2014 it triggers when the consumable runs low and resolves itself after replacement.",setups_none:"No supported devices with unwired consumable sensors found.",setups_adopt:"Set up selected",setups_done:"{tasks} sensor-wired tasks created.",complete_parts_used:"Parts used this time",part_delete_confirm:"Delete part '{name}'? Its stock tracking, task links and any open buy reminder will be removed.",baseline_start_value:"Start reading (optional)",baseline_start_help:"Counting starts from this reading. Leave empty to count from the current value; enter the reading at the last service so usage since then already counts.",setups_baseline_hint:"reading at last service (optional)",baseline_start_help_edit:"Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",baseline_current_effective:"Currently effective start value: {value}",runtime_on_states:"Active states",runtime_on_states_help:"States that count as running \u2014 default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",setups_target_new:"Create new: {name}",schedule_preview_title:"Next dates",schedule_preview_ontime:"Assuming on-time completion.",schedule_preview_ends:"(series ends)",adopt_problem_responsible:"Responsible user for all adopted tasks (optional)",adopt_for_minutes_hint:"Only trigger once the problem has persisted this long \u2014 0 reacts to the first flicker.",adopt_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_add:"Add a battery",battery_fleet_add_hint:"Pick a battery sensor the automatic discovery missed \u2014 it joins the roster immediately.",battery_fleet_track_self:"Track self-charging batteries",battery_fleet_track_self_hint:"Phones, vacuums and other devices that recharge themselves appear as rechargeables \u2014 a low one asks for a charge, never for new cells.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",cal_editor_window:"Default window",cal_editor_window_week:"Week (7 days)",cal_editor_window_fortnight:"Fortnight (14 days)",cal_editor_window_month:"Month (30 days, default)",cal_editor_window_year:"Year (365 days, empty days collapsed)",cal_editor_show_chips:"Show window chips inside the card",cal_editor_chips_hint:"Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",cal_editor_show_user_filter:"Show user filter dropdown",cal_editor_default_user:"Default user filter",cal_editor_my_tasks:"My tasks (current user)",cal_editor_show_object_filter:"Show object filter dropdown",cal_editor_object_hint:'Pre-select one object via YAML: object_filter: "" \u2014 or a list of names to restrict the card to several objects.'}});var ii,ri=w(()=>{"use strict";ii="2.63.1"});var Ce,si=w(()=>{"use strict";Ce={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"}});function yr(a){ne.en=Object.assign({},a,ne.en??{})}function We(a){let r=(a||pt).toLowerCase();return r.startsWith("pt")&&r.endsWith("br")?"pt-br":r.substring(0,2)}function s(a,r){let e=We(r);return ne[e]?.[a]??ne.en[a]??a}function oi(a,r){r.has("hass")&&$r(a.hass?.locale);let e=a.hass?.language;e&&!Re(e)&&Ne(e).then(()=>a.requestUpdate())}function j(a){return a?.language||"en"}function Re(a){let r=We(a);return r===pt||r in ne}function Ne(a){let r=We(a);return r===pt||r in ne||!br.has(r)?Promise.resolve():(r in Ie||(Ie[r]=fetch(`${xr}/${r}.json?v=${ii}`).then(e=>e.ok?e.json():null).then(e=>{e?ne[r]=e:delete Ie[r]}).catch(()=>{delete Ie[r]})),Ie[r])}function Le(a){let r=We(a);return{de:"de-DE",en:"en-US",nl:"nl-NL",fr:"fr-FR",it:"it-IT",es:"es-ES",pt:"pt-PT",ru:"ru-RU",uk:"uk-UA",zh:"zh-CN",da:"da-DK",fi:"fi-FI",nb:"nb-NO",ja:"ja-JP",hi:"hi-IN",pl:"pl-PL",cs:"cs-CZ",sv:"sv-SE","pt-br":"pt-BR",hu:"hu-HU",ko:"ko-KR",tr:"tr-TR"}[r]??"en-US"}function $r(a){a&&(Ue.date=a.date_format,Ue.time=a.time_format)}function li(a,r){let e=String(a.getDate()).padStart(2,"0"),t=String(a.getMonth()+1).padStart(2,"0"),i=String(a.getFullYear());switch(Ue.date){case"DMY":return`${e}/${t}/${i}`;case"MDY":return`${t}/${e}/${i}`;case"YMD":return`${i}-${t}-${e}`;case"system":return a.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return a.toLocaleDateString(Le(r),{day:"2-digit",month:"2-digit",year:"numeric"})}}function kr(a,r){switch(Ue.time){case"12":return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return a.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit"})}}function G(a,r){if(!a)return"\u2014";try{let e=a.includes("T")?a:a+"T00:00:00";return li(new Date(e),r)}catch{return a}}function di(a,r){if(!a)return"\u2014";try{let e=new Date(a);return li(e,r)+" "+kr(e,r)}catch{return a}}function ci(a,r){if(a==null)return"\u2014";let e=r||"en";return a<0?`${Math.abs(a)} ${s("d_overdue",e)}`:a===0?s("today",e):`${a} ${s(a===1?"day":"days",e)}`}function Be(a,r,e){return a==null?"\u2014":`${a} ${s("unit_"+(r||"days"),e)}`}function Pe(a,r,e="long"){return new Date(Date.UTC(2024,0,1+a)).toLocaleDateString(Le(r),{weekday:e,timeZone:"UTC"})}function pi(a,r){let e=a.schedule,t=e?.offset?` ${e.offset>0?"+":"\u2212"}${Math.abs(e.offset)}d`:"";switch(e?.kind){case"weekdays":return((e.weekdays||[]).map(i=>Pe(i,r,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?s("ord_last",r):s("ord_"+e.nth,r)} ${Pe(e.weekday,r,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?s(e.business?"last_business_day_month":"last_day_month",r):`${s("day_word",r)} ${e.day}`)+t;case"one_time":return a.due_date?G(a.due_date,r):s("one_time",r);case"manual":return s("manual",r);case"interval":return Be(e.every,e.unit,r)}return a.schedule_type==="one_time"?a.due_date?G(a.due_date,r):s("one_time",r):a.schedule_type==="manual"?s("manual",r):a.schedule_type==="sensor_based"?s("sensor_based",r):a.interval_days!=null?Be(a.interval_days,a.interval_unit,r):"\u2014"}function ui(a,r){a.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:r},bubbles:!0,composed:!0}))}var ai,pt,ni,ne,br,xr,Ie,wr,Ue,_i,Ve,R=w(()=>{"use strict";P();ei();ri();si();ai="\u20AC",pt="en",ni=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),ne=ni.store;yr(ti);br=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),xr="/maintenance_supporter_locales",Ie=ni.inflight;wr=window,Ue=wr.__msDateTimePrefs??={};_i=S` +Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_future_error:"The completion date cannot be in the future.",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_window_7:"7 days",cal_window_14:"14 days",cal_window_30:"30 days",cal_window_365:"1 year",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",qr_print:"Print",qr_download:"Download SVG",qr_action:"Action on scan",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",analysis:"Analysis",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",no_advanced_features:"No advanced features enabled",no_advanced_features_hint:"Enable \u201CAdaptive Intervals\u201D or \u201CSeasonal Patterns\u201D in the integration settings to see analysis data here.",analysis_not_enough_data:"Not enough data for analysis yet.",analysis_not_enough_data_hint:"Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",analysis_manual_task_hint:"Manual tasks without an interval do not generate analysis data.",completions:"completions",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_priority_help:"Empty = show all priorities. Tasks without an explicit priority count as Normal.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects and tasks\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_export_settings:"Export settings (JSON)",settings_import_csv:"Import CSV",settings_import_placeholder:"Paste JSON or CSV content here\u2026",settings_import_btn:"Import",settings_import_success:"{count} objects imported successfully.",settings_export_success:"Export downloaded.",settings_saved:"Setting saved.",settings_include_history:"Include history",settings_export_selection:"Limit to selected objects (optional)",settings_docs_archive:"Documents archive (with files)",settings_docs_archive_hint:"The JSON/YAML/CSV exports carry settings only. This ZIP includes the uploaded file contents so a restore is complete.",settings_docs_export_btn:"Download documents ZIP",settings_docs_import_btn:"Restore documents ZIP",settings_docs_import_success:"Restored: {blobs} files, {docs} documents",sort_alphabetical:"Alphabetical",sort_due_soonest:"Due soonest",sort_task_count:"Task count",sort_area:"Area",sort_assigned_user:"Assigned user",sort_group:"Group",groupby_none:"No grouping",groupby_area:"By area",groupby_group:"By group",groupby_user:"By user",filter_label:"Filter",user_label:"User",photo_label:"Photo",sort_label:"Sort",group_by_label:"Group by",state_value_help:'Use the HA state value (usually lowercase, e.g. "on"/"off"). Case is normalised on save.',target_changes_help:"Number of matching transitions before the trigger fires (default: 1).",for_minutes_state_help:"0 counts every change immediately. Set minutes and the new state must hold that long first \u2014 brief flickers then neither trigger nor count.",qr_print_title:"Print QR codes",qr_print_desc:"Generate a printable page of QR codes to cut out and stick on your equipment.",qr_print_load:"Load objects",qr_print_filter:"Filter",qr_print_objects:"Objects",qr_print_actions:"Actions",qr_print_url_mode:"Link type",qr_print_estimate:"Estimated QR codes",qr_print_over_limit:"cap is 200, narrow the filter",qr_print_generate:"Generate QR codes",qr_print_generating:"Generating\u2026",qr_print_ready:"QR codes ready",qr_print_print_button:"Print",qr_print_empty:"Nothing to generate",qr_action_skip:"Skip",vacation_title:"Vacation mode",vacation_active:"active",vacation_ended:"ended",vacation_desc:"Plan a vacation: notifications are paused during the period plus a buffer of days. You can opt specific tasks back in.",vacation_enable:"Enable vacation mode",vacation_start:"Start",vacation_end:"End",vacation_buffer:"Buffer (days)",vacation_exempt_title:"Notify anyway during vacation",vacation_exempt_desc:"Pick tasks that should still notify during vacation (e.g. critical pool chemistry).",vacation_load_tasks:"Load tasks",vacation_preview_btn:"Show preview",vacation_preview_affected:"tasks affected",vacation_event_due_soon:"becomes due soon",vacation_event_overdue:"becomes overdue",vacation_event_triggered_est:"sensor trigger possible",vacation_sensor_based:"(sensor-based)",vacation_action_notify:"Notify anyway",vacation_action_unsilence:"Silence again",vacation_marked_complete:"Marked complete",vacation_marked_skip:"Skipped",vacation_end_now:"End vacation now",add:"Add",show_stats:"Show stats + graphs",hide_stats:"Hide stats",adaptive_no_data:"Not enough completion history yet for adaptive analysis. Complete this task a few more times to unlock interval recommendations and reliability charts.",suggestion_applied:"Suggested interval applied",vacation_mode:"Vacation mode",vacation_status_active:"Active now",vacation_status_scheduled:"Scheduled",vacation_status_inactive:"Inactive",vacation_end_now_confirm:"End vacation immediately?",vacation_exempt_count:"exempt",vacation_advanced:"Advanced\u2026",vacation_open_panel:"Open in panel",enable:"Enable",saved:"Saved",budget_monthly_set:"Set monthly",budget_yearly_set:"Set yearly",budget_advanced:"Currency, alerts\u2026",budget_open_panel:"Open in panel",groups_empty:"No groups yet.",group_new_placeholder:"Add group\u2026",group_delete_confirm:'Delete group "{name}"?',groups_manage_tasks:"Manage task assignments\u2026",groups_open_panel:"Open in panel",unassigned:"Unassigned",no_area:"No area",has_overdue:"Has overdue tasks",object:"Object",settings_panel_access:"Panel access",settings_panel_access_desc:"Admins always have full access. To delegate create, edit and delete to specific non-admins, switch this on and pick them below \u2014 everyone else sees only Complete and Skip.",settings_operator_write:"Allow selected users to create, edit & delete",settings_operator_write_desc:"Off: only admins can change content. On: the selected users below get full access too.",no_non_admin_users:"No non-admin users found. Add some in Settings \u2192 People.",owner_label:"Owner",feat_completion_actions:"Completion actions",feat_completion_actions_desc:"Per-task HA action on complete + quick-complete QR with pre-set values.",on_complete_action_title:"On complete: trigger HA action (optional)",on_complete_action_desc:"Calls an HA service when the task is completed \u2014 e.g. reset a counter on the device.",on_complete_action_service:"Service",on_complete_action_target:"Target entity",on_complete_action_target_hint:"Note: the entity domain must match the service \u2014 e.g. 'button.press' only works on button.*, 'counter.increment' only on counter.*, 'input_button.press' only on input_button.* etc. On a mismatch the action will silently fail (HA logs 'Referenced entities ... missing or not currently available').",on_complete_action_data:"Data (JSON, optional)",on_complete_action_test:"Validate configuration",on_complete_action_test_success:"\u2713 Configuration valid (action will fire only on task completion)",on_complete_action_test_failed:"Failed",quick_complete_defaults_title:"Quick-complete defaults (for QR scans, optional)",quick_complete_defaults_desc:"Pre-set values for quick-complete QR scans. Without these, the QR opens the complete dialog.",quick_complete_defaults_notes:"Notes",quick_complete_defaults_cost:"Cost",quick_complete_defaults_duration:"Duration (minutes)",quick_complete_defaults_feedback_none:"No feedback",quick_complete_defaults_feedback_needed:"Was needed",quick_complete_defaults_feedback_not_needed:"Not needed",quick_complete_success:"Quickly marked complete",show_all_objects:"Show all objects",show_all_tasks:"Clear filter \u2014 show all tasks",filter_to_overdue:"Filter task list to overdue only",filter_to_due_soon:"Filter task list to due-soon only",filter_to_triggered:"Filter task list to triggered only",open_task:"Open task",show_details:"Show history + stats",hide_details:"Hide details",history_empty:"No history yet.",history_edit_button:"Edit entry",total_cost:"Total cost",times_performed:"Performed",older_entries:"older",open_in_panel:"Open in Maintenance panel",skip_reason:"Skip reason (optional)",reset_to_date:"Reset last_performed to",delete_task_confirm:"Delete this task and its history?",delete_object_confirm:"Delete this object and all its tasks?",loading:"Loading\u2026",archive:"Archive",undo:"Undo",task_archived:"Task archived",object_archived:"Object archived",unarchive:"Unarchive",archived:"Archived",show_archived:"Show archived",hide_archived:"Hide archived",confirm_archive_object:"Archive this object and its tasks? They keep their history and can be unarchived later.",settings_archive:"Archive & Retention",settings_archive_desc:"Retire completed one-off tasks without deleting them. Archived items are hidden and inert but keep their history and cost.",settings_archive_oneoff_days:"Auto-archive completed one-off tasks after (days, 0 = off)",settings_delete_archived_oneoff_days:"Auto-delete archived one-off tasks after (days, 0 = never)",archive_object:"Archive object",unarchive_object:"Unarchive object",documents:"Documents",documents_empty:"No documents yet.",doc_upload:"Upload file",doc_uploading:"Uploading\u2026",doc_add_link:"Add link",doc_link_url:"URL (https://\u2026)",doc_link_title:"Title (optional)",doc_open:"Open",doc_delete_confirm:'Delete "{name}"?',doc_too_large:"File is too large (max 25 MB).",doc_upload_failed:"Upload failed.",completion_photo_optional:"Completion photo (optional)",add_photo:"Add photo",uploading:"Uploading\u2026",remove:"Remove",doc_deduped:"Already stored elsewhere \u2014 shared, no extra space used.",doc_dup_in_object:"This file is already attached to this object.",doc_link_invalid:"Only http/https links are allowed.",doc_cat_manual:"Manual",doc_cat_warranty:"Warranty",doc_cat_invoice:"Invoice",doc_cat_spare_parts:"Spare parts",doc_cat_photo:"Photo",doc_cat_other:"Other",doc_link_badge:"Link",doc_storage_title:"Document storage",doc_storage_saved:"Saved via deduplication",doc_storage_refresh:"Refresh",doc_download:"Download",doc_close:"Close",doc_camera:"Take photo",doc_drop_hint:"Drop files here",doc_task_none:"No documents linked to this task.",doc_link_existing:"Link a document\u2026",doc_attach:"Link",doc_unlink:"Unlink",doc_page:"Page",chart_range_7d:"7d",chart_range_30d:"30d",chart_range_90d:"90d",chart_range_1y:"1y",chart_since_service:"since last service",chart_no_stats:"No long-term statistics for this entity \u2014 showing maintenance-event values only",auto_complete_on_recovery:"Auto-complete when the sensor recovers",auto_complete_on_recovery_help:"Records a completion (sets last performed) when the trigger clears itself \u2014 e.g. salt refilled, filter replaced.",doc_search:"Search documents\u2026",doc_search_none:"No matching documents",link_device_optional:"Link to existing device (optional)",parent_object_optional:"Parent object (optional)",parent_none:"(No parent)",paused:"Paused",pause_object:"Pause",resume_object:"Resume",pause_until_prompt:"Freeze this object's schedules \u2014 nothing becomes due and nothing notifies until it is resumed. Optionally set an auto-resume date.",pause_until_label:"Resume on (optional)",object_paused:"Object paused",object_resumed:"Object resumed \u2014 schedules restarted",object_paused_badge:"Paused",paused_until_label:"until",replace_object:"Replace\u2026",replace_object_prompt:"Retire this object and create a successor. History and costs stay archived on the old one; tasks and documents carry over to the new one, counters start fresh.",replace_name_label:"Successor name",object_replaced:"Object replaced \u2014 successor created",reading_unit_label:"Reading unit (e.g. kWh, m\xB3)",reading_unit_help:"Shown next to the recorded value when completing this task.",reading_value_label:"Reading value",reading_label:"Reading",settings_templates_label:"Template gallery",settings_templates_hint:`Untick templates you'll never need \u2014 they disappear from the "From template" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.`,worksheet:"Work sheet",worksheet_scan_view:"Scan to open the task",worksheet_scan_complete:"Scan to complete",worksheet_manual_excerpt:"Manual excerpt",worksheet_pages:"pages",worksheet_printed:"Printed",worksheet_never:"Never",card_all_caught_up:"All caught up \u2014 nothing needs attention",postpone:"Postpone",postpone_date_prompt:"Postpone this occurrence to which date?",postpone_date_label:"New due date",postponed:"Postponed",postponed_to:"Postponed to",season_window_label:"Seasonal window (months)",season_window_hint:"Only due in the selected months; off-season dates roll to the next active month. None = all year.",series_end_label:"Ends",series_end_never:"Never (repeats indefinitely)",series_end_after_count:"After a number of times",series_end_until:"On a date",series_end_count_label:"Number of times",series_end_until_label:"End date",parts_section:"Parts & consumables",parts_inventory_value:"Inventory value",part_add:"Add part",part_name:"Name",part_vendor:"Manufacturer",part_storage_location:"Storage location",part_product_url:"Product URL",part_unit:"Unit",part_cost:"Unit price",part_stock:"Stock",part_reorder_threshold:"Reorder at",part_restock_quantity:"Restock quantity",part_auto_buy:"Auto-create buy task when low",part_restock:"Adjust stock",parts_used_by:"Used by",restock_quantity_label:"Quantity bought",consumes_parts_label:"Consumes parts",shared_parts_other_objects:"Parts from other objects",shared_parts_help:"Several objects can share one stock. Completing this task takes from the owning object.",shared_part_unknown:"Unknown part",parts_load_failed:"Couldn't load this object's parts \u2014 the consumes-parts options are unavailable right now.",adopt_problem_button:"Adopt problem sensors",adopt_problem_title:"Adopt problem sensors",adopt_problem_hint:"Turn HA problem sensors (printer errors, filter warnings, low battery) into maintenance tasks that trigger while the problem is active and clear themselves when it resolves.",adopt_problem_none:"No problem sensors found that aren't already tracked.",adopt_problem_active:"active",adopt_problem_ok:"ok",adopt_problem_new_object:"(new)",adopt_problem_adopt:"Adopt selected",adopt_problem_done:"Adopted {tasks} problem sensor(s)",views_label:"Views",views_none:"\u2014 No view \u2014",views_manage:"Save / manage views",views_dialog_title:"Saved views",views_dialog_hint:"Save the current filters as a named view everyone can reuse.",views_name_placeholder:"View name",views_save_current:"Save current filters",views_none_yet:"No saved views yet.",close:"Close",trigger_hint_now:"The sensor reads {value} right now.",trigger_hint_above:"The task triggers once it rises above {target}.",trigger_hint_below:"It triggers once it falls below {target}.",trigger_hint_counter_delta:"Counts from the current reading ({value}): due at {due} (+{target}), and the count restarts after each completion.",trigger_hint_counter_delta_edit:"Counts usage since the last completion: due after +{target}; the count restarts after each completion.",trigger_hint_counter_abs:"The task becomes due once the sensor reaches {target}.",trigger_hint_runtime:"The task becomes due after {hours} h of accumulated on-time; the counter restarts after each completion.",trigger_hint_state_change:"The task becomes due after {count} state change(s).",trigger_hint_state_change_to:"The task becomes due after {count} change(s) to \u201C{state}\u201D.",trigger_hint_state_now:"Current state: {value}.",adopt_problem_part:"Uses part: {name}",label_filter:"Label",all_labels:"All labels",settings_notify_scope:"Notify only for view",settings_notify_scope_all:"All tasks",settings_notify_scope_hint:"Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",card_saved_view:"Saved view",card_saved_view_none:"None",card_saved_view_help:"Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",doc_part_none:"No documents linked to this part.",settings_templates_toggle_group:"Enable or disable all templates in this group",setups_button:"Suggested setups",setups_title:"Suggested setups (Beta)",setups_hint:"Devices of supported integrations whose consumable sensors can drive maintenance tasks. Adopting creates the object and wires each task to its sensor \u2014 it triggers when the consumable runs low and resolves itself after replacement.",setups_none:"No supported devices with unwired consumable sensors found.",setups_adopt:"Set up selected",setups_done:"{tasks} sensor-wired tasks created.",complete_parts_used:"Parts used this time",part_delete_confirm:"Delete part '{name}'? Its stock tracking, task links and any open buy reminder will be removed.",baseline_start_value:"Start reading (optional)",baseline_start_help:"Counting starts from this reading. Leave empty to count from the current value; enter the reading at the last service so usage since then already counts.",setups_baseline_hint:"reading at last service (optional)",baseline_start_help_edit:"Leave empty to keep the existing counting. Entering a value re-anchors the counting (e.g. the reading at the last service).",baseline_current_effective:"Currently effective start value: {value}",runtime_on_states:"Active states",runtime_on_states_help:"States that count as running \u2014 default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",setups_target_new:"Create new: {name}",schedule_preview_title:"Next dates",schedule_preview_ontime:"Assuming on-time completion.",schedule_preview_ends:"(series ends)",adopt_problem_responsible:"Responsible user for all adopted tasks (optional)",adopt_for_minutes_hint:"Only trigger once the problem has persisted this long \u2014 0 reacts to the first flicker.",adopt_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_add:"Add a battery",battery_fleet_add_hint:"Pick a battery sensor the automatic discovery missed \u2014 it joins the roster immediately.",battery_fleet_track_self:"Track self-charging batteries",battery_fleet_track_self_hint:"Phones, vacuums and other devices that recharge themselves appear as rechargeables \u2014 a low one asks for a charge, never for new cells.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",cal_editor_window:"Default window",cal_editor_window_week:"Week (7 days)",cal_editor_window_fortnight:"Fortnight (14 days)",cal_editor_window_month:"Month (30 days, default)",cal_editor_window_year:"Year (365 days, empty days collapsed)",cal_editor_show_chips:"Show window chips inside the card",cal_editor_chips_hint:"Hide the chips when the card is embedded in a strategy view that already serves as the window selector.",cal_editor_show_user_filter:"Show user filter dropdown",cal_editor_default_user:"Default user filter",cal_editor_my_tasks:"My tasks (current user)",cal_editor_show_object_filter:"Show object filter dropdown",cal_editor_object_hint:'Pre-select one object via YAML: object_filter: "" \u2014 or a list of names to restrict the card to several objects.',object_history_section:"History (all tasks)",object_history_all_tasks:"All tasks",object_history_empty:"No entries in this range.",object_history_cap_note:"History keeps up to 500 entries per task \u2014 very old entries may be missing.",service_record_title:"Service record",service_record_print:"Service record (PDF)",date:"Date",service_record_entries:"entries",completed_by:"Completed by",date_from:"From",date_to:"To"}});var si,ai=w(()=>{"use strict";si="2.64.0"});var Ce,ni=w(()=>{"use strict";Ce={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"}});function wr(a){oe.en=Object.assign({},a,oe.en??{})}function We(a){let r=(a||ut).toLowerCase();return r.startsWith("pt")&&r.endsWith("br")?"pt-br":r.substring(0,2)}function s(a,r){let e=We(r);return oe[e]?.[a]??oe.en[a]??a}function di(a,r){r.has("hass")&&Sr(a.hass?.locale);let e=a.hass?.language;e&&!Re(e)&&je(e).then(()=>a.requestUpdate())}function N(a){return a?.language||"en"}function Re(a){let r=We(a);return r===ut||r in oe}function je(a){let r=We(a);return r===ut||r in oe||!$r.has(r)?Promise.resolve():(r in Ie||(Ie[r]=fetch(`${kr}/${r}.json?v=${si}`).then(e=>e.ok?e.json():null).then(e=>{e?oe[r]=e:delete Ie[r]}).catch(()=>{delete Ie[r]})),Ie[r])}function Le(a){let r=We(a);return{de:"de-DE",en:"en-US",nl:"nl-NL",fr:"fr-FR",it:"it-IT",es:"es-ES",pt:"pt-PT",ru:"ru-RU",uk:"uk-UA",zh:"zh-CN",da:"da-DK",fi:"fi-FI",nb:"nb-NO",ja:"ja-JP",hi:"hi-IN",pl:"pl-PL",cs:"cs-CZ",sv:"sv-SE","pt-br":"pt-BR",hu:"hu-HU",ko:"ko-KR",tr:"tr-TR"}[r]??"en-US"}function Sr(a){a&&(Ue.date=a.date_format,Ue.time=a.time_format)}function ci(a,r){let e=String(a.getDate()).padStart(2,"0"),t=String(a.getMonth()+1).padStart(2,"0"),i=String(a.getFullYear());switch(Ue.date){case"DMY":return`${e}/${t}/${i}`;case"MDY":return`${t}/${e}/${i}`;case"YMD":return`${i}-${t}-${e}`;case"system":return a.toLocaleDateString(void 0,{day:"2-digit",month:"2-digit",year:"numeric"});default:return a.toLocaleDateString(Le(r),{day:"2-digit",month:"2-digit",year:"numeric"})}}function Ar(a,r){switch(Ue.time){case"12":return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit",hour12:!0});case"24":return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit",hour12:!1});case"system":return a.toLocaleTimeString(void 0,{hour:"2-digit",minute:"2-digit"});default:return a.toLocaleTimeString(Le(r),{hour:"2-digit",minute:"2-digit"})}}function Y(a,r){if(!a)return"\u2014";try{let e=a.includes("T")?a:a+"T00:00:00";return ci(new Date(e),r)}catch{return a}}function pi(a,r){if(!a)return"\u2014";try{let e=new Date(a);return ci(e,r)+" "+Ar(e,r)}catch{return a}}function _i(a,r){if(a==null)return"\u2014";let e=r||"en";return a<0?`${Math.abs(a)} ${s("d_overdue",e)}`:a===0?s("today",e):`${a} ${s(a===1?"day":"days",e)}`}function Be(a,r,e){return a==null?"\u2014":`${a} ${s("unit_"+(r||"days"),e)}`}function Pe(a,r,e="long"){return new Date(Date.UTC(2024,0,1+a)).toLocaleDateString(Le(r),{weekday:e,timeZone:"UTC"})}function ui(a,r){let e=a.schedule,t=e?.offset?` ${e.offset>0?"+":"\u2212"}${Math.abs(e.offset)}d`:"";switch(e?.kind){case"weekdays":return((e.weekdays||[]).map(i=>Pe(i,r,"short")).join(" & ")||"\u2014")+t;case"nth_weekday":return e.weekday==null||e.nth==null?"\u2014":`${e.nth===-1?s("ord_last",r):s("ord_"+e.nth,r)} ${Pe(e.weekday,r,"long")}${t}`;case"day_of_month":return e.day==null?"\u2014":(e.day===-1?s(e.business?"last_business_day_month":"last_day_month",r):`${s("day_word",r)} ${e.day}`)+t;case"one_time":return a.due_date?Y(a.due_date,r):s("one_time",r);case"manual":return s("manual",r);case"interval":return Be(e.every,e.unit,r)}return a.schedule_type==="one_time"?a.due_date?Y(a.due_date,r):s("one_time",r):a.schedule_type==="manual"?s("manual",r):a.schedule_type==="sensor_based"?s("sensor_based",r):a.interval_days!=null?Be(a.interval_days,a.interval_unit,r):"\u2014"}function hi(a,r){a.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:r},bubbles:!0,composed:!0}))}var oi,ut,li,oe,$r,kr,Ie,Er,Ue,gi,Ve,R=w(()=>{"use strict";P();ii();ai();ni();oi="\u20AC",ut="en",li=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),oe=li.store;wr(ri);$r=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),kr="/maintenance_supporter_locales",Ie=li.inflight;Er=window,Ue=Er.__msDateTimePrefs??={};gi=S` .field { display: flex; flex-direction: column; gap: 4px; } .field-label { font-size: 12px; color: var(--secondary-text-color); } .field-input { @@ -608,10 +608,20 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" } /* Budget KPI tiles in the stats strip (#125) — replaced the full-width - budget-bars row. */ + budget-bars row. The spent amount inherits .stat-value's full 24px bold + so the budget tiles read exactly like the other KPI chips (user report + 2026-08-24: the old 15px override made them visibly smaller); only the + "/ max" suffix stays secondary. */ .stat-item.budget-tile .budget-tile-value { - font-size: 15px; - padding-top: 5px; + white-space: nowrap; + } + /* The "/ max" ratio is its OWN small line between value and bar — inline + it overflowed the ~150px grid cell into the neighbouring tile once the + value took the full 24px. */ + .budget-tile-max { + font-size: 11px; + line-height: 1.2; + color: var(--secondary-text-color); white-space: nowrap; } .budget-tile-bar { @@ -1094,9 +1104,10 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" .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; } + /* Budget tiles on narrow screens (#125): the spent amount keeps the + full chip size (consistency, user report 2026-08-24); the "/ max" + suffix is hidden instead — the bar and the title carry the ratio. */ + .budget-tile-max { display: none; } .group-card { min-width: 0; max-width: 100%; } @@ -1118,7 +1129,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; } } -`});function Sr(a,r){let e=Er[a];if(!e)return a;let t=s(e,r);return t&&t!==e?t:a}function Ar(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 N(a,r,e){if(e=e??s("action_error",r),typeof a=="string")return a;if(typeof a!="object"||a===null)return e;let t=a,i=t.message||t.error?.message||"";if(!i)return e;let n=Ar(i),o=n.field?Sr(n.field,r):"",p=c=>s(c,r).replace("{field}",o).replace("{n}",n.param??"");switch(n.rule){case"too_long":return p("err_too_long");case"too_short":return p("err_too_short");case"value_too_high":return p("err_value_too_high");case"value_too_low":return p("err_value_too_low");case"required":return p("err_required");case"wrong_type":return p("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return p("err_invalid_choice");case"invalid_value":return p("err_invalid_value");default:return i||e}}var Er,oe=w(()=>{"use strict";R();Er={name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"}});var H,ut=w(()=>{"use strict";P();U();H=class extends A{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return l` +`});function Cr(a,r){let e=Tr[a];if(!e)return a;let t=s(e,r);return t&&t!==e?t:a}function Ir(a){let e=a.match(/data\['([^']+)'\]/)?.[1],t;return(t=a.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=a.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=a.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=a.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(a)?{field:e,rule:"required"}:(t=a.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(a)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(a)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function j(a,r,e){if(e=e??s("action_error",r),typeof a=="string")return a;if(typeof a!="object"||a===null)return e;let t=a,i=t.message||t.error?.message||"";if(!i)return e;let n=Ir(i),o=n.field?Cr(n.field,r):"",p=c=>s(c,r).replace("{field}",o).replace("{n}",n.param??"");switch(n.rule){case"too_long":return p("err_too_long");case"too_short":return p("err_too_short");case"value_too_high":return p("err_value_too_high");case"value_too_low":return p("err_value_too_low");case"required":return p("err_required");case"wrong_type":return p("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return p("err_invalid_choice");case"invalid_value":return p("err_invalid_value");default:return i||e}}var Tr,le=w(()=>{"use strict";R();Tr={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"}});var H,ht=w(()=>{"use strict";P();U();H=class extends A{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return l`