diff --git a/.storage/lovelace_resources b/.storage/lovelace_resources index 88e076cb..354de518 100644 --- a/.storage/lovelace_resources +++ b/.storage/lovelace_resources @@ -31,7 +31,7 @@ }, { "id": "53837b51a371459b95ffc0989ce615fb", - "url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749360", + "url": "/hacsfiles/calendar-card-pro/calendar-card-pro.js?hacstag=939311749400", "type": "module" }, { diff --git a/custom_components/maintenance_supporter/__init__.py b/custom_components/maintenance_supporter/__init__.py index c42c377c..12d6f5a4 100644 --- a/custom_components/maintenance_supporter/__init__.py +++ b/custom_components/maintenance_supporter/__init__.py @@ -138,6 +138,9 @@ SERVICE_COMPLETE_SCHEMA = vol.Schema( # #128: who did it — a person ENTITY (validated picker, no free text); # resolved to the linked HA user id. Omitted -> the calling user. vol.Optional("completed_by"): cv.entity_id, + # #133: when the maintenance was actually performed (backfill a past + # completion). Future values are rejected by the coordinator. + vol.Optional("completed_at"): cv.datetime, } ) @@ -474,6 +477,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool: duration=call.data.get("duration"), reading_value=call.data.get("reading_value"), completed_by=completed_by, + completed_at=call.data.get("completed_at"), ) async def _handle_reset(call: ServiceCall) -> None: 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 a51b5cf5..2a735c79 100644 --- a/custom_components/maintenance_supporter/config_flow_options_task_trigger.py +++ b/custom_components/maintenance_supporter/config_flow_options_task_trigger.py @@ -59,6 +59,10 @@ class TriggerStepsMixin(TriggerConfigMixin): parts.append(f"above: {cond['trigger_above']}") if cond.get("trigger_below") is not None: parts.append(f"below: {cond['trigger_below']}") + if cond.get("trigger_equals") is not None: + parts.append(f"= {cond['trigger_equals']}") + if cond.get("trigger_not_equals") is not None: + parts.append(f"≠ {cond['trigger_not_equals']}") if cond.get("trigger_for_minutes"): parts.append(f"for: {cond['trigger_for_minutes']}min") elif ctype == TriggerType.COUNTER: @@ -97,6 +101,10 @@ class TriggerStepsMixin(TriggerConfigMixin): config_parts.append(f"above: {tc['trigger_above']}") if tc.get("trigger_below") is not None: config_parts.append(f"below: {tc['trigger_below']}") + if tc.get("trigger_equals") is not None: + config_parts.append(f"= {tc['trigger_equals']}") + if tc.get("trigger_not_equals") is not None: + config_parts.append(f"≠ {tc['trigger_not_equals']}") if tc.get("trigger_for_minutes"): config_parts.append(f"for: {tc['trigger_for_minutes']}min") elif trigger_type == TriggerType.COUNTER: diff --git a/custom_components/maintenance_supporter/config_flow_trigger.py b/custom_components/maintenance_supporter/config_flow_trigger.py index 0b3f5b41..4590551a 100644 --- a/custom_components/maintenance_supporter/config_flow_trigger.py +++ b/custom_components/maintenance_supporter/config_flow_trigger.py @@ -44,11 +44,14 @@ from .const import ( CONF_TRIGGER_ABOVE, CONF_TRIGGER_ATTRIBUTE, CONF_TRIGGER_BELOW, + CONF_TRIGGER_COMBINATOR, CONF_TRIGGER_DELTA_MODE, CONF_TRIGGER_ENTITY, CONF_TRIGGER_ENTITY_LOGIC, + CONF_TRIGGER_EQUALS, CONF_TRIGGER_FOR_MINUTES, CONF_TRIGGER_FROM_STATE, + CONF_TRIGGER_NOT_EQUALS, CONF_TRIGGER_ON_STATES, CONF_TRIGGER_RUNTIME_HOURS, CONF_TRIGGER_TARGET_CHANGES, @@ -133,7 +136,7 @@ def _entity_logic_field(entity_ids: list[Any]) -> dict[Any, Any]: } -def _interval_warning_fields(hass: HomeAssistant) -> dict[Any, Any]: +def _interval_warning_fields(hass: HomeAssistant, tc: dict[str, Any] | None = None) -> dict[Any, Any]: """The safety-interval + warning-days tail shared by all four type steps.""" return { vol.Optional(CONF_TASK_INTERVAL_DAYS): selector.NumberSelector( @@ -145,6 +148,19 @@ def _interval_warning_fields(hass: HomeAssistant) -> dict[Any, Any]: ) ), vol.Optional(CONF_TASK_INTERVAL_UNIT, default="days"): interval_unit_selector(), + vol.Optional( + CONF_TRIGGER_COMBINATOR, + default=(tc or {}).get(CONF_TRIGGER_COMBINATOR, DEFAULT_ENTITY_LOGIC), + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=[ + selector.SelectOptionDict(value="any", label="Trigger or interval (whichever first)"), + selector.SelectOptionDict(value="all", label="Trigger and interval (both required)"), + ], + mode=selector.SelectSelectorMode.DROPDOWN, + translation_key="trigger_combinator", + ) + ), vol.Optional( CONF_TASK_WARNING_DAYS, default=get_default_warning_days(hass), @@ -156,6 +172,14 @@ def _interval_warning_fields(hass: HomeAssistant) -> dict[Any, Any]: } +def _apply_combinator(tc: dict[str, Any], user_input: dict[str, Any]) -> None: + """Store the trigger∧interval combinator; absence means the default "any".""" + if user_input.get(CONF_TRIGGER_COMBINATOR) == "all": + tc[CONF_TRIGGER_COMBINATOR] = "all" + else: + tc.pop(CONF_TRIGGER_COMBINATOR, None) + + def _state_selector(entity_id: str | None, *, multiple: bool = False) -> Any: """State field bound to the trigger entity (#129 follow-up). @@ -463,8 +487,10 @@ class TriggerConfigMixin: above = user_input.get(CONF_TRIGGER_ABOVE) below = user_input.get(CONF_TRIGGER_BELOW) + equals = user_input.get(CONF_TRIGGER_EQUALS) + not_equals = user_input.get(CONF_TRIGGER_NOT_EQUALS) - if above is None and below is None: + if above is None and below is None and equals is None and not_equals is None: errors["base"] = "invalid_threshold" else: tc = self._current_task["trigger_config"] @@ -472,8 +498,13 @@ class TriggerConfigMixin: tc[CONF_TRIGGER_ABOVE] = above if below is not None: tc[CONF_TRIGGER_BELOW] = below + if equals is not None: + tc[CONF_TRIGGER_EQUALS] = equals + if not_equals is not None: + tc[CONF_TRIGGER_NOT_EQUALS] = not_equals tc[CONF_TRIGGER_FOR_MINUTES] = user_input.get(CONF_TRIGGER_FOR_MINUTES, 0) _apply_recovery_flag(tc, user_input) + _apply_combinator(tc, user_input) # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -509,13 +540,25 @@ class TriggerConfigMixin: step="any", ) ), + vol.Optional(CONF_TRIGGER_EQUALS): selector.NumberSelector( + selector.NumberSelectorConfig( + mode=selector.NumberSelectorMode.BOX, + step="any", + ) + ), + vol.Optional(CONF_TRIGGER_NOT_EQUALS): selector.NumberSelector( + selector.NumberSelectorConfig( + mode=selector.NumberSelectorMode.BOX, + step="any", + ) + ), vol.Optional(CONF_TRIGGER_FOR_MINUTES, default=0): selector.NumberSelector( selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) ), **_recovery_field(self._current_task.get("trigger_config")), } schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", []))) - schema_fields.update(_interval_warning_fields(self.hass)) + schema_fields.update(_interval_warning_fields(self.hass, self._current_task.get("trigger_config"))) return self.async_show_form( step_id=step_id, @@ -541,6 +584,7 @@ class TriggerConfigMixin: tc[CONF_TRIGGER_TARGET_VALUE] = user_input[CONF_TRIGGER_TARGET_VALUE] tc[CONF_TRIGGER_DELTA_MODE] = user_input.get(CONF_TRIGGER_DELTA_MODE, False) _apply_recovery_flag(tc, user_input) + _apply_combinator(tc, user_input) # Counting start value (#102/#103): editable here since the parity # round — an omitted field keeps the value the attribute step # carried over; the backend clears stale Store state on change. @@ -601,7 +645,7 @@ class TriggerConfigMixin: **_recovery_field(prev_tc), } schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", []))) - schema_fields.update(_interval_warning_fields(self.hass)) + schema_fields.update(_interval_warning_fields(self.hass, self._current_task.get("trigger_config"))) return self.async_show_form( step_id=step_id, @@ -638,6 +682,7 @@ class TriggerConfigMixin: tc[CONF_TRIGGER_TO_STATE] = to_state tc[CONF_TRIGGER_TARGET_CHANGES] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1) _apply_recovery_flag(tc, user_input) + _apply_combinator(tc, user_input) # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -669,7 +714,7 @@ class TriggerConfigMixin: **_recovery_field(self._current_task.get("trigger_config")), } schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", []))) - schema_fields.update(_interval_warning_fields(self.hass)) + schema_fields.update(_interval_warning_fields(self.hass, self._current_task.get("trigger_config"))) return self.async_show_form( step_id=step_id, @@ -701,6 +746,7 @@ class TriggerConfigMixin: else: tc.pop(CONF_TRIGGER_ON_STATES, None) _apply_recovery_flag(tc, user_input) + _apply_combinator(tc, user_input) # Multi-entity: store entity_logic if multiple entities selected entity_ids = tc.get("entity_ids", []) @@ -740,7 +786,7 @@ class TriggerConfigMixin: **_recovery_field(current_tc), } schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", []))) - schema_fields.update(_interval_warning_fields(self.hass)) + schema_fields.update(_interval_warning_fields(self.hass, self._current_task.get("trigger_config"))) return self.async_show_form( step_id=step_id, @@ -920,6 +966,12 @@ class TriggerConfigMixin: cond["trigger_above"] = above if below is not None: cond["trigger_below"] = below + equals = user_input.get(CONF_TRIGGER_EQUALS) + if equals is not None: + cond["trigger_equals"] = equals + not_equals = user_input.get(CONF_TRIGGER_NOT_EQUALS) + if not_equals is not None: + cond["trigger_not_equals"] = not_equals for_min = user_input.get(CONF_TRIGGER_FOR_MINUTES) if for_min: cond["trigger_for_minutes"] = for_min @@ -957,6 +1009,12 @@ class TriggerConfigMixin: vol.Optional(CONF_TRIGGER_BELOW): selector.NumberSelector( selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX) ), + vol.Optional(CONF_TRIGGER_EQUALS): selector.NumberSelector( + selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX) + ), + vol.Optional(CONF_TRIGGER_NOT_EQUALS): selector.NumberSelector( + selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX) + ), vol.Optional(CONF_TRIGGER_FOR_MINUTES, default=0): selector.NumberSelector( selector.NumberSelectorConfig( min=0, diff --git a/custom_components/maintenance_supporter/const.py b/custom_components/maintenance_supporter/const.py index 88380c87..4bfbdfb4 100644 --- a/custom_components/maintenance_supporter/const.py +++ b/custom_components/maintenance_supporter/const.py @@ -401,7 +401,14 @@ DEFAULT_ENTITY_LOGIC = "any" CONF_TRIGGER_ATTRIBUTE = "trigger_attribute" CONF_TRIGGER_ABOVE = "trigger_above" CONF_TRIGGER_BELOW = "trigger_below" +CONF_TRIGGER_EQUALS = "trigger_equals" +CONF_TRIGGER_NOT_EQUALS = "trigger_not_equals" CONF_TRIGGER_FOR_MINUTES = "trigger_for_minutes" +# How the trigger combines with the safety interval on the same task: +# "any" (default) — whichever fires first makes the task due; +# "all" — the task only becomes due once the trigger fired AND the interval +# elapsed (the interval acts as a minimum age, not a deadline). +CONF_TRIGGER_COMBINATOR = "trigger_combinator" CONF_TRIGGER_TARGET_VALUE = "trigger_target_value" CONF_TRIGGER_DELTA_MODE = "trigger_delta_mode" CONF_TRIGGER_BASELINE_VALUE = "trigger_baseline_value" @@ -622,6 +629,22 @@ class HistoryEntryType(StrEnum): TRIGGER_REPLACED = "trigger_replaced" +# Entry types that reset the maintenance cycle — the ``last_performed`` anchor +# is always the LATEST such entry (by timestamp). Shared by the history-edit +# reconciliation and the backdated-completion path (#133) so the two can't +# drift on what counts as a cycle anchor. MISSED is included because +# ``skip(as_missed=True)`` moves ``last_performed`` exactly like a skip — +# the edit path historically omitted it (latent reconciliation gap). +LIFECYCLE_HISTORY_TYPES = frozenset( + { + HistoryEntryType.COMPLETED, + HistoryEntryType.RESET, + HistoryEntryType.SKIPPED, + HistoryEntryType.MISSED, + } +) + + class MaintenanceFeedback(StrEnum): """Feedback from user about whether maintenance was needed.""" diff --git a/custom_components/maintenance_supporter/coordinator.py b/custom_components/maintenance_supporter/coordinator.py index 3f76feef..d6cd701c 100644 --- a/custom_components/maintenance_supporter/coordinator.py +++ b/custom_components/maintenance_supporter/coordinator.py @@ -4,7 +4,7 @@ from __future__ import annotations import logging import time -from datetime import date, timedelta +from datetime import date, datetime, timedelta from typing import TYPE_CHECKING, Any from homeassistant.config_entries import ConfigEntry @@ -907,6 +907,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): used_parts: list[dict[str, Any]] | None = None, auto: bool = False, unattended: bool = False, + completed_at: datetime | None = None, ) -> None: """Mark a task as completed and persist. @@ -915,12 +916,28 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): voice command. Those paths attach a canned provenance note ("Completed via NFC tag"), which must NOT be mistaken for the note a task demands: the point of a required note is that somebody wrote it. + + ``completed_at`` (#133) records the completion at a past moment + (dialog date field / service parameter). Validated HERE — the one + point the WS command and the HA service both funnel through. See + :meth:`MaintenanceTask.complete` for the latest-vs-backfill split. """ merged = self._get_merged_tasks_data() if task_id not in merged: _LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title) return + if completed_at is not None: + # Naive input (datetime-local field, service YAML) means local time. + if completed_at.tzinfo is None: + completed_at = completed_at.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE) + if completed_at > dt_util.now(): + raise ServiceValidationError( + "The completion date cannot be in the future", + translation_domain=DOMAIN, + translation_key="completed_at_in_future", + ) + # Required completion details. Checked HERE — the one point every # surface funnels through — so a task demanding a note cannot be # closed out from a button, the to-do list, an NFC tag, a @@ -972,29 +989,38 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # because the task is already completed. Deliberately a SEPARATE map # from _recently_completed: that one is also stamped by skip/reset, # and a complete right after a date-correction reset must go through. - 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( - "Ignoring duplicate completion of %s within %.0fs (double-tap from a second device?)", - task_id, - time.monotonic() - last_manual, - ) - return - # Stamp the guard NOW, before any await (the photo-link below yields the - # loop). Stamping only at the end let two photo-carrying completions in - # the same tick both pass the check and interleave → double rotation / - # part-consume / history entry. - self._recent_manual_completions[task_id] = time.monotonic() + # An explicit completed_at is a deliberate backfill, not a double-tap + # — 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: + 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( + "Ignoring duplicate completion of %s within %.0fs (double-tap from a second device?)", + task_id, + time.monotonic() - last_manual, + ) + return + # Stamp the guard NOW, before any await (the photo-link below yields the + # loop). Stamping only at the end let two photo-carrying completions in + # the same tick both pass the check and interleave → double rotation / + # part-consume / history entry. + self._recent_manual_completions[task_id] = 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() - # Compute actual interval before updating last_performed + # Compute actual interval before updating last_performed. Anchored on + # the EFFECTIVE moment: "did it three days ago" must feed the real + # elapsed interval into adaptive learning, and a pure backfill yields + # a negative interval the learning guard below already rejects. actual_interval: int | None = None if task.last_performed: try: last = date.fromisoformat(task.last_performed) - actual_interval = (dt_util.now().date() - last).days + actual_interval = (effective_ts.date() - last).days except (ValueError, TypeError): actual_interval = None @@ -1029,7 +1055,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): if isinstance(link, dict) and link.get("part_id") ] - task.complete( + is_latest = task.complete( notes=notes, cost=cost, duration=duration, @@ -1040,10 +1066,13 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): reading_value=reading_value, used_parts=enriched_used, auto=auto, + completed_at=completed_at, ) # #73: a completed cycle retires its in-cycle checklist ticks — the - # snapshot that matters is in the history entry above. - self._store.clear_checklist_progress(task_id) + # snapshot that matters is in the history entry above. A pure backfill + # closed no current cycle, so the live ticks stay. + if is_latest: + self._store.clear_checklist_progress(task_id) # Link the completion photo to this task so it also surfaces under the # object's documents and is deref'd correctly on cleanup. Best-effort: @@ -1051,8 +1080,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): if photo_doc_id: await self._link_completion_photo(photo_doc_id, task_id) - # Update adaptive scheduling if enabled - if task.adaptive_config and task.adaptive_config.get("enabled"): + # Update adaptive scheduling if enabled. Gated on is_latest: a pure + # backfill is not a fresh service interval (its negative + # actual_interval would be rejected below anyway — the gate makes the + # intent explicit and keeps the seasonal stamps off stale months). + if is_latest and task.adaptive_config and task.adaptive_config.get("enabled"): if actual_interval is not None and actual_interval > 0: from .helpers.interval_analyzer import IntervalAnalyzer @@ -1060,11 +1092,12 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): # Store the base interval for blending reference if "base_interval" not in task.adaptive_config: task.adaptive_config["base_interval"] = task.interval_days or DEFAULT_INTERVAL_DAYS - # Inject hemisphere, current month/date for seasonal awareness + # Inject hemisphere + month/date of the EFFECTIVE completion + # moment for seasonal awareness (a completion logged today but + # performed in March belongs to March). task.adaptive_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north" - now = dt_util.now() - task.adaptive_config["_current_month"] = now.month - task.adaptive_config["_current_date"] = now.date().isoformat() + task.adaptive_config["_current_month"] = effective_ts.month + task.adaptive_config["_current_date"] = effective_ts.date().isoformat() updated_config = analyzer.update_on_completion(task.adaptive_config, actual_interval, feedback) task.adaptive_config = updated_config @@ -1118,6 +1151,14 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]): duration=duration, feedback=feedback, completed_by=completed_by, + # #133: the history entry's own timestamp — identical to what + # the history records, so automations can attribute backdated + # completions to the right period instead of time_fired. + completed_at=effective_ts.isoformat(), + # True when this completion was OLDER than the latest one (a + # pure history backfill): the action listener skips + # on_complete_action for those, and automations can filter. + backfill=not is_latest, ), ) diff --git a/custom_components/maintenance_supporter/entity/triggers/threshold.py b/custom_components/maintenance_supporter/entity/triggers/threshold.py index 11098f0f..4d553e65 100644 --- a/custom_components/maintenance_supporter/entity/triggers/threshold.py +++ b/custom_components/maintenance_supporter/entity/triggers/threshold.py @@ -13,6 +13,7 @@ from homeassistant.util import dt as dt_util if TYPE_CHECKING: from ...sensor import MaintenanceSensor +from ...helpers.trigger_fallback import threshold_exceeds from .base_trigger import BaseTrigger _LOGGER = logging.getLogger(__name__) @@ -24,6 +25,7 @@ class ThresholdTrigger(BaseTrigger): Supports: - Above threshold (value > above) - Below threshold (value < below) + - Equals / not-equals a discrete level (value = / ≠ equals) - Duration requirement (value must exceed for X minutes) """ @@ -38,6 +40,8 @@ class ThresholdTrigger(BaseTrigger): self._above: float | None = trigger_config.get("trigger_above") self._below: float | None = trigger_config.get("trigger_below") + self._equals: float | None = trigger_config.get("trigger_equals") + self._not_equals: float | None = trigger_config.get("trigger_not_equals") self._for_minutes: int = trigger_config.get("trigger_for_minutes", 0) self._threshold_exceeded = False @@ -63,11 +67,13 @@ class ThresholdTrigger(BaseTrigger): def _value_exceeds_threshold(self, value: float) -> bool: """Check if the value exceeds configured thresholds.""" - if self._above is not None and value > self._above: - return True - if self._below is not None and value < self._below: - return True - return False + return threshold_exceeds( + value, + above=self._above, + below=self._below, + equals=self._equals, + not_equals=self._not_equals, + ) def evaluate(self, value: float) -> bool: """Evaluate threshold condition.""" diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts index bbebc6be..32777567 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/complete-dialog.test.ts @@ -101,6 +101,41 @@ describe("complete-dialog", () => { expect("feedback" in msg).to.be.false; expect("checklist_state" in msg).to.be.false; expect("photo_doc_id" in msg).to.be.false; + expect("completed_at" in msg).to.be.false; + }); + + it("sends completed_at with seconds re-added when a backdate is picked (#133)", async () => { + const { el, sent } = await mount(); + const dt = el.shadowRoot!.querySelector('input[type="datetime-local"]')!; + expect(dt, "backdate field rendered").to.exist; + dt.value = "2026-01-10T14:30"; + dt.dispatchEvent(new Event("change")); + await el.updateComplete; + + clickComplete(el); + await new Promise((r) => setTimeout(r, 10)); + + const msg = sent.find((m) => m.type === "maintenance_supporter/task/complete")!; + expect(msg).to.exist; + expect(msg.completed_at).to.equal("2026-01-10T14:30:00"); + }); + + it("rejects a future completed_at client-side without a WS roundtrip (#133)", async () => { + const { el, sent } = await mount(); + const future = new Date(Date.now() + 48 * 3600 * 1000); + const pad = (n: number) => String(n).padStart(2, "0"); + const v = `${future.getFullYear()}-${pad(future.getMonth() + 1)}-${pad(future.getDate())}T12:00`; + const dt = el.shadowRoot!.querySelector('input[type="datetime-local"]')!; + dt.value = v; + dt.dispatchEvent(new Event("change")); + await el.updateComplete; + + clickComplete(el); + await new Promise((r) => setTimeout(r, 10)); + + expect(sent.find((m) => m.type === "maintenance_supporter/task/complete")).to.equal(undefined); + await el.updateComplete; + expect(el.shadowRoot!.textContent).to.include("future"); }); it("attaches an uploaded photo as photo_doc_id", async () => { diff --git a/custom_components/maintenance_supporter/frontend-src/__tests__/task-dialog-trigger-roundtrip.test.ts b/custom_components/maintenance_supporter/frontend-src/__tests__/task-dialog-trigger-roundtrip.test.ts index 2752c043..cc933ef8 100644 --- a/custom_components/maintenance_supporter/frontend-src/__tests__/task-dialog-trigger-roundtrip.test.ts +++ b/custom_components/maintenance_supporter/frontend-src/__tests__/task-dialog-trigger-roundtrip.test.ts @@ -63,6 +63,31 @@ describe("task-dialog trigger_config roundtrip closure (#103 class)", () => { }); }); + it("threshold: =/≠ limits and the all-combinator survive", async () => { + const tc = await saveRoundtrip({ + type: "threshold", + entity_id: "sensor.a", + trigger_equals: 3, + trigger_not_equals: 1, + trigger_combinator: "all", + }); + expect(tc).to.deep.include({ + type: "threshold", + trigger_equals: 3, + trigger_not_equals: 1, + trigger_combinator: "all", + }); + }); + + it("combinator defaults to any and is then omitted from the payload", async () => { + const tc = await saveRoundtrip({ + type: "threshold", + entity_id: "sensor.a", + trigger_above: 80, + }); + expect(tc.trigger_combinator, "any must not be persisted").to.equal(undefined); + }); + it("threshold stored with ONLY plural entity_ids survives an edit (#106)", async () => { // The Battery Fleet task's trigger has no singular entity_id; the save // path gates on _triggerEntityId, so before the hydration fallback an @@ -179,4 +204,34 @@ describe("task-dialog trigger_config roundtrip closure (#103 class)", () => { entity_logic: "any", }); }); + + it("compound: condition =/≠ limits and task-level combinator survive", async () => { + const tc = await saveRoundtrip({ + type: "compound", + compound_logic: "AND", + trigger_combinator: "all", + conditions: [ + { + type: "threshold", + entity_id: "sensor.a", + entity_ids: ["sensor.a"], + trigger_equals: 3, + trigger_not_equals: 1, + }, + { + type: "threshold", + entity_id: "sensor.b", + entity_ids: ["sensor.b"], + trigger_above: 80, + }, + ], + }); + expect(tc.trigger_combinator).to.equal("all"); + const conds = tc.conditions as Array>; + expect(conds[0]).to.deep.include({ + type: "threshold", + trigger_equals: 3, + trigger_not_equals: 1, + }); + }); }); diff --git a/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts index c03cfa1a..2dba9d55 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/complete-dialog.ts @@ -53,6 +53,8 @@ export class MaintenanceCompleteDialog extends LitElement { @state() private _photoUploading = false; @state() private _readingValue = ""; @state() private _restockQty = ""; + /** #133: optional backdated completion moment (datetime-local value; "" = now). */ + @state() private _completedAt = ""; /** Keyed by `partLinkKey` — the (entry_id, part_id) pair — because two * objects can carry the same part id, so part_id alone would merge pools. */ @state() private _usedParts: Record = {}; @@ -81,6 +83,7 @@ export class MaintenanceCompleteDialog extends LitElement { this._photoUploading = false; this._readingValue = ""; this._restockQty = this.restockDefault !== null ? String(this.restockDefault) : ""; + this._completedAt = ""; // #99: prefill "parts used" with the task's fixed links — the user can // untick or adjust before completing. The whole link is kept, entry_id // included, so a shared pool survives the edit (#111). @@ -167,6 +170,18 @@ export class MaintenanceCompleteDialog extends LitElement { if (this._photoDocId) { data.photo_doc_id = this._photoDocId; } + if (this._completedAt) { + // Client-side guard mirrors the backend rule — a picked future moment + // fails fast with a localized message instead of a WS roundtrip. + if (new Date(this._completedAt).getTime() > Date.now()) { + this._error = t("completed_at_future_error", this.lang); + this._loading = false; + return; + } + // Re-add seconds if the datetime-local input drops them (same + // normalisation as the history-edit dialog). + data.completed_at = this._completedAt.length === 16 ? `${this._completedAt}:00` : this._completedAt; + } if (this._readingValue !== "") { const rv = parseFloat(this._readingValue); if (!isNaN(rv)) data.reading_value = rv; @@ -371,6 +386,13 @@ export class MaintenanceCompleteDialog extends LitElement { .value=${this._duration} @input=${(e: Event) => (this._duration = (e.target as HTMLInputElement).value)} /> +
${t("completion_photo_optional", L)}${this._req("photo")} ${this._photoPreview diff --git a/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts b/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts index f66b4dfc..6597fca3 100644 --- a/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts +++ b/custom_components/maintenance_supporter/frontend-src/components/task-dialog.ts @@ -35,6 +35,8 @@ interface CompoundConditionDraft { attribute: string; // "" = use the entity state above: string; below: string; + equals: string; + notEquals: string; forMinutes: string; targetValue: string; deltaMode: boolean; @@ -50,7 +52,8 @@ interface CompoundConditionDraft { function emptyCondition(): CompoundConditionDraft { return { - entityIds: "", type: "threshold", attribute: "", above: "", below: "", forMinutes: "0", + entityIds: "", type: "threshold", attribute: "", above: "", below: "", + equals: "", notEquals: "", forMinutes: "0", targetValue: "", deltaMode: false, fromState: "", toState: "", targetChanges: "", runtimeHours: "", onStates: "", carry: {}, }; @@ -60,7 +63,7 @@ function emptyCondition(): CompoundConditionDraft { * travels through `carry` untouched. */ const MANAGED_CONDITION_KEYS = new Set([ "entity_id", "entity_ids", "type", "attribute", - "trigger_above", "trigger_below", "trigger_for_minutes", + "trigger_above", "trigger_below", "trigger_equals", "trigger_not_equals", "trigger_for_minutes", "trigger_target_value", "trigger_delta_mode", "trigger_from_state", "trigger_to_state", "trigger_target_changes", "trigger_runtime_hours", "trigger_on_states", @@ -75,6 +78,8 @@ function conditionToDraft(c: TriggerConfig): CompoundConditionDraft { attribute: c.attribute || "", above: c.trigger_above?.toString() ?? "", below: c.trigger_below?.toString() ?? "", + equals: c.trigger_equals?.toString() ?? "", + notEquals: c.trigger_not_equals?.toString() ?? "", forMinutes: c.trigger_for_minutes?.toString() ?? "0", targetValue: c.trigger_target_value?.toString() ?? "", deltaMode: c.trigger_delta_mode || false, @@ -99,6 +104,8 @@ function draftToCondition(d: CompoundConditionDraft): TriggerConfig | null { if (d.type === "threshold") { const a = parseFloat(d.above); if (!isNaN(a)) c.trigger_above = a; const b = parseFloat(d.below); if (!isNaN(b)) c.trigger_below = b; + const eq = parseFloat(d.equals); if (!isNaN(eq)) c.trigger_equals = eq; + const ne = parseFloat(d.notEquals); if (!isNaN(ne)) c.trigger_not_equals = ne; const f = parseInt(d.forMinutes, 10); if (!isNaN(f)) c.trigger_for_minutes = f; } else if (d.type === "counter") { const v = parseFloat(d.targetValue); if (!isNaN(v)) c.trigger_target_value = v; @@ -202,7 +209,11 @@ export class MaintenanceTaskDialog extends LitElement { @state() private _triggerType = "threshold"; @state() private _triggerAbove = ""; @state() private _triggerBelow = ""; + @state() private _triggerEquals = ""; + @state() private _triggerNotEquals = ""; @state() private _triggerForMinutes = "0"; + /** Trigger vs. safety interval: "any" = whichever first (default), "all" = both required. */ + @state() private _triggerCombinator: "any" | "all" = "any"; @state() private _triggerTargetValue = ""; @state() private _triggerDeltaMode = false; @state() private _triggerBaselineValue = ""; @@ -426,7 +437,10 @@ export class MaintenanceTaskDialog extends LitElement { this._triggerType = tc.type || "threshold"; this._triggerAbove = tc.trigger_above?.toString() || ""; this._triggerBelow = tc.trigger_below?.toString() || ""; + this._triggerEquals = tc.trigger_equals?.toString() || ""; + this._triggerNotEquals = tc.trigger_not_equals?.toString() || ""; this._triggerForMinutes = tc.trigger_for_minutes?.toString() || "0"; + this._triggerCombinator = tc.trigger_combinator === "all" ? "all" : "any"; this._triggerTargetValue = tc.trigger_target_value?.toString() || ""; this._triggerDeltaMode = tc.trigger_delta_mode || false; this._triggerBaselineValue = tc.trigger_baseline_value?.toString() || ""; @@ -530,7 +544,10 @@ export class MaintenanceTaskDialog extends LitElement { this._triggerType = "threshold"; this._triggerAbove = ""; this._triggerBelow = ""; + this._triggerEquals = ""; + this._triggerNotEquals = ""; this._triggerForMinutes = "0"; + this._triggerCombinator = "any"; this._triggerTargetValue = ""; this._triggerDeltaMode = false; this._triggerBaselineValue = ""; @@ -1089,6 +1106,7 @@ export class MaintenanceTaskDialog extends LitElement { conditions, }; if (this._autoCompleteOnRecovery) triggerConfig.auto_complete_on_recovery = true; + if (this._triggerCombinator === "all") triggerConfig.trigger_combinator = "all"; data.trigger_config = triggerConfig; } else if (this._taskId) { data.trigger_config = null; @@ -1104,6 +1122,7 @@ export class MaintenanceTaskDialog extends LitElement { }; if (this._triggerAttribute) triggerConfig.attribute = this._triggerAttribute; if (this._autoCompleteOnRecovery) triggerConfig.auto_complete_on_recovery = true; + if (this._triggerCombinator === "all") triggerConfig.trigger_combinator = "all"; // Multi-entity: store entity_logic for all trigger types if (entityIds.length > 1) { @@ -1113,6 +1132,8 @@ export class MaintenanceTaskDialog extends LitElement { if (this._triggerType === "threshold") { if (this._triggerAbove) { const v = parseFloat(this._triggerAbove); if (!isNaN(v)) triggerConfig.trigger_above = v; } if (this._triggerBelow) { const v = parseFloat(this._triggerBelow); if (!isNaN(v)) triggerConfig.trigger_below = v; } + if (this._triggerEquals) { const v = parseFloat(this._triggerEquals); if (!isNaN(v)) triggerConfig.trigger_equals = v; } + if (this._triggerNotEquals) { const v = parseFloat(this._triggerNotEquals); if (!isNaN(v)) triggerConfig.trigger_not_equals = v; } if (this._triggerForMinutes) { const v = parseInt(this._triggerForMinutes, 10); if (!isNaN(v)) triggerConfig.trigger_for_minutes = v; } } else if (this._triggerType === "counter") { if (this._triggerTargetValue) { const v = parseFloat(this._triggerTargetValue); if (!isNaN(v)) triggerConfig.trigger_target_value = v; } @@ -1342,6 +1363,19 @@ export class MaintenanceTaskDialog extends LitElement { @input=${(e: Event) => (this._intervalDays = (e.target as HTMLInputElement).value)} > ${this._intervalDays ? this._renderUnitSelect() : nothing} + ${this._intervalDays + ? html` +
+ + +
+ ` + : nothing} `; } @@ -1636,6 +1670,10 @@ export class MaintenanceTaskDialog extends LitElement { @input=${(e: Event) => this._patchCondition(i, { above: (e.target as HTMLInputElement).value })}> this._patchCondition(i, { below: (e.target as HTMLInputElement).value })}> + this._patchCondition(i, { equals: (e.target as HTMLInputElement).value })}> + this._patchCondition(i, { notEquals: (e.target as HTMLInputElement).value })}> this._patchCondition(i, { forMinutes: (e.target as HTMLInputElement).value })}> `; @@ -2122,6 +2160,20 @@ export class MaintenanceTaskDialog extends LitElement { .value=${this._triggerBelow} @input=${(e: Event) => (this._triggerBelow = (e.target as HTMLInputElement).value)} > + (this._triggerEquals = (e.target as HTMLInputElement).value)} + > + (this._triggerNotEquals = (e.target as HTMLInputElement).value)} + > \" — nebo seznam názvů pro omezení karty na více objektů." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/da.json b/custom_components/maintenance_supporter/frontend-src/locales/da.json index 9449d4f7..3fb9d80c 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/da.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/da.json @@ -129,6 +129,8 @@ "notes_optional": "Noter (valgfrit)", "cost_optional": "Omkostning (valgfrit)", "duration_minutes": "Varighed i minutter (valgfrit)", + "completed_at_optional": "Udført den (valgfrit, tomt = nu)", + "completed_at_future_error": "Udførelsesdatoen må ikke ligge i fremtiden.", "days": "dage", "day": "dag", "today": "I dag", @@ -193,9 +195,14 @@ "use_entity_state": "Brug enhedstilstand (ingen attribut)", "trigger_above": "Udløs over", "trigger_below": "Udløs under", + "trigger_equals": "Udløs ved lig med (=)", + "trigger_not_equals": "Udløs ved forskellig fra (≠)", "for_at_least_minutes": "I mindst (minutter)", "safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)", "safety_interval": "Sikkerhedsinterval (valgfrit)", + "trigger_combinator": "Kombinér trigger og interval", + "trigger_combinator_any": "Trigger eller interval (først opfyldt)", + "trigger_combinator_all": "Trigger og interval (begge kræves)", "delta_mode": "Delta-tilstand", "from_state_optional": "Fra tilstand (valgfrit)", "to_state_optional": "Til tilstand (valgfrit)", @@ -850,5 +857,17 @@ "gs_label": "Kom godt i gang — disse tips forsvinder, efterhånden som opsætningen vokser", "gs_setups_chip": "Foreslåede opsætninger fandt {n} enheder med forudindstillede udløsere", "gs_adopt_chip": "{n} problemsensorer kan blive vedligeholdelsesopgaver", - "gs_fleet_chip": "Ét klik opsætter batteriflåden" + "gs_fleet_chip": "Ét klik opsætter batteriflåden", + "cal_editor_window": "Standardvindue", + "cal_editor_window_week": "Uge (7 dage)", + "cal_editor_window_fortnight": "To uger (14 dage)", + "cal_editor_window_month": "Måned (30 dage, standard)", + "cal_editor_window_year": "År (365 dage, tomme dage skjult)", + "cal_editor_show_chips": "Vis vinduechips i kortet", + "cal_editor_chips_hint": "Skjul chips, når kortet er indlejret i en strategivisning, der allerede fungerer som vinduesvælger.", + "cal_editor_show_user_filter": "Vis brugerfilter", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/de.json b/custom_components/maintenance_supporter/frontend-src/locales/de.json index 900e84a9..21e95890 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/de.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/de.json @@ -129,6 +129,8 @@ "notes_optional": "Notizen (optional)", "cost_optional": "Kosten (optional)", "duration_minutes": "Dauer in Minuten (optional)", + "completed_at_optional": "Erledigt am (optional, leer = jetzt)", + "completed_at_future_error": "Das Erledigungsdatum darf nicht in der Zukunft liegen.", "days": "Tage", "day": "Tag", "today": "Heute", @@ -193,9 +195,14 @@ "use_entity_state": "Entitäts-Zustand verwenden (kein Attribut)", "trigger_above": "Auslösen wenn über", "trigger_below": "Auslösen wenn unter", + "trigger_equals": "Auslösen bei genau (=)", + "trigger_not_equals": "Auslösen bei abweichend von (≠)", "for_at_least_minutes": "Für mindestens (Minuten)", "safety_interval_days": "Sicherheitsintervall (Tage, optional)", "safety_interval": "Sicherheitsintervall (optional)", + "trigger_combinator": "Trigger und Intervall kombinieren", + "trigger_combinator_any": "Trigger oder Intervall (zuerst erfüllt)", + "trigger_combinator_all": "Trigger und Intervall (beides erforderlich)", "delta_mode": "Delta-Modus", "from_state_optional": "Von Zustand (optional)", "to_state_optional": "Zu Zustand (optional)", @@ -850,5 +857,17 @@ "gs_label": "Erste Schritte — diese Hinweise verschwinden, wenn dein Setup wächst", "gs_setups_chip": "Vorgeschlagene Setups: {n} Geräte mit vorverdrahteten Auslösern gefunden", "gs_adopt_chip": "{n} Problem-Sensoren können zu Wartungsaufgaben werden", - "gs_fleet_chip": "Ein Klick richtet die Batterieflotte ein" + "gs_fleet_chip": "Ein Klick richtet die Batterieflotte ein", + "cal_editor_window": "Standard-Zeitfenster", + "cal_editor_window_week": "Woche (7 Tage)", + "cal_editor_window_fortnight": "Zwei Wochen (14 Tage)", + "cal_editor_window_month": "Monat (30 Tage, Standard)", + "cal_editor_window_year": "Jahr (365 Tage, leere Tage ausgeblendet)", + "cal_editor_show_chips": "Zeitfenster-Chips in der Karte anzeigen", + "cal_editor_chips_hint": "Chips ausblenden, wenn die Karte in einer Strategie-Ansicht steckt, die bereits als Zeitfenster-Auswahl dient.", + "cal_editor_show_user_filter": "Benutzerfilter-Dropdown anzeigen", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/en.json b/custom_components/maintenance_supporter/frontend-src/locales/en.json index 37f9159e..364c6ff5 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/en.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/en.json @@ -129,6 +129,8 @@ "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", @@ -193,9 +195,14 @@ "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 (≠)", "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)", @@ -850,5 +857,17 @@ "gs_label": "Getting started — 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" + "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: \"\" — or a list of names to restrict the card to several objects." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/es.json b/custom_components/maintenance_supporter/frontend-src/locales/es.json index 7701e67e..e45af7c8 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/es.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/es.json @@ -128,6 +128,8 @@ "notes_optional": "Notas (opcional)", "cost_optional": "Coste (opcional)", "duration_minutes": "Duración en minutos (opcional)", + "completed_at_optional": "Completado el (opcional, vacío = ahora)", + "completed_at_future_error": "La fecha de finalización no puede estar en el futuro.", "days": "días", "day": "día", "today": "Hoy", @@ -192,9 +194,14 @@ "use_entity_state": "Usar estado de la entidad (sin atributo)", "trigger_above": "Activar por encima de", "trigger_below": "Activar por debajo de", + "trigger_equals": "Activar cuando sea igual a (=)", + "trigger_not_equals": "Activar cuando sea distinto de (≠)", "for_at_least_minutes": "Durante al menos (minutos)", "safety_interval_days": "Intervalo de seguridad (días, opcional)", "safety_interval": "Intervalo de seguridad (opcional)", + "trigger_combinator": "Combinar disparador e intervalo", + "trigger_combinator_any": "Disparador o intervalo (el primero)", + "trigger_combinator_all": "Disparador e intervalo (ambos requeridos)", "delta_mode": "Modo delta", "from_state_optional": "Desde estado (opcional)", "to_state_optional": "Hasta estado (opcional)", @@ -850,5 +857,17 @@ "gs_label": "Primeros pasos: estas sugerencias desaparecen a medida que crece tu configuración", "gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados", "gs_adopt_chip": "{n} sensores de problemas pueden convertirse en tareas de mantenimiento", - "gs_fleet_chip": "Un clic configura la flota de baterías" + "gs_fleet_chip": "Un clic configura la flota de baterías", + "cal_editor_window": "Ventana predeterminada", + "cal_editor_window_week": "Semana (7 días)", + "cal_editor_window_fortnight": "Quincena (14 días)", + "cal_editor_window_month": "Mes (30 días, predeterminado)", + "cal_editor_window_year": "Año (365 días, días vacíos ocultos)", + "cal_editor_show_chips": "Mostrar chips de ventana en la tarjeta", + "cal_editor_chips_hint": "Oculta los chips cuando la tarjeta está en una vista de estrategia que ya sirve como selector de ventana.", + "cal_editor_show_user_filter": "Mostrar filtro de usuario", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fi.json b/custom_components/maintenance_supporter/frontend-src/locales/fi.json index 19ca1a84..55270f58 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fi.json @@ -129,6 +129,8 @@ "notes_optional": "Muistiinpanot (valinnainen)", "cost_optional": "Kustannus (valinnainen)", "duration_minutes": "Kesto minuutteina (valinnainen)", + "completed_at_optional": "Suoritettu (valinnainen, tyhjä = nyt)", + "completed_at_future_error": "Suorituspäivä ei voi olla tulevaisuudessa.", "days": "päivää", "day": "päivä", "today": "Tänään", @@ -193,9 +195,14 @@ "use_entity_state": "Käytä entiteetin tilaa (ei attribuuttia)", "trigger_above": "Laukaise yli", "trigger_below": "Laukaise alle", + "trigger_equals": "Laukaise kun yhtä suuri kuin (=)", + "trigger_not_equals": "Laukaise kun eri kuin (≠)", "for_at_least_minutes": "Vähintään (minuuttia)", "safety_interval_days": "Turvaväli (päivää, valinnainen)", "safety_interval": "Turvaväli (valinnainen)", + "trigger_combinator": "Yhdistä laukaisin ja väli", + "trigger_combinator_any": "Laukaisin tai väli (ensin täyttyvä)", + "trigger_combinator_all": "Laukaisin ja väli (molemmat vaaditaan)", "delta_mode": "Delta-tila", "from_state_optional": "Lähtötilasta (valinnainen)", "to_state_optional": "Kohdetilaan (valinnainen)", @@ -850,5 +857,17 @@ "gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa", "gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla", "gs_adopt_chip": "{n} ongelma-anturia voi muuttua huoltotehtäviksi", - "gs_fleet_chip": "Yksi napsautus määrittää akkukannan" + "gs_fleet_chip": "Yksi napsautus määrittää akkukannan", + "cal_editor_window": "Oletusikkuna", + "cal_editor_window_week": "Viikko (7 päivää)", + "cal_editor_window_fortnight": "Kaksi viikkoa (14 päivää)", + "cal_editor_window_month": "Kuukausi (30 päivää, oletus)", + "cal_editor_window_year": "Vuosi (365 päivää, tyhjät päivät piilotettu)", + "cal_editor_show_chips": "Näytä ikkunavalinnat kortissa", + "cal_editor_chips_hint": "Piilota valinnat, kun kortti on strategianäkymässä, joka jo toimii ikkunan valitsimena.", + "cal_editor_show_user_filter": "Näytä käyttäjäsuodatin", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/fr.json b/custom_components/maintenance_supporter/frontend-src/locales/fr.json index 5dba5716..584df1dc 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/fr.json @@ -128,6 +128,8 @@ "notes_optional": "Notes (optionnel)", "cost_optional": "Coût (optionnel)", "duration_minutes": "Durée en minutes (optionnel)", + "completed_at_optional": "Effectué le (optionnel, vide = maintenant)", + "completed_at_future_error": "La date d'achèvement ne peut pas être dans le futur.", "days": "jours", "day": "jour", "today": "Aujourd'hui", @@ -192,9 +194,14 @@ "use_entity_state": "Utiliser l'état de l'entité (pas d'attribut)", "trigger_above": "Déclencher au-dessus de", "trigger_below": "Déclencher en dessous de", + "trigger_equals": "Déclencher si égal à (=)", + "trigger_not_equals": "Déclencher si différent de (≠)", "for_at_least_minutes": "Pendant au moins (minutes)", "safety_interval_days": "Intervalle de sécurité (jours, optionnel)", "safety_interval": "Intervalle de sécurité (optionnel)", + "trigger_combinator": "Combiner déclencheur et intervalle", + "trigger_combinator_any": "Déclencheur ou intervalle (premier atteint)", + "trigger_combinator_all": "Déclencheur et intervalle (les deux requis)", "delta_mode": "Mode delta", "from_state_optional": "État source (optionnel)", "to_state_optional": "État cible (optionnel)", @@ -850,5 +857,17 @@ "gs_label": "Premiers pas — ces conseils disparaissent à mesure que votre configuration grandit", "gs_setups_chip": "Configurations suggérées : {n} appareils avec déclencheurs pré-câblés trouvés", "gs_adopt_chip": "{n} capteurs de problème peuvent devenir des tâches de maintenance", - "gs_fleet_chip": "Un clic configure le parc de piles" + "gs_fleet_chip": "Un clic configure le parc de piles", + "cal_editor_window": "Fenêtre par défaut", + "cal_editor_window_week": "Semaine (7 jours)", + "cal_editor_window_fortnight": "Quinzaine (14 jours)", + "cal_editor_window_month": "Mois (30 jours, défaut)", + "cal_editor_window_year": "Année (365 jours, jours vides masqués)", + "cal_editor_show_chips": "Afficher les puces de fenêtre dans la carte", + "cal_editor_chips_hint": "Masquez les puces lorsque la carte est intégrée dans une vue stratégie qui sert déjà de sélecteur de fenêtre.", + "cal_editor_show_user_filter": "Afficher le filtre utilisateur", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hi.json b/custom_components/maintenance_supporter/frontend-src/locales/hi.json index 8de72e44..42fd2b6b 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hi.json @@ -129,6 +129,8 @@ "notes_optional": "टिप्पणियाँ (वैकल्पिक)", "cost_optional": "लागत (वैकल्पिक)", "duration_minutes": "मिनटों में अवधि (वैकल्पिक)", + "completed_at_optional": "पूर्ण होने का समय (वैकल्पिक, खाली = अभी)", + "completed_at_future_error": "पूर्णता की तारीख भविष्य में नहीं हो सकती।", "days": "दिन", "day": "दिन", "today": "आज", @@ -193,9 +195,14 @@ "use_entity_state": "एंटिटी स्थिति का उपयोग करें (कोई विशेषता नहीं)", "trigger_above": "इससे ऊपर ट्रिगर करें", "trigger_below": "इससे नीचे ट्रिगर करें", + "trigger_equals": "बराबर होने पर ट्रिगर करें (=)", + "trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)", "for_at_least_minutes": "कम से कम (मिनट)", "safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)", "safety_interval": "सुरक्षा अंतराल (वैकल्पिक)", + "trigger_combinator": "ट्रिगर और अंतराल संयोजित करें", + "trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)", + "trigger_combinator_all": "ट्रिगर और अंतराल (दोनों आवश्यक)", "delta_mode": "डेल्टा मोड", "from_state_optional": "किस स्थिति से (वैकल्पिक)", "to_state_optional": "किस स्थिति तक (वैकल्पिक)", @@ -850,5 +857,17 @@ "gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं", "gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले", "gs_adopt_chip": "{n} समस्या सेंसर रखरखाव कार्य बन सकते हैं", - "gs_fleet_chip": "एक क्लिक में बैटरी बेड़ा सेट करें" + "gs_fleet_chip": "एक क्लिक में बैटरी बेड़ा सेट करें", + "cal_editor_window": "डिफ़ॉल्ट विंडो", + "cal_editor_window_week": "सप्ताह (7 दिन)", + "cal_editor_window_fortnight": "पखवाड़ा (14 दिन)", + "cal_editor_window_month": "महीना (30 दिन, डिफ़ॉल्ट)", + "cal_editor_window_year": "वर्ष (365 दिन, खाली दिन छिपे)", + "cal_editor_show_chips": "कार्ड में विंडो चिप्स दिखाएँ", + "cal_editor_chips_hint": "जब कार्ड ऐसी स्ट्रैटेजी व्यू में हो जो पहले से विंडो चयनक है, तो चिप्स छिपाएँ।", + "cal_editor_show_user_filter": "उपयोगकर्ता फ़िल्टर दिखाएँ", + "cal_editor_default_user": "डिफ़ॉल्ट उपयोगकर्ता फ़िल्टर", + "cal_editor_my_tasks": "मेरे कार्य (वर्तमान उपयोगकर्ता)", + "cal_editor_show_object_filter": "ऑब्जेक्ट फ़िल्टर दिखाएँ", + "cal_editor_object_hint": "YAML से एक ऑब्जेक्ट पहले से चुनें: object_filter: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/hu.json b/custom_components/maintenance_supporter/frontend-src/locales/hu.json index 53ecbdaf..849d34a3 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/hu.json @@ -129,6 +129,8 @@ "notes_optional": "Megjegyzések (opcionális)", "cost_optional": "Költség (opcionális)", "duration_minutes": "Időtartam percben (opcionális)", + "completed_at_optional": "Elvégezve ekkor (opcionális, üres = most)", + "completed_at_future_error": "Az elvégzés dátuma nem lehet a jövőben.", "days": "nap", "day": "nap", "today": "Ma", @@ -193,9 +195,14 @@ "use_entity_state": "Entitás állapotának használata (attribútum nélkül)", "trigger_above": "Kiváltás e fölött", "trigger_below": "Kiváltás ez alatt", + "trigger_equals": "Aktiválás ha egyenlő (=)", + "trigger_not_equals": "Aktiválás ha eltér ettől (≠)", "for_at_least_minutes": "Legalább ennyi ideig (perc)", "safety_interval_days": "Biztonsági intervallum (nap, opcionális)", "safety_interval": "Biztonsági intervallum (opcionális)", + "trigger_combinator": "Trigger és időköz kombinálása", + "trigger_combinator_any": "Trigger vagy időköz (amelyik előbb)", + "trigger_combinator_all": "Trigger és időköz (mindkettő szükséges)", "delta_mode": "Delta mód", "from_state_optional": "Kezdő állapot (opcionális)", "to_state_optional": "Célállapot (opcionális)", @@ -850,5 +857,17 @@ "gs_label": "Első lépések — a tippek eltűnnek, ahogy a beállítás bővül", "gs_setups_chip": "Javasolt beállítások: {n} eszköz előre bekötött triggerekkel", "gs_adopt_chip": "{n} problémaérzékelő karbantartási feladattá válhat", - "gs_fleet_chip": "Egy kattintás beállítja az elemflottát" + "gs_fleet_chip": "Egy kattintás beállítja az elemflottát", + "cal_editor_window": "Alapértelmezett időablak", + "cal_editor_window_week": "Hét (7 nap)", + "cal_editor_window_fortnight": "Két hét (14 nap)", + "cal_editor_window_month": "Hónap (30 nap, alapértelmezett)", + "cal_editor_window_year": "Év (365 nap, üres napok elrejtve)", + "cal_editor_show_chips": "Időablak-választók megjelenítése a kártyán", + "cal_editor_chips_hint": "Rejtsd el a választókat, ha a kártya olyan stratégianézetben van, amely már időablak-választóként szolgál.", + "cal_editor_show_user_filter": "Felhasználószűrő megjelenítése", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/it.json b/custom_components/maintenance_supporter/frontend-src/locales/it.json index 3fd26b70..ca4f7473 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/it.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/it.json @@ -128,6 +128,8 @@ "notes_optional": "Note (opzionale)", "cost_optional": "Costo (opzionale)", "duration_minutes": "Durata in minuti (opzionale)", + "completed_at_optional": "Completato il (opzionale, vuoto = adesso)", + "completed_at_future_error": "La data di completamento non può essere nel futuro.", "days": "giorni", "day": "giorno", "today": "Oggi", @@ -192,9 +194,14 @@ "use_entity_state": "Usa stato dell'entità (nessun attributo)", "trigger_above": "Attivare sopra", "trigger_below": "Attivare sotto", + "trigger_equals": "Attiva quando uguale a (=)", + "trigger_not_equals": "Attiva quando diverso da (≠)", "for_at_least_minutes": "Per almeno (minuti)", "safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)", "safety_interval": "Intervallo di sicurezza (opzionale)", + "trigger_combinator": "Combina trigger e intervallo", + "trigger_combinator_any": "Trigger o intervallo (il primo)", + "trigger_combinator_all": "Trigger e intervallo (entrambi richiesti)", "delta_mode": "Modalità delta", "from_state_optional": "Dallo stato (opzionale)", "to_state_optional": "Allo stato (opzionale)", @@ -850,5 +857,17 @@ "gs_label": "Primi passi — questi suggerimenti scompaiono man mano che la configurazione cresce", "gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati", "gs_adopt_chip": "{n} sensori di problemi possono diventare attività di manutenzione", - "gs_fleet_chip": "Un clic configura la flotta di batterie" + "gs_fleet_chip": "Un clic configura la flotta di batterie", + "cal_editor_window": "Finestra predefinita", + "cal_editor_window_week": "Settimana (7 giorni)", + "cal_editor_window_fortnight": "Due settimane (14 giorni)", + "cal_editor_window_month": "Mese (30 giorni, predefinito)", + "cal_editor_window_year": "Anno (365 giorni, giorni vuoti nascosti)", + "cal_editor_show_chips": "Mostra i chip della finestra nella scheda", + "cal_editor_chips_hint": "Nascondi i chip quando la scheda è in una vista strategia che funge già da selettore di finestra.", + "cal_editor_show_user_filter": "Mostra il filtro utente", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ja.json b/custom_components/maintenance_supporter/frontend-src/locales/ja.json index d80403b8..529bf2ab 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ja.json @@ -129,6 +129,8 @@ "notes_optional": "メモ(任意)", "cost_optional": "費用(任意)", "duration_minutes": "所要時間(分、任意)", + "completed_at_optional": "完了日時(任意・空欄 = 現在)", + "completed_at_future_error": "完了日時に未来は指定できません。", "days": "日", "day": "日", "today": "今日", @@ -193,9 +195,14 @@ "use_entity_state": "エンティティの状態を使用(属性なし)", "trigger_above": "この値を超えたらトリガー", "trigger_below": "この値を下回ったらトリガー", + "trigger_equals": "値が一致したらトリガー(=)", + "trigger_not_equals": "値が異なればトリガー(≠)", "for_at_least_minutes": "最低継続時間(分)", "safety_interval_days": "安全間隔(日、任意)", "safety_interval": "安全間隔(任意)", + "trigger_combinator": "トリガーと間隔の組み合わせ", + "trigger_combinator_any": "トリガーまたは間隔(先に満たした方)", + "trigger_combinator_all": "トリガーと間隔(両方必須)", "delta_mode": "差分モード", "from_state_optional": "変化前の状態(任意)", "to_state_optional": "変化後の状態(任意)", @@ -850,5 +857,17 @@ "gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます", "gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出", "gs_adopt_chip": "{n}個の問題センサーをメンテナンスタスクにできます", - "gs_fleet_chip": "ワンクリックで電池フリートを設定" + "gs_fleet_chip": "ワンクリックで電池フリートを設定", + "cal_editor_window": "既定の期間", + "cal_editor_window_week": "1週間(7日)", + "cal_editor_window_fortnight": "2週間(14日)", + "cal_editor_window_month": "1か月(30日・既定)", + "cal_editor_window_year": "1年(365日・空の日は省略)", + "cal_editor_show_chips": "カード内に期間チップを表示", + "cal_editor_chips_hint": "ストラテジービューが期間選択を担う場合はチップを非表示にします。", + "cal_editor_show_user_filter": "ユーザーフィルターを表示", + "cal_editor_default_user": "既定のユーザーフィルター", + "cal_editor_my_tasks": "自分のタスク(現在のユーザー)", + "cal_editor_show_object_filter": "オブジェクトフィルターを表示", + "cal_editor_object_hint": "YAML でオブジェクトを事前選択:object_filter: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。" } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ko.json b/custom_components/maintenance_supporter/frontend-src/locales/ko.json index c53c60fc..8ae4396d 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ko.json @@ -129,6 +129,8 @@ "notes_optional": "메모 (선택)", "cost_optional": "비용 (선택)", "duration_minutes": "소요 시간(분, 선택)", + "completed_at_optional": "완료 시각 (선택, 비우면 지금)", + "completed_at_future_error": "완료 날짜는 미래일 수 없습니다.", "days": "일", "day": "일", "today": "오늘", @@ -193,9 +195,14 @@ "use_entity_state": "엔티티 상태 사용 (속성 없음)", "trigger_above": "초과 시 트리거", "trigger_below": "미만 시 트리거", + "trigger_equals": "값이 같으면 트리거 (=)", + "trigger_not_equals": "값이 다르면 트리거 (≠)", "for_at_least_minutes": "최소 지속 시간 (분)", "safety_interval_days": "안전 주기 (일, 선택)", "safety_interval": "안전 주기 (선택)", + "trigger_combinator": "트리거와 간격 결합", + "trigger_combinator_any": "트리거 또는 간격 (먼저 충족)", + "trigger_combinator_all": "트리거와 간격 (둘 다 필요)", "delta_mode": "델타 모드", "from_state_optional": "변경 전 상태 (선택)", "to_state_optional": "변경 후 상태 (선택)", @@ -850,5 +857,17 @@ "gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다", "gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견", "gs_adopt_chip": "문제 센서 {n}개를 유지보수 작업으로 만들 수 있습니다", - "gs_fleet_chip": "클릭 한 번으로 배터리 플릿 설정" + "gs_fleet_chip": "클릭 한 번으로 배터리 플릿 설정", + "cal_editor_window": "기본 기간", + "cal_editor_window_week": "1주 (7일)", + "cal_editor_window_fortnight": "2주 (14일)", + "cal_editor_window_month": "1개월 (30일, 기본)", + "cal_editor_window_year": "1년 (365일, 빈 날은 접힘)", + "cal_editor_show_chips": "카드 안에 기간 칩 표시", + "cal_editor_chips_hint": "전략 뷰가 이미 기간 선택기 역할을 하면 칩을 숨기세요.", + "cal_editor_show_user_filter": "사용자 필터 표시", + "cal_editor_default_user": "기본 사용자 필터", + "cal_editor_my_tasks": "내 작업 (현재 사용자)", + "cal_editor_show_object_filter": "객체 필터 표시", + "cal_editor_object_hint": "YAML로 객체를 미리 선택: object_filter: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nb.json b/custom_components/maintenance_supporter/frontend-src/locales/nb.json index 0fa109d9..ff7f5ad2 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nb.json @@ -129,6 +129,8 @@ "notes_optional": "Notater (valgfritt)", "cost_optional": "Kostnad (valgfritt)", "duration_minutes": "Varighet i minutter (valgfritt)", + "completed_at_optional": "Utført den (valgfritt, tomt = nå)", + "completed_at_future_error": "Fullføringsdatoen kan ikke ligge i fremtiden.", "days": "dager", "day": "dag", "today": "I dag", @@ -193,9 +195,14 @@ "use_entity_state": "Bruk entitetstilstand (ingen attributt)", "trigger_above": "Utløs over", "trigger_below": "Utløs under", + "trigger_equals": "Utløs ved lik (=)", + "trigger_not_equals": "Utløs ved forskjellig fra (≠)", "for_at_least_minutes": "I minst (minutter)", "safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)", "safety_interval": "Sikkerhetsintervall (valgfritt)", + "trigger_combinator": "Kombiner utløser og intervall", + "trigger_combinator_any": "Utløser eller intervall (først oppfylt)", + "trigger_combinator_all": "Utløser og intervall (begge kreves)", "delta_mode": "Deltamodus", "from_state_optional": "Fra tilstand (valgfritt)", "to_state_optional": "Til tilstand (valgfritt)", @@ -850,5 +857,17 @@ "gs_label": "Kom i gang — tipsene forsvinner etter hvert som oppsettet vokser", "gs_setups_chip": "Foreslåtte oppsett fant {n} enheter med ferdigkoblede utløsere", "gs_adopt_chip": "{n} problemsensorer kan bli vedlikeholdsoppgaver", - "gs_fleet_chip": "Ett klikk setter opp batteriflåten" + "gs_fleet_chip": "Ett klikk setter opp batteriflåten", + "cal_editor_window": "Standardvindu", + "cal_editor_window_week": "Uke (7 dager)", + "cal_editor_window_fortnight": "To uker (14 dager)", + "cal_editor_window_month": "Måned (30 dager, standard)", + "cal_editor_window_year": "År (365 dager, tomme dager skjult)", + "cal_editor_show_chips": "Vis vindus-chips i kortet", + "cal_editor_chips_hint": "Skjul chipsene når kortet er innebygd i en strategivisning som allerede fungerer som vindusvelger.", + "cal_editor_show_user_filter": "Vis brukerfilter", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/nl.json b/custom_components/maintenance_supporter/frontend-src/locales/nl.json index 305a34a4..a1f8adf7 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/nl.json @@ -128,6 +128,8 @@ "notes_optional": "Notities (optioneel)", "cost_optional": "Kosten (optioneel)", "duration_minutes": "Duur in minuten (optioneel)", + "completed_at_optional": "Voltooid op (optioneel, leeg = nu)", + "completed_at_future_error": "De voltooiingsdatum mag niet in de toekomst liggen.", "days": "dagen", "day": "dag", "today": "Vandaag", @@ -192,9 +194,14 @@ "use_entity_state": "Entiteitsstatus gebruiken (geen attribuut)", "trigger_above": "Activeren als boven", "trigger_below": "Activeren als onder", + "trigger_equals": "Activeren bij gelijk aan (=)", + "trigger_not_equals": "Activeren bij afwijkend van (≠)", "for_at_least_minutes": "Voor minstens (minuten)", "safety_interval_days": "Veiligheidsinterval (dagen, optioneel)", "safety_interval": "Veiligheidsinterval (optioneel)", + "trigger_combinator": "Trigger en interval combineren", + "trigger_combinator_any": "Trigger of interval (eerst vervuld)", + "trigger_combinator_all": "Trigger en interval (beide vereist)", "delta_mode": "Deltamodus", "from_state_optional": "Van status (optioneel)", "to_state_optional": "Naar status (optioneel)", @@ -850,5 +857,17 @@ "gs_label": "Aan de slag — deze tips verdwijnen naarmate je installatie groeit", "gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden", "gs_adopt_chip": "{n} probleemsensoren kunnen onderhoudstaken worden", - "gs_fleet_chip": "Eén klik stelt het batterijpark in" + "gs_fleet_chip": "Eén klik stelt het batterijpark in", + "cal_editor_window": "Standaardvenster", + "cal_editor_window_week": "Week (7 dagen)", + "cal_editor_window_fortnight": "Twee weken (14 dagen)", + "cal_editor_window_month": "Maand (30 dagen, standaard)", + "cal_editor_window_year": "Jaar (365 dagen, lege dagen verborgen)", + "cal_editor_show_chips": "Vensterchips in de kaart tonen", + "cal_editor_chips_hint": "Verberg de chips wanneer de kaart in een strategieweergave staat die al als vensterkeuze dient.", + "cal_editor_show_user_filter": "Gebruikersfilter tonen", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pl.json b/custom_components/maintenance_supporter/frontend-src/locales/pl.json index 9c2c10db..6793764e 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pl.json @@ -128,6 +128,8 @@ "notes_optional": "Notatki (opcjonalne)", "cost_optional": "Koszt (opcjonalne)", "duration_minutes": "Czas trwania w minutach (opcjonalne)", + "completed_at_optional": "Wykonano dnia (opcjonalne, puste = teraz)", + "completed_at_future_error": "Data wykonania nie może być w przyszłości.", "days": "dni", "day": "dzień", "today": "Dzisiaj", @@ -192,9 +194,14 @@ "use_entity_state": "Użyj stanu encji (bez atrybutu)", "trigger_above": "Wyzwól powyżej", "trigger_below": "Wyzwól poniżej", + "trigger_equals": "Wyzwól przy równym (=)", + "trigger_not_equals": "Wyzwól przy różnym od (≠)", "for_at_least_minutes": "Przez co najmniej (minuty)", "safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)", "safety_interval": "Interwał bezpieczeństwa (opcjonalny)", + "trigger_combinator": "Połącz wyzwalacz i interwał", + "trigger_combinator_any": "Wyzwalacz lub interwał (co pierwsze)", + "trigger_combinator_all": "Wyzwalacz i interwał (oba wymagane)", "delta_mode": "Tryb delta", "from_state_optional": "Ze stanu (opcjonalne)", "to_state_optional": "Do stanu (opcjonalne)", @@ -850,5 +857,17 @@ "gs_label": "Pierwsze kroki — te wskazówki znikają wraz z rozwojem konfiguracji", "gs_setups_chip": "Sugerowane konfiguracje: znaleziono {n} urządzeń z gotowymi wyzwalaczami", "gs_adopt_chip": "{n} czujników problemów może stać się zadaniami konserwacji", - "gs_fleet_chip": "Jedno kliknięcie konfiguruje flotę baterii" + "gs_fleet_chip": "Jedno kliknięcie konfiguruje flotę baterii", + "cal_editor_window": "Okno domyślne", + "cal_editor_window_week": "Tydzień (7 dni)", + "cal_editor_window_fortnight": "Dwa tygodnie (14 dni)", + "cal_editor_window_month": "Miesiąc (30 dni, domyślnie)", + "cal_editor_window_year": "Rok (365 dni, puste dni ukryte)", + "cal_editor_show_chips": "Pokaż przełączniki okna na karcie", + "cal_editor_chips_hint": "Ukryj przełączniki, gdy karta jest osadzona w widoku strategii, który już pełni rolę wyboru okna.", + "cal_editor_show_user_filter": "Pokaż filtr użytkownika", + "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." } 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 33010069..9c6acdb4 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt-br.json @@ -129,6 +129,8 @@ "notes_optional": "Observações (opcional)", "cost_optional": "Custo (opcional)", "duration_minutes": "Duração em minutos (opcional)", + "completed_at_optional": "Concluído em (opcional, vazio = agora)", + "completed_at_future_error": "A data de conclusão não pode estar no futuro.", "days": "dias", "day": "dia", "today": "Hoje", @@ -193,9 +195,14 @@ "use_entity_state": "Usar o estado da entidade (sem atributo)", "trigger_above": "Acionar acima de", "trigger_below": "Acionar abaixo de", + "trigger_equals": "Acionar quando igual a (=)", + "trigger_not_equals": "Acionar quando diferente de (≠)", "for_at_least_minutes": "Por pelo menos (minutos)", "safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval": "Intervalo de segurança (opcional)", + "trigger_combinator": "Combinar gatilho e intervalo", + "trigger_combinator_any": "Gatilho ou intervalo (o primeiro)", + "trigger_combinator_all": "Gatilho e intervalo (ambos exigidos)", "delta_mode": "Modo delta", "from_state_optional": "Do estado (opcional)", "to_state_optional": "Para o estado (opcional)", @@ -850,5 +857,17 @@ "gs_label": "Primeiros passos — estas dicas somem conforme a configuração cresce", "gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados", "gs_adopt_chip": "{n} sensores de problema podem virar tarefas de manutenção", - "gs_fleet_chip": "Um clique configura a frota de pilhas" + "gs_fleet_chip": "Um clique configura a frota de pilhas", + "cal_editor_window": "Janela padrão", + "cal_editor_window_week": "Semana (7 dias)", + "cal_editor_window_fortnight": "Quinzena (14 dias)", + "cal_editor_window_month": "Mês (30 dias, padrão)", + "cal_editor_window_year": "Ano (365 dias, dias vazios ocultos)", + "cal_editor_show_chips": "Mostrar chips de janela no cartão", + "cal_editor_chips_hint": "Oculte os chips quando o cartão estiver em uma visão de estratégia que já serve como seletor de janela.", + "cal_editor_show_user_filter": "Mostrar filtro de usuário", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/pt.json b/custom_components/maintenance_supporter/frontend-src/locales/pt.json index 0ce44bca..61014803 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/pt.json @@ -128,6 +128,8 @@ "notes_optional": "Notas (opcional)", "cost_optional": "Custo (opcional)", "duration_minutes": "Duração em minutos (opcional)", + "completed_at_optional": "Concluído em (opcional, vazio = agora)", + "completed_at_future_error": "A data de conclusão não pode estar no futuro.", "days": "dias", "day": "dia", "today": "Hoje", @@ -192,9 +194,14 @@ "use_entity_state": "Usar estado da entidade (sem atributo)", "trigger_above": "Acionar acima de", "trigger_below": "Acionar abaixo de", + "trigger_equals": "Acionar quando igual a (=)", + "trigger_not_equals": "Acionar quando diferente de (≠)", "for_at_least_minutes": "Durante pelo menos (minutos)", "safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval": "Intervalo de segurança (opcional)", + "trigger_combinator": "Combinar acionador e intervalo", + "trigger_combinator_any": "Acionador ou intervalo (o primeiro)", + "trigger_combinator_all": "Acionador e intervalo (ambos exigidos)", "delta_mode": "Modo delta", "from_state_optional": "Do estado (opcional)", "to_state_optional": "Para o estado (opcional)", @@ -850,5 +857,17 @@ "gs_label": "Primeiros passos — estas dicas desaparecem à medida que a configuração cresce", "gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados", "gs_adopt_chip": "{n} sensores de problema podem tornar-se tarefas de manutenção", - "gs_fleet_chip": "Um clique configura a frota de pilhas" + "gs_fleet_chip": "Um clique configura a frota de pilhas", + "cal_editor_window": "Janela predefinida", + "cal_editor_window_week": "Semana (7 dias)", + "cal_editor_window_fortnight": "Quinzena (14 dias)", + "cal_editor_window_month": "Mês (30 dias, predefinido)", + "cal_editor_window_year": "Ano (365 dias, dias vazios ocultos)", + "cal_editor_show_chips": "Mostrar chips de janela no cartão", + "cal_editor_chips_hint": "Oculte os chips quando o cartão está numa vista de estratégia que já serve de seletor de janela.", + "cal_editor_show_user_filter": "Mostrar filtro de utilizador", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/ru.json b/custom_components/maintenance_supporter/frontend-src/locales/ru.json index b9e78ea2..ad702546 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/ru.json @@ -128,6 +128,8 @@ "notes_optional": "Примечания (опционально)", "cost_optional": "Стоимость (опционально)", "duration_minutes": "Длительность в минутах (опционально)", + "completed_at_optional": "Выполнено (необязательно, пусто = сейчас)", + "completed_at_future_error": "Дата выполнения не может быть в будущем.", "days": "дней", "day": "день", "today": "Сегодня", @@ -192,9 +194,14 @@ "use_entity_state": "Использовать состояние сущности (без атрибута)", "trigger_above": "Срабатывать выше", "trigger_below": "Срабатывать ниже", + "trigger_equals": "Срабатывать при равенстве (=)", + "trigger_not_equals": "Срабатывать при отличии от (≠)", "for_at_least_minutes": "Не менее (минут)", "safety_interval_days": "Интервал безопасности (дни, опционально)", "safety_interval": "Интервал безопасности (опционально)", + "trigger_combinator": "Совместить триггер и интервал", + "trigger_combinator_any": "Триггер или интервал (что раньше)", + "trigger_combinator_all": "Триггер и интервал (оба условия)", "delta_mode": "Режим дельты", "from_state_optional": "Из состояния (опционально)", "to_state_optional": "В состояние (опционально)", @@ -850,5 +857,17 @@ "gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки", "gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами", "gs_adopt_chip": "{n} датчиков проблем могут стать задачами обслуживания", - "gs_fleet_chip": "Один клик настроит парк батарей" + "gs_fleet_chip": "Один клик настроит парк батарей", + "cal_editor_window": "Окно по умолчанию", + "cal_editor_window_week": "Неделя (7 дней)", + "cal_editor_window_fortnight": "Две недели (14 дней)", + "cal_editor_window_month": "Месяц (30 дней, по умолчанию)", + "cal_editor_window_year": "Год (365 дней, пустые дни скрыты)", + "cal_editor_show_chips": "Показывать чипы окна в карточке", + "cal_editor_chips_hint": "Скройте чипы, если карточка встроена в стратегию, которая уже служит выбором окна.", + "cal_editor_show_user_filter": "Показывать фильтр пользователя", + "cal_editor_default_user": "Фильтр пользователя по умолчанию", + "cal_editor_my_tasks": "Мои задачи (текущий пользователь)", + "cal_editor_show_object_filter": "Показывать фильтр объекта", + "cal_editor_object_hint": "Предварительный выбор объекта через YAML: object_filter: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/sv.json b/custom_components/maintenance_supporter/frontend-src/locales/sv.json index 33b3a71e..482530f2 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/sv.json @@ -128,6 +128,8 @@ "notes_optional": "Anteckningar (valfritt)", "cost_optional": "Kostnad (valfritt)", "duration_minutes": "Varaktighet i minuter (valfritt)", + "completed_at_optional": "Utförd den (valfritt, tomt = nu)", + "completed_at_future_error": "Slutförandedatumet får inte ligga i framtiden.", "days": "dagar", "day": "dag", "today": "Idag", @@ -192,9 +194,14 @@ "use_entity_state": "Använd entitetstillstånd (inget attribut)", "trigger_above": "Utlös över", "trigger_below": "Utlös under", + "trigger_equals": "Utlös vid lika med (=)", + "trigger_not_equals": "Utlös vid skilt från (≠)", "for_at_least_minutes": "Under minst (minuter)", "safety_interval_days": "Säkerhetsintervall (dagar, valfritt)", "safety_interval": "Säkerhetsintervall (valfritt)", + "trigger_combinator": "Kombinera utlösare och intervall", + "trigger_combinator_any": "Utlösare eller intervall (först uppfylld)", + "trigger_combinator_all": "Utlösare och intervall (båda krävs)", "delta_mode": "Delta-läge", "from_state_optional": "Från tillstånd (valfritt)", "to_state_optional": "Till tillstånd (valfritt)", @@ -850,5 +857,17 @@ "gs_label": "Kom igång — tipsen försvinner när din installation växer", "gs_setups_chip": "Föreslagna uppsättningar hittade {n} enheter med förkopplade utlösare", "gs_adopt_chip": "{n} problemsensorer kan bli underhållsuppgifter", - "gs_fleet_chip": "Ett klick konfigurerar batteriflottan" + "gs_fleet_chip": "Ett klick konfigurerar batteriflottan", + "cal_editor_window": "Standardfönster", + "cal_editor_window_week": "Vecka (7 dagar)", + "cal_editor_window_fortnight": "Två veckor (14 dagar)", + "cal_editor_window_month": "Månad (30 dagar, standard)", + "cal_editor_window_year": "År (365 dagar, tomma dagar dolda)", + "cal_editor_show_chips": "Visa fönsterchips i kortet", + "cal_editor_chips_hint": "Dölj chipsen när kortet är inbäddat i en strategivy som redan fungerar som fönsterväljare.", + "cal_editor_show_user_filter": "Visa användarfilter", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/tr.json b/custom_components/maintenance_supporter/frontend-src/locales/tr.json index 6a89ce27..f2a7daee 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/tr.json @@ -129,6 +129,8 @@ "notes_optional": "Notlar (isteğe bağlı)", "cost_optional": "Maliyet (isteğe bağlı)", "duration_minutes": "Dakika cinsinden süre (isteğe bağlı)", + "completed_at_optional": "Tamamlanma zamanı (isteğe bağlı, boş = şimdi)", + "completed_at_future_error": "Tamamlanma tarihi gelecekte olamaz.", "days": "gün", "day": "gün", "today": "Bugün", @@ -193,9 +195,14 @@ "use_entity_state": "Varlık durumunu kullan (öznitelik yok)", "trigger_above": "Üstünde tetikle", "trigger_below": "Altında tetikle", + "trigger_equals": "Şuna eşitse tetikle (=)", + "trigger_not_equals": "Şundan farklıysa tetikle (≠)", "for_at_least_minutes": "En az (dakika)", "safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)", "safety_interval": "Güvenlik aralığı (isteğe bağlı)", + "trigger_combinator": "Tetikleyici ve aralığı birleştir", + "trigger_combinator_any": "Tetikleyici veya aralık (ilk gerçekleşen)", + "trigger_combinator_all": "Tetikleyici ve aralık (her ikisi gerekli)", "delta_mode": "Fark modu", "from_state_optional": "Başlangıç durumu (isteğe bağlı)", "to_state_optional": "Hedef durum (isteğe bağlı)", @@ -850,5 +857,17 @@ "gs_label": "Başlarken — kurulumunuz büyüdükçe bu ipuçları kaybolur", "gs_setups_chip": "Önerilen kurulumlar {n} cihaz buldu (hazır tetikleyicilerle)", "gs_adopt_chip": "{n} sorun sensörü bakım görevine dönüşebilir", - "gs_fleet_chip": "Tek tıkla pil filosunu kurun" + "gs_fleet_chip": "Tek tıkla pil filosunu kurun", + "cal_editor_window": "Varsayılan pencere", + "cal_editor_window_week": "Hafta (7 gün)", + "cal_editor_window_fortnight": "İki hafta (14 gün)", + "cal_editor_window_month": "Ay (30 gün, varsayılan)", + "cal_editor_window_year": "Yıl (365 gün, boş günler gizli)", + "cal_editor_show_chips": "Pencere seçeneklerini kartta göster", + "cal_editor_chips_hint": "Kart, zaten pencere seçici görevi gören bir strateji görünümüne gömülüyse seçenekleri gizleyin.", + "cal_editor_show_user_filter": "Kullanıcı filtresini göster", + "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." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/uk.json b/custom_components/maintenance_supporter/frontend-src/locales/uk.json index 26a6f96b..7a56c0e6 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/uk.json @@ -128,6 +128,8 @@ "notes_optional": "Примітки (необов'язково)", "cost_optional": "Вартість (необов'язково)", "duration_minutes": "Тривалість у хвилинах (необов'язково)", + "completed_at_optional": "Виконано (необов'язково, порожньо = зараз)", + "completed_at_future_error": "Дата виконання не може бути в майбутньому.", "days": "днів", "day": "день", "today": "Сьогодні", @@ -192,9 +194,14 @@ "use_entity_state": "Використовувати стан об'єкта (без атрибута)", "trigger_above": "Спрацювати, коли вище", "trigger_below": "Спрацювати, коли нижче", + "trigger_equals": "Спрацьовувати при рівності (=)", + "trigger_not_equals": "Спрацьовувати при відмінності від (≠)", "for_at_least_minutes": "Протягом не менше (хвилин)", "safety_interval_days": "Страховий інтервал (дні, необов'язково)", "safety_interval": "Страховий інтервал (необов'язково)", + "trigger_combinator": "Поєднати тригер та інтервал", + "trigger_combinator_any": "Тригер або інтервал (що раніше)", + "trigger_combinator_all": "Тригер та інтервал (обидва потрібні)", "delta_mode": "Режим дельти", "from_state_optional": "З стану (необов'язково)", "to_state_optional": "До стану (необов'язково)", @@ -850,5 +857,17 @@ "gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування", "gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами", "gs_adopt_chip": "{n} датчиків проблем можуть стати завданнями обслуговування", - "gs_fleet_chip": "Один клік налаштує парк батарей" + "gs_fleet_chip": "Один клік налаштує парк батарей", + "cal_editor_window": "Вікно за замовчуванням", + "cal_editor_window_week": "Тиждень (7 днів)", + "cal_editor_window_fortnight": "Два тижні (14 днів)", + "cal_editor_window_month": "Місяць (30 днів, типово)", + "cal_editor_window_year": "Рік (365 днів, порожні дні приховано)", + "cal_editor_show_chips": "Показувати чипи вікна в картці", + "cal_editor_chips_hint": "Приховайте чипи, якщо картка вбудована в стратегію, що вже слугує вибором вікна.", + "cal_editor_show_user_filter": "Показувати фільтр користувача", + "cal_editor_default_user": "Типовий фільтр користувача", + "cal_editor_my_tasks": "Мої завдання (поточний користувач)", + "cal_editor_show_object_filter": "Показувати фільтр об'єкта", + "cal_editor_object_hint": "Попередній вибір об'єкта через YAML: object_filter: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами." } diff --git a/custom_components/maintenance_supporter/frontend-src/locales/zh.json b/custom_components/maintenance_supporter/frontend-src/locales/zh.json index 885de6c7..63e45581 100644 --- a/custom_components/maintenance_supporter/frontend-src/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend-src/locales/zh.json @@ -129,6 +129,8 @@ "notes_optional": "备注 (可选)", "cost_optional": "成本 (可选)", "duration_minutes": "耗时 (分钟, 可选)", + "completed_at_optional": "完成时间(可选,留空 = 现在)", + "completed_at_future_error": "完成日期不能是未来时间。", "days": "天", "day": "天", "today": "今天", @@ -193,9 +195,14 @@ "use_entity_state": "使用实体状态 (不使用属性)", "trigger_above": "高于此值触发", "trigger_below": "低于此值触发", + "trigger_equals": "等于时触发(=)", + "trigger_not_equals": "不等于时触发(≠)", "for_at_least_minutes": "持续至少 (分钟)", "safety_interval_days": "安全间隔 (天, 可选)", "safety_interval": "安全间隔 (可选)", + "trigger_combinator": "组合触发器与间隔", + "trigger_combinator_any": "触发器或间隔(先到者)", + "trigger_combinator_all": "触发器与间隔(两者皆需)", "delta_mode": "增量模式", "from_state_optional": "起始状态 (可选)", "to_state_optional": "目标状态 (可选)", @@ -850,5 +857,17 @@ "gs_label": "入门提示——随着配置的完善,这些提示会自动消失", "gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)", "gs_adopt_chip": "{n} 个问题传感器可转换为维护任务", - "gs_fleet_chip": "一键设置电池车队" + "gs_fleet_chip": "一键设置电池车队", + "cal_editor_window": "默认时间窗", + "cal_editor_window_week": "一周(7天)", + "cal_editor_window_fortnight": "两周(14天)", + "cal_editor_window_month": "一个月(30天,默认)", + "cal_editor_window_year": "一年(365天,空白日折叠)", + "cal_editor_show_chips": "在卡片内显示时间窗标签", + "cal_editor_chips_hint": "当卡片嵌入已充当时间窗选择器的策略视图时,请隐藏标签。", + "cal_editor_show_user_filter": "显示用户筛选", + "cal_editor_default_user": "默认用户筛选", + "cal_editor_my_tasks": "我的任务(当前用户)", + "cal_editor_show_object_filter": "显示对象筛选", + "cal_editor_object_hint": "通过 YAML 预选对象:object_filter: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。" } diff --git a/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts b/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts index 80453e2b..fbda0acf 100644 --- a/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts +++ b/custom_components/maintenance_supporter/frontend-src/maintenance-calendar-card.ts @@ -5,11 +5,12 @@ * (clock vs trending-up), prediction-confidence pill, projected recurrences * at 55% opacity, today-pill highlight, empty-day collapsing in the year view. * - * Click on an event fires an ``ll-custom`` event with payload - * ``{type: "maintenance-supporter:open-task", entry_id, task_id}``. The - * dashboard-strategy bundle's document-level handler picks that up and - * either opens the task dialog in-place (preferred) or deep-links into the - * panel as a fallback. + * Click on an event opens the task quick-actions dialog (future events) or + * the history-edit dialog (past events) DIRECTLY via the shared dialog-mount + * — so clicks work on any dashboard, with or without the strategy bundle. + * When the dialog helper cannot mount, an ``ll-custom`` event + * (``{type: "maintenance-supporter:open-task", entry_id, task_id}``) is + * dispatched as the fallback for the strategy bundle's document listener. * * Card config: * @@ -36,6 +37,7 @@ import { import { calendarStyles } from "./calendar-styles"; import { sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles"; import { registerCustomCard } from "./helpers/register-card"; +import { openHistoryEditDialog, openTaskQuickActions } from "./dialog-mount"; import type { HomeAssistant, MaintenanceObjectResponse, @@ -200,23 +202,17 @@ export class MaintenanceCalendarCard extends LitElement { private _onEventClick(ev: CalendarEvent): void { // Past events carry a history_timestamp — those open the history-edit - // dialog instead of the task editor. Future / next_due events open - // the task editor as usual. + // dialog instead of the task editor. Future / next_due events open the + // task quick-actions. Dialogs open DIRECTLY via the shared dialog-mount + // (same fix the task card got): the ll-custom event this used to + // dispatch is only handled by the strategy bundle's document listener, + // so on a plain dashboard the clicks silently did nothing. ll-custom + // stays as the fallback when the dialog helper cannot mount. if (ev.history_timestamp) { - this.dispatchEvent( - new CustomEvent("ll-custom", { - detail: { - type: "maintenance-supporter:edit-history", - entry_id: ev.entry_id, - task_id: ev.task_id, - original_timestamp: ev.history_timestamp, - }, - bubbles: true, - composed: true, - }), - ); + void this._openHistoryEntry(ev); return; } + if (openTaskQuickActions(ev.entry_id, ev.task_id)) return; this.dispatchEvent( new CustomEvent("ll-custom", { detail: { @@ -230,6 +226,48 @@ export class MaintenanceCalendarCard extends LitElement { ); } + /** Fetch the recorded entry and open the history-edit dialog directly + * (mirrors the strategy shim's ll-custom "edit-history" path). */ + private async _openHistoryEntry(ev: CalendarEvent): Promise { + try { + const resp = await this.hass.connection.sendMessagePromise<{ + tasks?: Array<{ id: string; history?: Array> }>; + }>({ type: "maintenance_supporter/object", entry_id: ev.entry_id }); + const entry = resp.tasks + ?.find((tk) => tk.id === ev.task_id) + ?.history?.find((h) => h.timestamp === ev.history_timestamp); + if (!entry) return; + const opened = openHistoryEditDialog({ + entry_id: ev.entry_id, + task_id: ev.task_id, + original_timestamp: ev.history_timestamp!, + type: (entry.type as string) || "completed", + timestamp: (entry.timestamp as string) || ev.history_timestamp!, + notes: (entry.notes as string | null) ?? null, + cost: (entry.cost as number | null) ?? null, + duration: (entry.duration as number | null) ?? null, + completed_by: (entry.completed_by as string | null) ?? null, + used_parts: + (entry.used_parts as Array<{ part_id: string; name?: string; quantity: number; entry_id?: string }> | null) ?? null, + }); + if (opened) return; + } catch { + /* fall through to the ll-custom fallback below */ + } + this.dispatchEvent( + new CustomEvent("ll-custom", { + detail: { + type: "maintenance-supporter:edit-history", + entry_id: ev.entry_id, + task_id: ev.task_id, + original_timestamp: ev.history_timestamp, + }, + bubbles: true, + composed: true, + }), + ); + } + render() { if (!this.hass) return nothing; @@ -456,11 +494,11 @@ export class MaintenanceCalendarCard extends LitElement { // and user-filter on/off. Same pattern as MaintenanceSupporterCardEditor — // LitElement with setConfig + dispatched config-changed. -const WINDOW_DAY_OPTIONS: Array<{ value: WindowDays; label: string }> = [ - { value: 7, label: "Week (7 days)" }, - { value: 14, label: "Fortnight (14 days)" }, - { value: 30, label: "Month (30 days, default)" }, - { value: 365, label: "Year (365 days, empty days collapsed)" }, +const WINDOW_DAY_KEYS: Array<{ value: WindowDays; key: string }> = [ + { value: 7, key: "cal_editor_window_week" }, + { value: 14, key: "cal_editor_window_fortnight" }, + { value: 30, key: "cal_editor_window_month" }, + { value: 365, key: "cal_editor_window_year" }, ]; class MaintenanceCalendarCardEditor extends LitElement { @@ -469,10 +507,21 @@ class MaintenanceCalendarCardEditor extends LitElement { type: "custom:maintenance-supporter-calendar-card", }; + private get _lang(): string { + return langOf(this.hass); + } + setConfig(config: CalendarCardConfig): void { this._config = { ...config }; } + /** The editor renders before the locale JSON is fetched — re-render once + * it lands so the labels localize (same pattern as the card itself). */ + updated(): void { + const lang = this._lang; + if (lang && !isLocaleLoaded(lang)) void ensureLocale(lang).then(() => this.requestUpdate()); + } + private _valueChanged(key: keyof CalendarCardConfig, value: unknown): void { const newConfig = { ...this._config, [key]: value } as CalendarCardConfig; // Drop default-equivalent values so saved YAML stays minimal @@ -502,6 +551,7 @@ class MaintenanceCalendarCardEditor extends LitElement { } render() { + const L = this._lang; const currentWindow = this._config.window_days ?? 30; const showChips = this._config.show_window_chips !== false; const showUserFilter = this._config.show_user_filter !== false; @@ -510,7 +560,7 @@ class MaintenanceCalendarCardEditor extends LitElement { return html`
- +
- +
- +
-
- Hide the chips when the card is embedded in a strategy view that - already serves as the window selector. -
+
${t("cal_editor_chips_hint", L)}
- +
- +
- +
-
- Pre-select one object via YAML: object_filter: "<object name>" — or a - list of names to restrict the card to several objects. -
+
${t("cal_editor_object_hint", L)}
`; } diff --git a/custom_components/maintenance_supporter/frontend-src/renderers/progress.ts b/custom_components/maintenance_supporter/frontend-src/renderers/progress.ts index 1efc4c78..0feddb2e 100644 --- a/custom_components/maintenance_supporter/frontend-src/renderers/progress.ts +++ b/custom_components/maintenance_supporter/frontend-src/renderers/progress.ts @@ -50,6 +50,12 @@ export function renderTriggerProgress(row: TaskRow | MaintenanceTask) { const range = high - below || 1; pct = Math.min(100, Math.max(0, ((high - val) / range) * 100)); label = `${val.toFixed(1)} / ${below} ${unit}`; + } else if (tc.trigger_equals != null || tc.trigger_not_equals != null) { + // Discrete = / ≠ levels have no meaningful gradient — binary bar, + // same treatment as compound. + const target = tc.trigger_equals != null ? `= ${tc.trigger_equals}` : `≠ ${tc.trigger_not_equals}`; + label = `${val.toFixed(1)} (${target}${unit ? ` ${unit}` : ""})`; + pct = row.trigger_active ? 100 : 0; } else { return nothing; } diff --git a/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts b/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts index 3db4724a..44e3422d 100644 --- a/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts +++ b/custom_components/maintenance_supporter/frontend-src/renderers/sparkline.ts @@ -98,6 +98,8 @@ export function renderTriggerSection(task: MaintenanceTask, ctx: SparklineContex ${triggerType === "threshold" ? html` ${tc.trigger_above != null ? html` ${t("threshold_above", L)}: ${tc.trigger_above} ${unit}` : nothing} ${tc.trigger_below != null ? html` ${t("threshold_below", L)}: ${tc.trigger_below} ${unit}` : nothing} + ${tc.trigger_equals != null ? html` = ${tc.trigger_equals} ${unit}` : nothing} + ${tc.trigger_not_equals != null ? html` ≠ ${tc.trigger_not_equals} ${unit}` : nothing} ${tc.trigger_for_minutes ? html` ${t("for_minutes", L)}: ${tc.trigger_for_minutes}` : nothing} ` : nothing} ${triggerType === "state_change" ? html` diff --git a/custom_components/maintenance_supporter/frontend-src/types.ts b/custom_components/maintenance_supporter/frontend-src/types.ts index 78ccc9e7..514e5d1d 100644 --- a/custom_components/maintenance_supporter/frontend-src/types.ts +++ b/custom_components/maintenance_supporter/frontend-src/types.ts @@ -56,6 +56,10 @@ export interface TriggerConfig { type?: string; // "threshold" | "counter" | "state_change" | "runtime" trigger_above?: number | null; trigger_below?: number | null; + trigger_equals?: number | null; + trigger_not_equals?: number | null; + /** "any" (default) = trigger or safety interval, whichever first; "all" = both required. */ + trigger_combinator?: string; trigger_for_minutes?: number; trigger_target_value?: number; trigger_delta_mode?: boolean; diff --git a/custom_components/maintenance_supporter/frontend-src/ws-errors.ts b/custom_components/maintenance_supporter/frontend-src/ws-errors.ts index faefd445..d72e9d18 100644 --- a/custom_components/maintenance_supporter/frontend-src/ws-errors.ts +++ b/custom_components/maintenance_supporter/frontend-src/ws-errors.ts @@ -48,6 +48,8 @@ const FIELD_LABEL_KEYS: Record = { 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", }; diff --git a/custom_components/maintenance_supporter/frontend/locales/cs.json b/custom_components/maintenance_supporter/frontend/locales/cs.json index 9c2038a0..4d03390a 100644 --- a/custom_components/maintenance_supporter/frontend/locales/cs.json +++ b/custom_components/maintenance_supporter/frontend/locales/cs.json @@ -128,6 +128,8 @@ "notes_optional": "Poznámky (volitelné)", "cost_optional": "Náklady (volitelné)", "duration_minutes": "Doba trvání v minutách (volitelné)", + "completed_at_optional": "Dokončeno dne (volitelné, prázdné = nyní)", + "completed_at_future_error": "Datum dokončení nesmí být v budoucnosti.", "days": "dní", "day": "den", "today": "Dnes", @@ -192,9 +194,14 @@ "use_entity_state": "Použít stav entity (bez atributu)", "trigger_above": "Spustit nad", "trigger_below": "Spustit pod", + "trigger_equals": "Spustit při rovnosti (=)", + "trigger_not_equals": "Spustit při odlišnosti od (≠)", "for_at_least_minutes": "Po dobu alespoň (minut)", "safety_interval_days": "Bezpečnostní interval (dny, volitelný)", "safety_interval": "Bezpečnostní interval (volitelný)", + "trigger_combinator": "Kombinovat spouštěč a interval", + "trigger_combinator_any": "Spouštěč nebo interval (co dřív)", + "trigger_combinator_all": "Spouštěč a interval (obojí vyžadováno)", "delta_mode": "Režim delta", "from_state_optional": "Ze stavu (volitelné)", "to_state_optional": "Do stavu (volitelné)", @@ -850,5 +857,17 @@ "gs_label": "Začínáme — tyto tipy zmizí, jak vaše nastavení poroste", "gs_setups_chip": "Navrhovaná nastavení: nalezeno {n} zařízení s předpřipravenými spouštěči", "gs_adopt_chip": "{n} problémových senzorů se může stát údržbovými úkoly", - "gs_fleet_chip": "Jedno kliknutí nastaví flotilu baterií" + "gs_fleet_chip": "Jedno kliknutí nastaví flotilu baterií", + "cal_editor_window": "Výchozí okno", + "cal_editor_window_week": "Týden (7 dní)", + "cal_editor_window_fortnight": "Dva týdny (14 dní)", + "cal_editor_window_month": "Měsíc (30 dní, výchozí)", + "cal_editor_window_year": "Rok (365 dní, prázdné dny skryty)", + "cal_editor_show_chips": "Zobrazit přepínače okna v kartě", + "cal_editor_chips_hint": "Skryjte přepínače, pokud je karta ve strategickém pohledu, který už slouží jako výběr okna.", + "cal_editor_show_user_filter": "Zobrazit filtr uživatele", + "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ů." } diff --git a/custom_components/maintenance_supporter/frontend/locales/da.json b/custom_components/maintenance_supporter/frontend/locales/da.json index 9449d4f7..3fb9d80c 100644 --- a/custom_components/maintenance_supporter/frontend/locales/da.json +++ b/custom_components/maintenance_supporter/frontend/locales/da.json @@ -129,6 +129,8 @@ "notes_optional": "Noter (valgfrit)", "cost_optional": "Omkostning (valgfrit)", "duration_minutes": "Varighed i minutter (valgfrit)", + "completed_at_optional": "Udført den (valgfrit, tomt = nu)", + "completed_at_future_error": "Udførelsesdatoen må ikke ligge i fremtiden.", "days": "dage", "day": "dag", "today": "I dag", @@ -193,9 +195,14 @@ "use_entity_state": "Brug enhedstilstand (ingen attribut)", "trigger_above": "Udløs over", "trigger_below": "Udløs under", + "trigger_equals": "Udløs ved lig med (=)", + "trigger_not_equals": "Udløs ved forskellig fra (≠)", "for_at_least_minutes": "I mindst (minutter)", "safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)", "safety_interval": "Sikkerhedsinterval (valgfrit)", + "trigger_combinator": "Kombinér trigger og interval", + "trigger_combinator_any": "Trigger eller interval (først opfyldt)", + "trigger_combinator_all": "Trigger og interval (begge kræves)", "delta_mode": "Delta-tilstand", "from_state_optional": "Fra tilstand (valgfrit)", "to_state_optional": "Til tilstand (valgfrit)", @@ -850,5 +857,17 @@ "gs_label": "Kom godt i gang — disse tips forsvinder, efterhånden som opsætningen vokser", "gs_setups_chip": "Foreslåede opsætninger fandt {n} enheder med forudindstillede udløsere", "gs_adopt_chip": "{n} problemsensorer kan blive vedligeholdelsesopgaver", - "gs_fleet_chip": "Ét klik opsætter batteriflåden" + "gs_fleet_chip": "Ét klik opsætter batteriflåden", + "cal_editor_window": "Standardvindue", + "cal_editor_window_week": "Uge (7 dage)", + "cal_editor_window_fortnight": "To uger (14 dage)", + "cal_editor_window_month": "Måned (30 dage, standard)", + "cal_editor_window_year": "År (365 dage, tomme dage skjult)", + "cal_editor_show_chips": "Vis vinduechips i kortet", + "cal_editor_chips_hint": "Skjul chips, når kortet er indlejret i en strategivisning, der allerede fungerer som vinduesvælger.", + "cal_editor_show_user_filter": "Vis brugerfilter", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/de.json b/custom_components/maintenance_supporter/frontend/locales/de.json index 900e84a9..21e95890 100644 --- a/custom_components/maintenance_supporter/frontend/locales/de.json +++ b/custom_components/maintenance_supporter/frontend/locales/de.json @@ -129,6 +129,8 @@ "notes_optional": "Notizen (optional)", "cost_optional": "Kosten (optional)", "duration_minutes": "Dauer in Minuten (optional)", + "completed_at_optional": "Erledigt am (optional, leer = jetzt)", + "completed_at_future_error": "Das Erledigungsdatum darf nicht in der Zukunft liegen.", "days": "Tage", "day": "Tag", "today": "Heute", @@ -193,9 +195,14 @@ "use_entity_state": "Entitäts-Zustand verwenden (kein Attribut)", "trigger_above": "Auslösen wenn über", "trigger_below": "Auslösen wenn unter", + "trigger_equals": "Auslösen bei genau (=)", + "trigger_not_equals": "Auslösen bei abweichend von (≠)", "for_at_least_minutes": "Für mindestens (Minuten)", "safety_interval_days": "Sicherheitsintervall (Tage, optional)", "safety_interval": "Sicherheitsintervall (optional)", + "trigger_combinator": "Trigger und Intervall kombinieren", + "trigger_combinator_any": "Trigger oder Intervall (zuerst erfüllt)", + "trigger_combinator_all": "Trigger und Intervall (beides erforderlich)", "delta_mode": "Delta-Modus", "from_state_optional": "Von Zustand (optional)", "to_state_optional": "Zu Zustand (optional)", @@ -850,5 +857,17 @@ "gs_label": "Erste Schritte — diese Hinweise verschwinden, wenn dein Setup wächst", "gs_setups_chip": "Vorgeschlagene Setups: {n} Geräte mit vorverdrahteten Auslösern gefunden", "gs_adopt_chip": "{n} Problem-Sensoren können zu Wartungsaufgaben werden", - "gs_fleet_chip": "Ein Klick richtet die Batterieflotte ein" + "gs_fleet_chip": "Ein Klick richtet die Batterieflotte ein", + "cal_editor_window": "Standard-Zeitfenster", + "cal_editor_window_week": "Woche (7 Tage)", + "cal_editor_window_fortnight": "Zwei Wochen (14 Tage)", + "cal_editor_window_month": "Monat (30 Tage, Standard)", + "cal_editor_window_year": "Jahr (365 Tage, leere Tage ausgeblendet)", + "cal_editor_show_chips": "Zeitfenster-Chips in der Karte anzeigen", + "cal_editor_chips_hint": "Chips ausblenden, wenn die Karte in einer Strategie-Ansicht steckt, die bereits als Zeitfenster-Auswahl dient.", + "cal_editor_show_user_filter": "Benutzerfilter-Dropdown anzeigen", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/en.json b/custom_components/maintenance_supporter/frontend/locales/en.json index 37f9159e..364c6ff5 100644 --- a/custom_components/maintenance_supporter/frontend/locales/en.json +++ b/custom_components/maintenance_supporter/frontend/locales/en.json @@ -129,6 +129,8 @@ "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", @@ -193,9 +195,14 @@ "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 (≠)", "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)", @@ -850,5 +857,17 @@ "gs_label": "Getting started — 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" + "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: \"\" — or a list of names to restrict the card to several objects." } diff --git a/custom_components/maintenance_supporter/frontend/locales/es.json b/custom_components/maintenance_supporter/frontend/locales/es.json index 7701e67e..e45af7c8 100644 --- a/custom_components/maintenance_supporter/frontend/locales/es.json +++ b/custom_components/maintenance_supporter/frontend/locales/es.json @@ -128,6 +128,8 @@ "notes_optional": "Notas (opcional)", "cost_optional": "Coste (opcional)", "duration_minutes": "Duración en minutos (opcional)", + "completed_at_optional": "Completado el (opcional, vacío = ahora)", + "completed_at_future_error": "La fecha de finalización no puede estar en el futuro.", "days": "días", "day": "día", "today": "Hoy", @@ -192,9 +194,14 @@ "use_entity_state": "Usar estado de la entidad (sin atributo)", "trigger_above": "Activar por encima de", "trigger_below": "Activar por debajo de", + "trigger_equals": "Activar cuando sea igual a (=)", + "trigger_not_equals": "Activar cuando sea distinto de (≠)", "for_at_least_minutes": "Durante al menos (minutos)", "safety_interval_days": "Intervalo de seguridad (días, opcional)", "safety_interval": "Intervalo de seguridad (opcional)", + "trigger_combinator": "Combinar disparador e intervalo", + "trigger_combinator_any": "Disparador o intervalo (el primero)", + "trigger_combinator_all": "Disparador e intervalo (ambos requeridos)", "delta_mode": "Modo delta", "from_state_optional": "Desde estado (opcional)", "to_state_optional": "Hasta estado (opcional)", @@ -850,5 +857,17 @@ "gs_label": "Primeros pasos: estas sugerencias desaparecen a medida que crece tu configuración", "gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados", "gs_adopt_chip": "{n} sensores de problemas pueden convertirse en tareas de mantenimiento", - "gs_fleet_chip": "Un clic configura la flota de baterías" + "gs_fleet_chip": "Un clic configura la flota de baterías", + "cal_editor_window": "Ventana predeterminada", + "cal_editor_window_week": "Semana (7 días)", + "cal_editor_window_fortnight": "Quincena (14 días)", + "cal_editor_window_month": "Mes (30 días, predeterminado)", + "cal_editor_window_year": "Año (365 días, días vacíos ocultos)", + "cal_editor_show_chips": "Mostrar chips de ventana en la tarjeta", + "cal_editor_chips_hint": "Oculta los chips cuando la tarjeta está en una vista de estrategia que ya sirve como selector de ventana.", + "cal_editor_show_user_filter": "Mostrar filtro de usuario", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/fi.json b/custom_components/maintenance_supporter/frontend/locales/fi.json index 19ca1a84..55270f58 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fi.json +++ b/custom_components/maintenance_supporter/frontend/locales/fi.json @@ -129,6 +129,8 @@ "notes_optional": "Muistiinpanot (valinnainen)", "cost_optional": "Kustannus (valinnainen)", "duration_minutes": "Kesto minuutteina (valinnainen)", + "completed_at_optional": "Suoritettu (valinnainen, tyhjä = nyt)", + "completed_at_future_error": "Suorituspäivä ei voi olla tulevaisuudessa.", "days": "päivää", "day": "päivä", "today": "Tänään", @@ -193,9 +195,14 @@ "use_entity_state": "Käytä entiteetin tilaa (ei attribuuttia)", "trigger_above": "Laukaise yli", "trigger_below": "Laukaise alle", + "trigger_equals": "Laukaise kun yhtä suuri kuin (=)", + "trigger_not_equals": "Laukaise kun eri kuin (≠)", "for_at_least_minutes": "Vähintään (minuuttia)", "safety_interval_days": "Turvaväli (päivää, valinnainen)", "safety_interval": "Turvaväli (valinnainen)", + "trigger_combinator": "Yhdistä laukaisin ja väli", + "trigger_combinator_any": "Laukaisin tai väli (ensin täyttyvä)", + "trigger_combinator_all": "Laukaisin ja väli (molemmat vaaditaan)", "delta_mode": "Delta-tila", "from_state_optional": "Lähtötilasta (valinnainen)", "to_state_optional": "Kohdetilaan (valinnainen)", @@ -850,5 +857,17 @@ "gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa", "gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla", "gs_adopt_chip": "{n} ongelma-anturia voi muuttua huoltotehtäviksi", - "gs_fleet_chip": "Yksi napsautus määrittää akkukannan" + "gs_fleet_chip": "Yksi napsautus määrittää akkukannan", + "cal_editor_window": "Oletusikkuna", + "cal_editor_window_week": "Viikko (7 päivää)", + "cal_editor_window_fortnight": "Kaksi viikkoa (14 päivää)", + "cal_editor_window_month": "Kuukausi (30 päivää, oletus)", + "cal_editor_window_year": "Vuosi (365 päivää, tyhjät päivät piilotettu)", + "cal_editor_show_chips": "Näytä ikkunavalinnat kortissa", + "cal_editor_chips_hint": "Piilota valinnat, kun kortti on strategianäkymässä, joka jo toimii ikkunan valitsimena.", + "cal_editor_show_user_filter": "Näytä käyttäjäsuodatin", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/fr.json b/custom_components/maintenance_supporter/frontend/locales/fr.json index 5dba5716..584df1dc 100644 --- a/custom_components/maintenance_supporter/frontend/locales/fr.json +++ b/custom_components/maintenance_supporter/frontend/locales/fr.json @@ -128,6 +128,8 @@ "notes_optional": "Notes (optionnel)", "cost_optional": "Coût (optionnel)", "duration_minutes": "Durée en minutes (optionnel)", + "completed_at_optional": "Effectué le (optionnel, vide = maintenant)", + "completed_at_future_error": "La date d'achèvement ne peut pas être dans le futur.", "days": "jours", "day": "jour", "today": "Aujourd'hui", @@ -192,9 +194,14 @@ "use_entity_state": "Utiliser l'état de l'entité (pas d'attribut)", "trigger_above": "Déclencher au-dessus de", "trigger_below": "Déclencher en dessous de", + "trigger_equals": "Déclencher si égal à (=)", + "trigger_not_equals": "Déclencher si différent de (≠)", "for_at_least_minutes": "Pendant au moins (minutes)", "safety_interval_days": "Intervalle de sécurité (jours, optionnel)", "safety_interval": "Intervalle de sécurité (optionnel)", + "trigger_combinator": "Combiner déclencheur et intervalle", + "trigger_combinator_any": "Déclencheur ou intervalle (premier atteint)", + "trigger_combinator_all": "Déclencheur et intervalle (les deux requis)", "delta_mode": "Mode delta", "from_state_optional": "État source (optionnel)", "to_state_optional": "État cible (optionnel)", @@ -850,5 +857,17 @@ "gs_label": "Premiers pas — ces conseils disparaissent à mesure que votre configuration grandit", "gs_setups_chip": "Configurations suggérées : {n} appareils avec déclencheurs pré-câblés trouvés", "gs_adopt_chip": "{n} capteurs de problème peuvent devenir des tâches de maintenance", - "gs_fleet_chip": "Un clic configure le parc de piles" + "gs_fleet_chip": "Un clic configure le parc de piles", + "cal_editor_window": "Fenêtre par défaut", + "cal_editor_window_week": "Semaine (7 jours)", + "cal_editor_window_fortnight": "Quinzaine (14 jours)", + "cal_editor_window_month": "Mois (30 jours, défaut)", + "cal_editor_window_year": "Année (365 jours, jours vides masqués)", + "cal_editor_show_chips": "Afficher les puces de fenêtre dans la carte", + "cal_editor_chips_hint": "Masquez les puces lorsque la carte est intégrée dans une vue stratégie qui sert déjà de sélecteur de fenêtre.", + "cal_editor_show_user_filter": "Afficher le filtre utilisateur", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/hi.json b/custom_components/maintenance_supporter/frontend/locales/hi.json index 8de72e44..42fd2b6b 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hi.json +++ b/custom_components/maintenance_supporter/frontend/locales/hi.json @@ -129,6 +129,8 @@ "notes_optional": "टिप्पणियाँ (वैकल्पिक)", "cost_optional": "लागत (वैकल्पिक)", "duration_minutes": "मिनटों में अवधि (वैकल्पिक)", + "completed_at_optional": "पूर्ण होने का समय (वैकल्पिक, खाली = अभी)", + "completed_at_future_error": "पूर्णता की तारीख भविष्य में नहीं हो सकती।", "days": "दिन", "day": "दिन", "today": "आज", @@ -193,9 +195,14 @@ "use_entity_state": "एंटिटी स्थिति का उपयोग करें (कोई विशेषता नहीं)", "trigger_above": "इससे ऊपर ट्रिगर करें", "trigger_below": "इससे नीचे ट्रिगर करें", + "trigger_equals": "बराबर होने पर ट्रिगर करें (=)", + "trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)", "for_at_least_minutes": "कम से कम (मिनट)", "safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)", "safety_interval": "सुरक्षा अंतराल (वैकल्पिक)", + "trigger_combinator": "ट्रिगर और अंतराल संयोजित करें", + "trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)", + "trigger_combinator_all": "ट्रिगर और अंतराल (दोनों आवश्यक)", "delta_mode": "डेल्टा मोड", "from_state_optional": "किस स्थिति से (वैकल्पिक)", "to_state_optional": "किस स्थिति तक (वैकल्पिक)", @@ -850,5 +857,17 @@ "gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं", "gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले", "gs_adopt_chip": "{n} समस्या सेंसर रखरखाव कार्य बन सकते हैं", - "gs_fleet_chip": "एक क्लिक में बैटरी बेड़ा सेट करें" + "gs_fleet_chip": "एक क्लिक में बैटरी बेड़ा सेट करें", + "cal_editor_window": "डिफ़ॉल्ट विंडो", + "cal_editor_window_week": "सप्ताह (7 दिन)", + "cal_editor_window_fortnight": "पखवाड़ा (14 दिन)", + "cal_editor_window_month": "महीना (30 दिन, डिफ़ॉल्ट)", + "cal_editor_window_year": "वर्ष (365 दिन, खाली दिन छिपे)", + "cal_editor_show_chips": "कार्ड में विंडो चिप्स दिखाएँ", + "cal_editor_chips_hint": "जब कार्ड ऐसी स्ट्रैटेजी व्यू में हो जो पहले से विंडो चयनक है, तो चिप्स छिपाएँ।", + "cal_editor_show_user_filter": "उपयोगकर्ता फ़िल्टर दिखाएँ", + "cal_editor_default_user": "डिफ़ॉल्ट उपयोगकर्ता फ़िल्टर", + "cal_editor_my_tasks": "मेरे कार्य (वर्तमान उपयोगकर्ता)", + "cal_editor_show_object_filter": "ऑब्जेक्ट फ़िल्टर दिखाएँ", + "cal_editor_object_hint": "YAML से एक ऑब्जेक्ट पहले से चुनें: object_filter: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।" } diff --git a/custom_components/maintenance_supporter/frontend/locales/hu.json b/custom_components/maintenance_supporter/frontend/locales/hu.json index 53ecbdaf..849d34a3 100644 --- a/custom_components/maintenance_supporter/frontend/locales/hu.json +++ b/custom_components/maintenance_supporter/frontend/locales/hu.json @@ -129,6 +129,8 @@ "notes_optional": "Megjegyzések (opcionális)", "cost_optional": "Költség (opcionális)", "duration_minutes": "Időtartam percben (opcionális)", + "completed_at_optional": "Elvégezve ekkor (opcionális, üres = most)", + "completed_at_future_error": "Az elvégzés dátuma nem lehet a jövőben.", "days": "nap", "day": "nap", "today": "Ma", @@ -193,9 +195,14 @@ "use_entity_state": "Entitás állapotának használata (attribútum nélkül)", "trigger_above": "Kiváltás e fölött", "trigger_below": "Kiváltás ez alatt", + "trigger_equals": "Aktiválás ha egyenlő (=)", + "trigger_not_equals": "Aktiválás ha eltér ettől (≠)", "for_at_least_minutes": "Legalább ennyi ideig (perc)", "safety_interval_days": "Biztonsági intervallum (nap, opcionális)", "safety_interval": "Biztonsági intervallum (opcionális)", + "trigger_combinator": "Trigger és időköz kombinálása", + "trigger_combinator_any": "Trigger vagy időköz (amelyik előbb)", + "trigger_combinator_all": "Trigger és időköz (mindkettő szükséges)", "delta_mode": "Delta mód", "from_state_optional": "Kezdő állapot (opcionális)", "to_state_optional": "Célállapot (opcionális)", @@ -850,5 +857,17 @@ "gs_label": "Első lépések — a tippek eltűnnek, ahogy a beállítás bővül", "gs_setups_chip": "Javasolt beállítások: {n} eszköz előre bekötött triggerekkel", "gs_adopt_chip": "{n} problémaérzékelő karbantartási feladattá válhat", - "gs_fleet_chip": "Egy kattintás beállítja az elemflottát" + "gs_fleet_chip": "Egy kattintás beállítja az elemflottát", + "cal_editor_window": "Alapértelmezett időablak", + "cal_editor_window_week": "Hét (7 nap)", + "cal_editor_window_fortnight": "Két hét (14 nap)", + "cal_editor_window_month": "Hónap (30 nap, alapértelmezett)", + "cal_editor_window_year": "Év (365 nap, üres napok elrejtve)", + "cal_editor_show_chips": "Időablak-választók megjelenítése a kártyán", + "cal_editor_chips_hint": "Rejtsd el a választókat, ha a kártya olyan stratégianézetben van, amely már időablak-választóként szolgál.", + "cal_editor_show_user_filter": "Felhasználószűrő megjelenítése", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/it.json b/custom_components/maintenance_supporter/frontend/locales/it.json index 3fd26b70..ca4f7473 100644 --- a/custom_components/maintenance_supporter/frontend/locales/it.json +++ b/custom_components/maintenance_supporter/frontend/locales/it.json @@ -128,6 +128,8 @@ "notes_optional": "Note (opzionale)", "cost_optional": "Costo (opzionale)", "duration_minutes": "Durata in minuti (opzionale)", + "completed_at_optional": "Completato il (opzionale, vuoto = adesso)", + "completed_at_future_error": "La data di completamento non può essere nel futuro.", "days": "giorni", "day": "giorno", "today": "Oggi", @@ -192,9 +194,14 @@ "use_entity_state": "Usa stato dell'entità (nessun attributo)", "trigger_above": "Attivare sopra", "trigger_below": "Attivare sotto", + "trigger_equals": "Attiva quando uguale a (=)", + "trigger_not_equals": "Attiva quando diverso da (≠)", "for_at_least_minutes": "Per almeno (minuti)", "safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)", "safety_interval": "Intervallo di sicurezza (opzionale)", + "trigger_combinator": "Combina trigger e intervallo", + "trigger_combinator_any": "Trigger o intervallo (il primo)", + "trigger_combinator_all": "Trigger e intervallo (entrambi richiesti)", "delta_mode": "Modalità delta", "from_state_optional": "Dallo stato (opzionale)", "to_state_optional": "Allo stato (opzionale)", @@ -850,5 +857,17 @@ "gs_label": "Primi passi — questi suggerimenti scompaiono man mano che la configurazione cresce", "gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati", "gs_adopt_chip": "{n} sensori di problemi possono diventare attività di manutenzione", - "gs_fleet_chip": "Un clic configura la flotta di batterie" + "gs_fleet_chip": "Un clic configura la flotta di batterie", + "cal_editor_window": "Finestra predefinita", + "cal_editor_window_week": "Settimana (7 giorni)", + "cal_editor_window_fortnight": "Due settimane (14 giorni)", + "cal_editor_window_month": "Mese (30 giorni, predefinito)", + "cal_editor_window_year": "Anno (365 giorni, giorni vuoti nascosti)", + "cal_editor_show_chips": "Mostra i chip della finestra nella scheda", + "cal_editor_chips_hint": "Nascondi i chip quando la scheda è in una vista strategia che funge già da selettore di finestra.", + "cal_editor_show_user_filter": "Mostra il filtro utente", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/ja.json b/custom_components/maintenance_supporter/frontend/locales/ja.json index d80403b8..529bf2ab 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ja.json +++ b/custom_components/maintenance_supporter/frontend/locales/ja.json @@ -129,6 +129,8 @@ "notes_optional": "メモ(任意)", "cost_optional": "費用(任意)", "duration_minutes": "所要時間(分、任意)", + "completed_at_optional": "完了日時(任意・空欄 = 現在)", + "completed_at_future_error": "完了日時に未来は指定できません。", "days": "日", "day": "日", "today": "今日", @@ -193,9 +195,14 @@ "use_entity_state": "エンティティの状態を使用(属性なし)", "trigger_above": "この値を超えたらトリガー", "trigger_below": "この値を下回ったらトリガー", + "trigger_equals": "値が一致したらトリガー(=)", + "trigger_not_equals": "値が異なればトリガー(≠)", "for_at_least_minutes": "最低継続時間(分)", "safety_interval_days": "安全間隔(日、任意)", "safety_interval": "安全間隔(任意)", + "trigger_combinator": "トリガーと間隔の組み合わせ", + "trigger_combinator_any": "トリガーまたは間隔(先に満たした方)", + "trigger_combinator_all": "トリガーと間隔(両方必須)", "delta_mode": "差分モード", "from_state_optional": "変化前の状態(任意)", "to_state_optional": "変化後の状態(任意)", @@ -850,5 +857,17 @@ "gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます", "gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出", "gs_adopt_chip": "{n}個の問題センサーをメンテナンスタスクにできます", - "gs_fleet_chip": "ワンクリックで電池フリートを設定" + "gs_fleet_chip": "ワンクリックで電池フリートを設定", + "cal_editor_window": "既定の期間", + "cal_editor_window_week": "1週間(7日)", + "cal_editor_window_fortnight": "2週間(14日)", + "cal_editor_window_month": "1か月(30日・既定)", + "cal_editor_window_year": "1年(365日・空の日は省略)", + "cal_editor_show_chips": "カード内に期間チップを表示", + "cal_editor_chips_hint": "ストラテジービューが期間選択を担う場合はチップを非表示にします。", + "cal_editor_show_user_filter": "ユーザーフィルターを表示", + "cal_editor_default_user": "既定のユーザーフィルター", + "cal_editor_my_tasks": "自分のタスク(現在のユーザー)", + "cal_editor_show_object_filter": "オブジェクトフィルターを表示", + "cal_editor_object_hint": "YAML でオブジェクトを事前選択:object_filter: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。" } diff --git a/custom_components/maintenance_supporter/frontend/locales/ko.json b/custom_components/maintenance_supporter/frontend/locales/ko.json index c53c60fc..8ae4396d 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ko.json +++ b/custom_components/maintenance_supporter/frontend/locales/ko.json @@ -129,6 +129,8 @@ "notes_optional": "메모 (선택)", "cost_optional": "비용 (선택)", "duration_minutes": "소요 시간(분, 선택)", + "completed_at_optional": "완료 시각 (선택, 비우면 지금)", + "completed_at_future_error": "완료 날짜는 미래일 수 없습니다.", "days": "일", "day": "일", "today": "오늘", @@ -193,9 +195,14 @@ "use_entity_state": "엔티티 상태 사용 (속성 없음)", "trigger_above": "초과 시 트리거", "trigger_below": "미만 시 트리거", + "trigger_equals": "값이 같으면 트리거 (=)", + "trigger_not_equals": "값이 다르면 트리거 (≠)", "for_at_least_minutes": "최소 지속 시간 (분)", "safety_interval_days": "안전 주기 (일, 선택)", "safety_interval": "안전 주기 (선택)", + "trigger_combinator": "트리거와 간격 결합", + "trigger_combinator_any": "트리거 또는 간격 (먼저 충족)", + "trigger_combinator_all": "트리거와 간격 (둘 다 필요)", "delta_mode": "델타 모드", "from_state_optional": "변경 전 상태 (선택)", "to_state_optional": "변경 후 상태 (선택)", @@ -850,5 +857,17 @@ "gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다", "gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견", "gs_adopt_chip": "문제 센서 {n}개를 유지보수 작업으로 만들 수 있습니다", - "gs_fleet_chip": "클릭 한 번으로 배터리 플릿 설정" + "gs_fleet_chip": "클릭 한 번으로 배터리 플릿 설정", + "cal_editor_window": "기본 기간", + "cal_editor_window_week": "1주 (7일)", + "cal_editor_window_fortnight": "2주 (14일)", + "cal_editor_window_month": "1개월 (30일, 기본)", + "cal_editor_window_year": "1년 (365일, 빈 날은 접힘)", + "cal_editor_show_chips": "카드 안에 기간 칩 표시", + "cal_editor_chips_hint": "전략 뷰가 이미 기간 선택기 역할을 하면 칩을 숨기세요.", + "cal_editor_show_user_filter": "사용자 필터 표시", + "cal_editor_default_user": "기본 사용자 필터", + "cal_editor_my_tasks": "내 작업 (현재 사용자)", + "cal_editor_show_object_filter": "객체 필터 표시", + "cal_editor_object_hint": "YAML로 객체를 미리 선택: object_filter: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다." } diff --git a/custom_components/maintenance_supporter/frontend/locales/nb.json b/custom_components/maintenance_supporter/frontend/locales/nb.json index 0fa109d9..ff7f5ad2 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nb.json +++ b/custom_components/maintenance_supporter/frontend/locales/nb.json @@ -129,6 +129,8 @@ "notes_optional": "Notater (valgfritt)", "cost_optional": "Kostnad (valgfritt)", "duration_minutes": "Varighet i minutter (valgfritt)", + "completed_at_optional": "Utført den (valgfritt, tomt = nå)", + "completed_at_future_error": "Fullføringsdatoen kan ikke ligge i fremtiden.", "days": "dager", "day": "dag", "today": "I dag", @@ -193,9 +195,14 @@ "use_entity_state": "Bruk entitetstilstand (ingen attributt)", "trigger_above": "Utløs over", "trigger_below": "Utløs under", + "trigger_equals": "Utløs ved lik (=)", + "trigger_not_equals": "Utløs ved forskjellig fra (≠)", "for_at_least_minutes": "I minst (minutter)", "safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)", "safety_interval": "Sikkerhetsintervall (valgfritt)", + "trigger_combinator": "Kombiner utløser og intervall", + "trigger_combinator_any": "Utløser eller intervall (først oppfylt)", + "trigger_combinator_all": "Utløser og intervall (begge kreves)", "delta_mode": "Deltamodus", "from_state_optional": "Fra tilstand (valgfritt)", "to_state_optional": "Til tilstand (valgfritt)", @@ -850,5 +857,17 @@ "gs_label": "Kom i gang — tipsene forsvinner etter hvert som oppsettet vokser", "gs_setups_chip": "Foreslåtte oppsett fant {n} enheter med ferdigkoblede utløsere", "gs_adopt_chip": "{n} problemsensorer kan bli vedlikeholdsoppgaver", - "gs_fleet_chip": "Ett klikk setter opp batteriflåten" + "gs_fleet_chip": "Ett klikk setter opp batteriflåten", + "cal_editor_window": "Standardvindu", + "cal_editor_window_week": "Uke (7 dager)", + "cal_editor_window_fortnight": "To uker (14 dager)", + "cal_editor_window_month": "Måned (30 dager, standard)", + "cal_editor_window_year": "År (365 dager, tomme dager skjult)", + "cal_editor_show_chips": "Vis vindus-chips i kortet", + "cal_editor_chips_hint": "Skjul chipsene når kortet er innebygd i en strategivisning som allerede fungerer som vindusvelger.", + "cal_editor_show_user_filter": "Vis brukerfilter", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/nl.json b/custom_components/maintenance_supporter/frontend/locales/nl.json index 305a34a4..a1f8adf7 100644 --- a/custom_components/maintenance_supporter/frontend/locales/nl.json +++ b/custom_components/maintenance_supporter/frontend/locales/nl.json @@ -128,6 +128,8 @@ "notes_optional": "Notities (optioneel)", "cost_optional": "Kosten (optioneel)", "duration_minutes": "Duur in minuten (optioneel)", + "completed_at_optional": "Voltooid op (optioneel, leeg = nu)", + "completed_at_future_error": "De voltooiingsdatum mag niet in de toekomst liggen.", "days": "dagen", "day": "dag", "today": "Vandaag", @@ -192,9 +194,14 @@ "use_entity_state": "Entiteitsstatus gebruiken (geen attribuut)", "trigger_above": "Activeren als boven", "trigger_below": "Activeren als onder", + "trigger_equals": "Activeren bij gelijk aan (=)", + "trigger_not_equals": "Activeren bij afwijkend van (≠)", "for_at_least_minutes": "Voor minstens (minuten)", "safety_interval_days": "Veiligheidsinterval (dagen, optioneel)", "safety_interval": "Veiligheidsinterval (optioneel)", + "trigger_combinator": "Trigger en interval combineren", + "trigger_combinator_any": "Trigger of interval (eerst vervuld)", + "trigger_combinator_all": "Trigger en interval (beide vereist)", "delta_mode": "Deltamodus", "from_state_optional": "Van status (optioneel)", "to_state_optional": "Naar status (optioneel)", @@ -850,5 +857,17 @@ "gs_label": "Aan de slag — deze tips verdwijnen naarmate je installatie groeit", "gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden", "gs_adopt_chip": "{n} probleemsensoren kunnen onderhoudstaken worden", - "gs_fleet_chip": "Eén klik stelt het batterijpark in" + "gs_fleet_chip": "Eén klik stelt het batterijpark in", + "cal_editor_window": "Standaardvenster", + "cal_editor_window_week": "Week (7 dagen)", + "cal_editor_window_fortnight": "Twee weken (14 dagen)", + "cal_editor_window_month": "Maand (30 dagen, standaard)", + "cal_editor_window_year": "Jaar (365 dagen, lege dagen verborgen)", + "cal_editor_show_chips": "Vensterchips in de kaart tonen", + "cal_editor_chips_hint": "Verberg de chips wanneer de kaart in een strategieweergave staat die al als vensterkeuze dient.", + "cal_editor_show_user_filter": "Gebruikersfilter tonen", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/pl.json b/custom_components/maintenance_supporter/frontend/locales/pl.json index 9c2c10db..6793764e 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pl.json +++ b/custom_components/maintenance_supporter/frontend/locales/pl.json @@ -128,6 +128,8 @@ "notes_optional": "Notatki (opcjonalne)", "cost_optional": "Koszt (opcjonalne)", "duration_minutes": "Czas trwania w minutach (opcjonalne)", + "completed_at_optional": "Wykonano dnia (opcjonalne, puste = teraz)", + "completed_at_future_error": "Data wykonania nie może być w przyszłości.", "days": "dni", "day": "dzień", "today": "Dzisiaj", @@ -192,9 +194,14 @@ "use_entity_state": "Użyj stanu encji (bez atrybutu)", "trigger_above": "Wyzwól powyżej", "trigger_below": "Wyzwól poniżej", + "trigger_equals": "Wyzwól przy równym (=)", + "trigger_not_equals": "Wyzwól przy różnym od (≠)", "for_at_least_minutes": "Przez co najmniej (minuty)", "safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)", "safety_interval": "Interwał bezpieczeństwa (opcjonalny)", + "trigger_combinator": "Połącz wyzwalacz i interwał", + "trigger_combinator_any": "Wyzwalacz lub interwał (co pierwsze)", + "trigger_combinator_all": "Wyzwalacz i interwał (oba wymagane)", "delta_mode": "Tryb delta", "from_state_optional": "Ze stanu (opcjonalne)", "to_state_optional": "Do stanu (opcjonalne)", @@ -850,5 +857,17 @@ "gs_label": "Pierwsze kroki — te wskazówki znikają wraz z rozwojem konfiguracji", "gs_setups_chip": "Sugerowane konfiguracje: znaleziono {n} urządzeń z gotowymi wyzwalaczami", "gs_adopt_chip": "{n} czujników problemów może stać się zadaniami konserwacji", - "gs_fleet_chip": "Jedno kliknięcie konfiguruje flotę baterii" + "gs_fleet_chip": "Jedno kliknięcie konfiguruje flotę baterii", + "cal_editor_window": "Okno domyślne", + "cal_editor_window_week": "Tydzień (7 dni)", + "cal_editor_window_fortnight": "Dwa tygodnie (14 dni)", + "cal_editor_window_month": "Miesiąc (30 dni, domyślnie)", + "cal_editor_window_year": "Rok (365 dni, puste dni ukryte)", + "cal_editor_show_chips": "Pokaż przełączniki okna na karcie", + "cal_editor_chips_hint": "Ukryj przełączniki, gdy karta jest osadzona w widoku strategii, który już pełni rolę wyboru okna.", + "cal_editor_show_user_filter": "Pokaż filtr użytkownika", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/pt-br.json b/custom_components/maintenance_supporter/frontend/locales/pt-br.json index 33010069..9c6acdb4 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt-br.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt-br.json @@ -129,6 +129,8 @@ "notes_optional": "Observações (opcional)", "cost_optional": "Custo (opcional)", "duration_minutes": "Duração em minutos (opcional)", + "completed_at_optional": "Concluído em (opcional, vazio = agora)", + "completed_at_future_error": "A data de conclusão não pode estar no futuro.", "days": "dias", "day": "dia", "today": "Hoje", @@ -193,9 +195,14 @@ "use_entity_state": "Usar o estado da entidade (sem atributo)", "trigger_above": "Acionar acima de", "trigger_below": "Acionar abaixo de", + "trigger_equals": "Acionar quando igual a (=)", + "trigger_not_equals": "Acionar quando diferente de (≠)", "for_at_least_minutes": "Por pelo menos (minutos)", "safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval": "Intervalo de segurança (opcional)", + "trigger_combinator": "Combinar gatilho e intervalo", + "trigger_combinator_any": "Gatilho ou intervalo (o primeiro)", + "trigger_combinator_all": "Gatilho e intervalo (ambos exigidos)", "delta_mode": "Modo delta", "from_state_optional": "Do estado (opcional)", "to_state_optional": "Para o estado (opcional)", @@ -850,5 +857,17 @@ "gs_label": "Primeiros passos — estas dicas somem conforme a configuração cresce", "gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados", "gs_adopt_chip": "{n} sensores de problema podem virar tarefas de manutenção", - "gs_fleet_chip": "Um clique configura a frota de pilhas" + "gs_fleet_chip": "Um clique configura a frota de pilhas", + "cal_editor_window": "Janela padrão", + "cal_editor_window_week": "Semana (7 dias)", + "cal_editor_window_fortnight": "Quinzena (14 dias)", + "cal_editor_window_month": "Mês (30 dias, padrão)", + "cal_editor_window_year": "Ano (365 dias, dias vazios ocultos)", + "cal_editor_show_chips": "Mostrar chips de janela no cartão", + "cal_editor_chips_hint": "Oculte os chips quando o cartão estiver em uma visão de estratégia que já serve como seletor de janela.", + "cal_editor_show_user_filter": "Mostrar filtro de usuário", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/pt.json b/custom_components/maintenance_supporter/frontend/locales/pt.json index 0ce44bca..61014803 100644 --- a/custom_components/maintenance_supporter/frontend/locales/pt.json +++ b/custom_components/maintenance_supporter/frontend/locales/pt.json @@ -128,6 +128,8 @@ "notes_optional": "Notas (opcional)", "cost_optional": "Custo (opcional)", "duration_minutes": "Duração em minutos (opcional)", + "completed_at_optional": "Concluído em (opcional, vazio = agora)", + "completed_at_future_error": "A data de conclusão não pode estar no futuro.", "days": "dias", "day": "dia", "today": "Hoje", @@ -192,9 +194,14 @@ "use_entity_state": "Usar estado da entidade (sem atributo)", "trigger_above": "Acionar acima de", "trigger_below": "Acionar abaixo de", + "trigger_equals": "Acionar quando igual a (=)", + "trigger_not_equals": "Acionar quando diferente de (≠)", "for_at_least_minutes": "Durante pelo menos (minutos)", "safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval": "Intervalo de segurança (opcional)", + "trigger_combinator": "Combinar acionador e intervalo", + "trigger_combinator_any": "Acionador ou intervalo (o primeiro)", + "trigger_combinator_all": "Acionador e intervalo (ambos exigidos)", "delta_mode": "Modo delta", "from_state_optional": "Do estado (opcional)", "to_state_optional": "Para o estado (opcional)", @@ -850,5 +857,17 @@ "gs_label": "Primeiros passos — estas dicas desaparecem à medida que a configuração cresce", "gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados", "gs_adopt_chip": "{n} sensores de problema podem tornar-se tarefas de manutenção", - "gs_fleet_chip": "Um clique configura a frota de pilhas" + "gs_fleet_chip": "Um clique configura a frota de pilhas", + "cal_editor_window": "Janela predefinida", + "cal_editor_window_week": "Semana (7 dias)", + "cal_editor_window_fortnight": "Quinzena (14 dias)", + "cal_editor_window_month": "Mês (30 dias, predefinido)", + "cal_editor_window_year": "Ano (365 dias, dias vazios ocultos)", + "cal_editor_show_chips": "Mostrar chips de janela no cartão", + "cal_editor_chips_hint": "Oculte os chips quando o cartão está numa vista de estratégia que já serve de seletor de janela.", + "cal_editor_show_user_filter": "Mostrar filtro de utilizador", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/ru.json b/custom_components/maintenance_supporter/frontend/locales/ru.json index b9e78ea2..ad702546 100644 --- a/custom_components/maintenance_supporter/frontend/locales/ru.json +++ b/custom_components/maintenance_supporter/frontend/locales/ru.json @@ -128,6 +128,8 @@ "notes_optional": "Примечания (опционально)", "cost_optional": "Стоимость (опционально)", "duration_minutes": "Длительность в минутах (опционально)", + "completed_at_optional": "Выполнено (необязательно, пусто = сейчас)", + "completed_at_future_error": "Дата выполнения не может быть в будущем.", "days": "дней", "day": "день", "today": "Сегодня", @@ -192,9 +194,14 @@ "use_entity_state": "Использовать состояние сущности (без атрибута)", "trigger_above": "Срабатывать выше", "trigger_below": "Срабатывать ниже", + "trigger_equals": "Срабатывать при равенстве (=)", + "trigger_not_equals": "Срабатывать при отличии от (≠)", "for_at_least_minutes": "Не менее (минут)", "safety_interval_days": "Интервал безопасности (дни, опционально)", "safety_interval": "Интервал безопасности (опционально)", + "trigger_combinator": "Совместить триггер и интервал", + "trigger_combinator_any": "Триггер или интервал (что раньше)", + "trigger_combinator_all": "Триггер и интервал (оба условия)", "delta_mode": "Режим дельты", "from_state_optional": "Из состояния (опционально)", "to_state_optional": "В состояние (опционально)", @@ -850,5 +857,17 @@ "gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки", "gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами", "gs_adopt_chip": "{n} датчиков проблем могут стать задачами обслуживания", - "gs_fleet_chip": "Один клик настроит парк батарей" + "gs_fleet_chip": "Один клик настроит парк батарей", + "cal_editor_window": "Окно по умолчанию", + "cal_editor_window_week": "Неделя (7 дней)", + "cal_editor_window_fortnight": "Две недели (14 дней)", + "cal_editor_window_month": "Месяц (30 дней, по умолчанию)", + "cal_editor_window_year": "Год (365 дней, пустые дни скрыты)", + "cal_editor_show_chips": "Показывать чипы окна в карточке", + "cal_editor_chips_hint": "Скройте чипы, если карточка встроена в стратегию, которая уже служит выбором окна.", + "cal_editor_show_user_filter": "Показывать фильтр пользователя", + "cal_editor_default_user": "Фильтр пользователя по умолчанию", + "cal_editor_my_tasks": "Мои задачи (текущий пользователь)", + "cal_editor_show_object_filter": "Показывать фильтр объекта", + "cal_editor_object_hint": "Предварительный выбор объекта через YAML: object_filter: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами." } diff --git a/custom_components/maintenance_supporter/frontend/locales/sv.json b/custom_components/maintenance_supporter/frontend/locales/sv.json index 33b3a71e..482530f2 100644 --- a/custom_components/maintenance_supporter/frontend/locales/sv.json +++ b/custom_components/maintenance_supporter/frontend/locales/sv.json @@ -128,6 +128,8 @@ "notes_optional": "Anteckningar (valfritt)", "cost_optional": "Kostnad (valfritt)", "duration_minutes": "Varaktighet i minuter (valfritt)", + "completed_at_optional": "Utförd den (valfritt, tomt = nu)", + "completed_at_future_error": "Slutförandedatumet får inte ligga i framtiden.", "days": "dagar", "day": "dag", "today": "Idag", @@ -192,9 +194,14 @@ "use_entity_state": "Använd entitetstillstånd (inget attribut)", "trigger_above": "Utlös över", "trigger_below": "Utlös under", + "trigger_equals": "Utlös vid lika med (=)", + "trigger_not_equals": "Utlös vid skilt från (≠)", "for_at_least_minutes": "Under minst (minuter)", "safety_interval_days": "Säkerhetsintervall (dagar, valfritt)", "safety_interval": "Säkerhetsintervall (valfritt)", + "trigger_combinator": "Kombinera utlösare och intervall", + "trigger_combinator_any": "Utlösare eller intervall (först uppfylld)", + "trigger_combinator_all": "Utlösare och intervall (båda krävs)", "delta_mode": "Delta-läge", "from_state_optional": "Från tillstånd (valfritt)", "to_state_optional": "Till tillstånd (valfritt)", @@ -850,5 +857,17 @@ "gs_label": "Kom igång — tipsen försvinner när din installation växer", "gs_setups_chip": "Föreslagna uppsättningar hittade {n} enheter med förkopplade utlösare", "gs_adopt_chip": "{n} problemsensorer kan bli underhållsuppgifter", - "gs_fleet_chip": "Ett klick konfigurerar batteriflottan" + "gs_fleet_chip": "Ett klick konfigurerar batteriflottan", + "cal_editor_window": "Standardfönster", + "cal_editor_window_week": "Vecka (7 dagar)", + "cal_editor_window_fortnight": "Två veckor (14 dagar)", + "cal_editor_window_month": "Månad (30 dagar, standard)", + "cal_editor_window_year": "År (365 dagar, tomma dagar dolda)", + "cal_editor_show_chips": "Visa fönsterchips i kortet", + "cal_editor_chips_hint": "Dölj chipsen när kortet är inbäddat i en strategivy som redan fungerar som fönsterväljare.", + "cal_editor_show_user_filter": "Visa användarfilter", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/tr.json b/custom_components/maintenance_supporter/frontend/locales/tr.json index 6a89ce27..f2a7daee 100644 --- a/custom_components/maintenance_supporter/frontend/locales/tr.json +++ b/custom_components/maintenance_supporter/frontend/locales/tr.json @@ -129,6 +129,8 @@ "notes_optional": "Notlar (isteğe bağlı)", "cost_optional": "Maliyet (isteğe bağlı)", "duration_minutes": "Dakika cinsinden süre (isteğe bağlı)", + "completed_at_optional": "Tamamlanma zamanı (isteğe bağlı, boş = şimdi)", + "completed_at_future_error": "Tamamlanma tarihi gelecekte olamaz.", "days": "gün", "day": "gün", "today": "Bugün", @@ -193,9 +195,14 @@ "use_entity_state": "Varlık durumunu kullan (öznitelik yok)", "trigger_above": "Üstünde tetikle", "trigger_below": "Altında tetikle", + "trigger_equals": "Şuna eşitse tetikle (=)", + "trigger_not_equals": "Şundan farklıysa tetikle (≠)", "for_at_least_minutes": "En az (dakika)", "safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)", "safety_interval": "Güvenlik aralığı (isteğe bağlı)", + "trigger_combinator": "Tetikleyici ve aralığı birleştir", + "trigger_combinator_any": "Tetikleyici veya aralık (ilk gerçekleşen)", + "trigger_combinator_all": "Tetikleyici ve aralık (her ikisi gerekli)", "delta_mode": "Fark modu", "from_state_optional": "Başlangıç durumu (isteğe bağlı)", "to_state_optional": "Hedef durum (isteğe bağlı)", @@ -850,5 +857,17 @@ "gs_label": "Başlarken — kurulumunuz büyüdükçe bu ipuçları kaybolur", "gs_setups_chip": "Önerilen kurulumlar {n} cihaz buldu (hazır tetikleyicilerle)", "gs_adopt_chip": "{n} sorun sensörü bakım görevine dönüşebilir", - "gs_fleet_chip": "Tek tıkla pil filosunu kurun" + "gs_fleet_chip": "Tek tıkla pil filosunu kurun", + "cal_editor_window": "Varsayılan pencere", + "cal_editor_window_week": "Hafta (7 gün)", + "cal_editor_window_fortnight": "İki hafta (14 gün)", + "cal_editor_window_month": "Ay (30 gün, varsayılan)", + "cal_editor_window_year": "Yıl (365 gün, boş günler gizli)", + "cal_editor_show_chips": "Pencere seçeneklerini kartta göster", + "cal_editor_chips_hint": "Kart, zaten pencere seçici görevi gören bir strateji görünümüne gömülüyse seçenekleri gizleyin.", + "cal_editor_show_user_filter": "Kullanıcı filtresini göster", + "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." } diff --git a/custom_components/maintenance_supporter/frontend/locales/uk.json b/custom_components/maintenance_supporter/frontend/locales/uk.json index 26a6f96b..7a56c0e6 100644 --- a/custom_components/maintenance_supporter/frontend/locales/uk.json +++ b/custom_components/maintenance_supporter/frontend/locales/uk.json @@ -128,6 +128,8 @@ "notes_optional": "Примітки (необов'язково)", "cost_optional": "Вартість (необов'язково)", "duration_minutes": "Тривалість у хвилинах (необов'язково)", + "completed_at_optional": "Виконано (необов'язково, порожньо = зараз)", + "completed_at_future_error": "Дата виконання не може бути в майбутньому.", "days": "днів", "day": "день", "today": "Сьогодні", @@ -192,9 +194,14 @@ "use_entity_state": "Використовувати стан об'єкта (без атрибута)", "trigger_above": "Спрацювати, коли вище", "trigger_below": "Спрацювати, коли нижче", + "trigger_equals": "Спрацьовувати при рівності (=)", + "trigger_not_equals": "Спрацьовувати при відмінності від (≠)", "for_at_least_minutes": "Протягом не менше (хвилин)", "safety_interval_days": "Страховий інтервал (дні, необов'язково)", "safety_interval": "Страховий інтервал (необов'язково)", + "trigger_combinator": "Поєднати тригер та інтервал", + "trigger_combinator_any": "Тригер або інтервал (що раніше)", + "trigger_combinator_all": "Тригер та інтервал (обидва потрібні)", "delta_mode": "Режим дельти", "from_state_optional": "З стану (необов'язково)", "to_state_optional": "До стану (необов'язково)", @@ -850,5 +857,17 @@ "gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування", "gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами", "gs_adopt_chip": "{n} датчиків проблем можуть стати завданнями обслуговування", - "gs_fleet_chip": "Один клік налаштує парк батарей" + "gs_fleet_chip": "Один клік налаштує парк батарей", + "cal_editor_window": "Вікно за замовчуванням", + "cal_editor_window_week": "Тиждень (7 днів)", + "cal_editor_window_fortnight": "Два тижні (14 днів)", + "cal_editor_window_month": "Місяць (30 днів, типово)", + "cal_editor_window_year": "Рік (365 днів, порожні дні приховано)", + "cal_editor_show_chips": "Показувати чипи вікна в картці", + "cal_editor_chips_hint": "Приховайте чипи, якщо картка вбудована в стратегію, що вже слугує вибором вікна.", + "cal_editor_show_user_filter": "Показувати фільтр користувача", + "cal_editor_default_user": "Типовий фільтр користувача", + "cal_editor_my_tasks": "Мої завдання (поточний користувач)", + "cal_editor_show_object_filter": "Показувати фільтр об'єкта", + "cal_editor_object_hint": "Попередній вибір об'єкта через YAML: object_filter: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами." } diff --git a/custom_components/maintenance_supporter/frontend/locales/zh.json b/custom_components/maintenance_supporter/frontend/locales/zh.json index 885de6c7..63e45581 100644 --- a/custom_components/maintenance_supporter/frontend/locales/zh.json +++ b/custom_components/maintenance_supporter/frontend/locales/zh.json @@ -129,6 +129,8 @@ "notes_optional": "备注 (可选)", "cost_optional": "成本 (可选)", "duration_minutes": "耗时 (分钟, 可选)", + "completed_at_optional": "完成时间(可选,留空 = 现在)", + "completed_at_future_error": "完成日期不能是未来时间。", "days": "天", "day": "天", "today": "今天", @@ -193,9 +195,14 @@ "use_entity_state": "使用实体状态 (不使用属性)", "trigger_above": "高于此值触发", "trigger_below": "低于此值触发", + "trigger_equals": "等于时触发(=)", + "trigger_not_equals": "不等于时触发(≠)", "for_at_least_minutes": "持续至少 (分钟)", "safety_interval_days": "安全间隔 (天, 可选)", "safety_interval": "安全间隔 (可选)", + "trigger_combinator": "组合触发器与间隔", + "trigger_combinator_any": "触发器或间隔(先到者)", + "trigger_combinator_all": "触发器与间隔(两者皆需)", "delta_mode": "增量模式", "from_state_optional": "起始状态 (可选)", "to_state_optional": "目标状态 (可选)", @@ -850,5 +857,17 @@ "gs_label": "入门提示——随着配置的完善,这些提示会自动消失", "gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)", "gs_adopt_chip": "{n} 个问题传感器可转换为维护任务", - "gs_fleet_chip": "一键设置电池车队" + "gs_fleet_chip": "一键设置电池车队", + "cal_editor_window": "默认时间窗", + "cal_editor_window_week": "一周(7天)", + "cal_editor_window_fortnight": "两周(14天)", + "cal_editor_window_month": "一个月(30天,默认)", + "cal_editor_window_year": "一年(365天,空白日折叠)", + "cal_editor_show_chips": "在卡片内显示时间窗标签", + "cal_editor_chips_hint": "当卡片嵌入已充当时间窗选择器的策略视图时,请隐藏标签。", + "cal_editor_show_user_filter": "显示用户筛选", + "cal_editor_default_user": "默认用户筛选", + "cal_editor_my_tasks": "我的任务(当前用户)", + "cal_editor_show_object_filter": "显示对象筛选", + "cal_editor_object_hint": "通过 YAML 预选对象:object_filter: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。" } diff --git a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js index 63549707..65aa9193 100644 --- a/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js +++ b/custom_components/maintenance_supporter/frontend/maintenance-calendar-card.js @@ -1,206 +1,9 @@ -/*! maintenance_supporter frontend 2.58.0 */ -var it=Object.defineProperty;var lt=Object.getOwnPropertyDescriptor;var x=(a,e,t,o)=>{for(var r=o>1?void 0:o?lt(e,t):e,n=a.length-1,s;n>=0;n--)(s=a[n])&&(r=(o?s(e,t,r):s(r))||r);return o&&r&&it(e,t,r),r};var ee=globalThis,te=ee.ShadowRoot&&(ee.ShadyCSS===void 0||ee.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,ce=Symbol(),ke=new WeakMap,q=class{constructor(e,t,o){if(this._$cssResult$=!0,o!==ce)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=e,this.t=t}get styleSheet(){let e=this.o,t=this.t;if(te&&e===void 0){let o=t!==void 0&&t.length===1;o&&(e=ke.get(t)),e===void 0&&((this.o=e=new CSSStyleSheet).replaceSync(this.cssText),o&&ke.set(t,e))}return e}toString(){return this.cssText}},Se=a=>new q(typeof a=="string"?a:a+"",void 0,ce),k=(a,...e)=>{let t=a.length===1?a[0]:e.reduce((o,r,n)=>o+(s=>{if(s._$cssResult$===!0)return s.cssText;if(typeof s=="number")return s;throw Error("Value passed to 'css' function must be a 'css' function result: "+s+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(r)+a[n+1],a[0]);return new q(t,a,ce)},$e=(a,e)=>{if(te)a.adoptedStyleSheets=e.map(t=>t instanceof CSSStyleSheet?t:t.styleSheet);else for(let t of e){let o=document.createElement("style"),r=ee.litNonce;r!==void 0&&o.setAttribute("nonce",r),o.textContent=t.cssText,a.appendChild(o)}},de=te?a=>a:a=>a instanceof CSSStyleSheet?(e=>{let t="";for(let o of e.cssRules)t+=o.cssText;return Se(t)})(a):a;var{is:ct,defineProperty:dt,getOwnPropertyDescriptor:pt,getOwnPropertyNames:ut,getOwnPropertySymbols:_t,getPrototypeOf:ht}=Object,oe=globalThis,Ae=oe.trustedTypes,gt=Ae?Ae.emptyScript:"",ft=oe.reactiveElementPolyfillSupport,F=(a,e)=>a,B={toAttribute(a,e){switch(e){case Boolean:a=a?gt:null;break;case Object:case Array:a=a==null?a:JSON.stringify(a)}return a},fromAttribute(a,e){let t=a;switch(e){case Boolean:t=a!==null;break;case Number:t=a===null?null:Number(a);break;case Object:case Array:try{t=JSON.parse(a)}catch{t=null}}return t}},re=(a,e)=>!ct(a,e),Ce={attribute:!0,type:String,converter:B,reflect:!1,useDefault:!1,hasChanged:re};Symbol.metadata??=Symbol("metadata"),oe.litPropertyMetadata??=new WeakMap;var S=class extends HTMLElement{static addInitializer(e){this._$Ei(),(this.l??=[]).push(e)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(e,t=Ce){if(t.state&&(t.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(e)&&((t=Object.create(t)).wrapped=!0),this.elementProperties.set(e,t),!t.noAccessor){let o=Symbol(),r=this.getPropertyDescriptor(e,o,t);r!==void 0&&dt(this.prototype,e,r)}}static getPropertyDescriptor(e,t,o){let{get:r,set:n}=pt(this.prototype,e)??{get(){return this[t]},set(s){this[t]=s}};return{get:r,set(s){let l=r?.call(this);n?.call(this,s),this.requestUpdate(e,l,o)},configurable:!0,enumerable:!0}}static getPropertyOptions(e){return this.elementProperties.get(e)??Ce}static _$Ei(){if(this.hasOwnProperty(F("elementProperties")))return;let e=ht(this);e.finalize(),e.l!==void 0&&(this.l=[...e.l]),this.elementProperties=new Map(e.elementProperties)}static finalize(){if(this.hasOwnProperty(F("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(F("properties"))){let t=this.properties,o=[...ut(t),..._t(t)];for(let r of o)this.createProperty(r,t[r])}let e=this[Symbol.metadata];if(e!==null){let t=litPropertyMetadata.get(e);if(t!==void 0)for(let[o,r]of t)this.elementProperties.set(o,r)}this._$Eh=new Map;for(let[t,o]of this.elementProperties){let r=this._$Eu(t,o);r!==void 0&&this._$Eh.set(r,t)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(e){let t=[];if(Array.isArray(e)){let o=new Set(e.flat(1/0).reverse());for(let r of o)t.unshift(de(r))}else e!==void 0&&t.push(de(e));return t}static _$Eu(e,t){let o=t.attribute;return o===!1?void 0:typeof o=="string"?o:typeof e=="string"?e.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(e=>this.enableUpdating=e),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(e=>e(this))}addController(e){(this._$EO??=new Set).add(e),this.renderRoot!==void 0&&this.isConnected&&e.hostConnected?.()}removeController(e){this._$EO?.delete(e)}_$E_(){let e=new Map,t=this.constructor.elementProperties;for(let o of t.keys())this.hasOwnProperty(o)&&(e.set(o,this[o]),delete this[o]);e.size>0&&(this._$Ep=e)}createRenderRoot(){let e=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return $e(e,this.constructor.elementStyles),e}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(!0),this._$EO?.forEach(e=>e.hostConnected?.())}enableUpdating(e){}disconnectedCallback(){this._$EO?.forEach(e=>e.hostDisconnected?.())}attributeChangedCallback(e,t,o){this._$AK(e,o)}_$ET(e,t){let o=this.constructor.elementProperties.get(e),r=this.constructor._$Eu(e,o);if(r!==void 0&&o.reflect===!0){let n=(o.converter?.toAttribute!==void 0?o.converter:B).toAttribute(t,o.type);this._$Em=e,n==null?this.removeAttribute(r):this.setAttribute(r,n),this._$Em=null}}_$AK(e,t){let o=this.constructor,r=o._$Eh.get(e);if(r!==void 0&&this._$Em!==r){let n=o.getPropertyOptions(r),s=typeof n.converter=="function"?{fromAttribute:n.converter}:n.converter?.fromAttribute!==void 0?n.converter:B;this._$Em=r;let l=s.fromAttribute(t,n.type);this[r]=l??this._$Ej?.get(r)??l,this._$Em=null}}requestUpdate(e,t,o,r=!1,n){if(e!==void 0){let s=this.constructor;if(r===!1&&(n=this[e]),o??=s.getPropertyOptions(e),!((o.hasChanged??re)(n,t)||o.useDefault&&o.reflect&&n===this._$Ej?.get(e)&&!this.hasAttribute(s._$Eu(e,o))))return;this.C(e,t,o)}this.isUpdatePending===!1&&(this._$ES=this._$EP())}C(e,t,{useDefault:o,reflect:r,wrapped:n},s){o&&!(this._$Ej??=new Map).has(e)&&(this._$Ej.set(e,s??t??this[e]),n!==!0||s!==void 0)||(this._$AL.has(e)||(this.hasUpdated||o||(t=void 0),this._$AL.set(e,t)),r===!0&&this._$Em!==e&&(this._$Eq??=new Set).add(e))}async _$EP(){this.isUpdatePending=!0;try{await this._$ES}catch(t){Promise.reject(t)}let e=this.scheduleUpdate();return e!=null&&await e,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(let[r,n]of this._$Ep)this[r]=n;this._$Ep=void 0}let o=this.constructor.elementProperties;if(o.size>0)for(let[r,n]of o){let{wrapped:s}=n,l=this[r];s!==!0||this._$AL.has(r)||l===void 0||this.C(r,void 0,n,l)}}let e=!1,t=this._$AL;try{e=this.shouldUpdate(t),e?(this.willUpdate(t),this._$EO?.forEach(o=>o.hostUpdate?.()),this.update(t)):this._$EM()}catch(o){throw e=!1,this._$EM(),o}e&&this._$AE(t)}willUpdate(e){}_$AE(e){this._$EO?.forEach(t=>t.hostUpdated?.()),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(e)),this.updated(e)}_$EM(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(e){return!0}update(e){this._$Eq&&=this._$Eq.forEach(t=>this._$ET(t,this[t])),this._$EM()}updated(e){}firstUpdated(e){}};S.elementStyles=[],S.shadowRootOptions={mode:"open"},S[F("elementProperties")]=new Map,S[F("finalized")]=new Map,ft?.({ReactiveElement:S}),(oe.reactiveElementVersions??=[]).push("2.1.2");var me=globalThis,je=a=>a,ae=me.trustedTypes,Ee=ae?ae.createPolicy("lit-html",{createHTML:a=>a}):void 0,Oe="$lit$",A=`lit$${Math.random().toFixed(9).slice(2)}$`,ze="?"+A,mt=`<${ze}>`,T=document,Y=()=>T.createComment(""),G=a=>a===null||typeof a!="object"&&typeof a!="function",be=Array.isArray,bt=a=>be(a)||typeof a?.[Symbol.iterator]=="function",pe=`[ -\f\r]`,W=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Te=/-->/g,De=/>/g,j=RegExp(`>|${pe}(?:([^\\s"'>=/]+)(${pe}*=${pe}*(?:[^ -\f\r"'\`<>=]|("|')|))|$)`,"g"),Ne=/'/g,Re=/"/g,Le=/^(?:script|style|textarea|title)$/i,ye=a=>(e,...t)=>({_$litType$:a,strings:e,values:t}),m=ye(1),Ht=ye(2),It=ye(3),D=Symbol.for("lit-noChange"),g=Symbol.for("lit-nothing"),Pe=new WeakMap,E=T.createTreeWalker(T,129);function Me(a,e){if(!be(a)||!a.hasOwnProperty("raw"))throw Error("invalid template strings array");return Ee!==void 0?Ee.createHTML(e):e}var yt=(a,e)=>{let t=a.length-1,o=[],r,n=e===2?"":e===3?"":"",s=W;for(let l=0;l"?(s=r??W,c=-1):u[1]===void 0?c=-2:(c=s.lastIndex-u[2].length,p=u[1],s=u[3]===void 0?j:u[3]==='"'?Re:Ne):s===Re||s===Ne?s=j:s===Te||s===De?s=W:(s=j,r=void 0);let h=s===j&&a[l+1].startsWith("/>")?" ":"";n+=s===W?d+mt:c>=0?(o.push(p),d.slice(0,c)+Oe+d.slice(c)+A+h):d+A+(c===-2?l:h)}return[Me(a,n+(a[t]||"")+(e===2?"":e===3?"":"")),o]},V=class a{constructor({strings:e,_$litType$:t},o){let r;this.parts=[];let n=0,s=0,l=e.length-1,d=this.parts,[p,u]=yt(e,t);if(this.el=a.createElement(p,o),E.currentNode=this.el.content,t===2||t===3){let c=this.el.content.firstChild;c.replaceWith(...c.childNodes)}for(;(r=E.nextNode())!==null&&d.length0){r.textContent=ae?ae.emptyScript:"";for(let h=0;h<_;h++)r.append(c[h],Y()),E.nextNode(),d.push({type:2,index:++n});r.append(c[_],Y())}}}else if(r.nodeType===8)if(r.data===ze)d.push({type:2,index:n});else{let c=-1;for(;(c=r.data.indexOf(A,c+1))!==-1;)d.push({type:7,index:n}),c+=A.length-1}n++}}static createElement(e,t){let o=T.createElement("template");return o.innerHTML=e,o}};function O(a,e,t=a,o){if(e===D)return e;let r=o!==void 0?t._$Co?.[o]:t._$Cl,n=G(e)?void 0:e._$litDirective$;return r?.constructor!==n&&(r?._$AO?.(!1),n===void 0?r=void 0:(r=new n(a),r._$AT(a,t,o)),o!==void 0?(t._$Co??=[])[o]=r:t._$Cl=r),r!==void 0&&(e=O(a,r._$AS(a,e.values),r,o)),e}var ue=class{constructor(e,t){this._$AV=[],this._$AN=void 0,this._$AD=e,this._$AM=t}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(e){let{el:{content:t},parts:o}=this._$AD,r=(e?.creationScope??T).importNode(t,!0);E.currentNode=r;let n=E.nextNode(),s=0,l=0,d=o[0];for(;d!==void 0;){if(s===d.index){let p;d.type===2?p=new K(n,n.nextSibling,this,e):d.type===1?p=new d.ctor(n,d.name,d.strings,this,e):d.type===6&&(p=new fe(n,this,e)),this._$AV.push(p),d=o[++l]}s!==d?.index&&(n=E.nextNode(),s++)}return E.currentNode=T,r}p(e){let t=0;for(let o of this._$AV)o!==void 0&&(o.strings!==void 0?(o._$AI(e,o,t),t+=o.strings.length-2):o._$AI(e[t])),t++}},K=class a{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(e,t,o,r){this.type=2,this._$AH=g,this._$AN=void 0,this._$AA=e,this._$AB=t,this._$AM=o,this.options=r,this._$Cv=r?.isConnected??!0}get parentNode(){let e=this._$AA.parentNode,t=this._$AM;return t!==void 0&&e?.nodeType===11&&(e=t.parentNode),e}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(e,t=this){e=O(this,e,t),G(e)?e===g||e==null||e===""?(this._$AH!==g&&this._$AR(),this._$AH=g):e!==this._$AH&&e!==D&&this._(e):e._$litType$!==void 0?this.$(e):e.nodeType!==void 0?this.T(e):bt(e)?this.k(e):this._(e)}O(e){return this._$AA.parentNode.insertBefore(e,this._$AB)}T(e){this._$AH!==e&&(this._$AR(),this._$AH=this.O(e))}_(e){this._$AH!==g&&G(this._$AH)?this._$AA.nextSibling.data=e:this.T(T.createTextNode(e)),this._$AH=e}$(e){let{values:t,_$litType$:o}=e,r=typeof o=="number"?this._$AC(e):(o.el===void 0&&(o.el=V.createElement(Me(o.h,o.h[0]),this.options)),o);if(this._$AH?._$AD===r)this._$AH.p(t);else{let n=new ue(r,this),s=n.u(this.options);n.p(t),this.T(s),this._$AH=n}}_$AC(e){let t=Pe.get(e.strings);return t===void 0&&Pe.set(e.strings,t=new V(e)),t}k(e){be(this._$AH)||(this._$AH=[],this._$AR());let t=this._$AH,o,r=0;for(let n of e)r===t.length?t.push(o=new a(this.O(Y()),this.O(Y()),this,this.options)):o=t[r],o._$AI(n),r++;r2||o[0]!==""||o[1]!==""?(this._$AH=Array(o.length-1).fill(new String),this.strings=o):this._$AH=g}_$AI(e,t=this,o,r){let n=this.strings,s=!1;if(n===void 0)e=O(this,e,t,0),s=!G(e)||e!==this._$AH&&e!==D,s&&(this._$AH=e);else{let l=e,d,p;for(e=n[0],d=0;d{let o=t?.renderBefore??e,r=o._$litPart$;if(r===void 0){let n=t?.renderBefore??null;o._$litPart$=r=new K(e.insertBefore(Y(),n),n,void 0,t??{})}return r._$AI(a),r};var ve=globalThis,$=class extends S{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0}createRenderRoot(){let e=super.createRenderRoot();return this.renderOptions.renderBefore??=e.firstChild,e}update(e){let t=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(e),this._$Do=Ue(t,this.renderRoot,this.renderOptions)}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(!0)}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(!1)}render(){return D}};$._$litElement$=!0,$.finalized=!0,ve.litElementHydrateSupport?.({LitElement:$});var xt=ve.litElementPolyfillSupport;xt?.({LitElement:$});(ve.litElementVersions??=[]).push("4.2.2");var wt={attribute:!0,type:String,converter:B,reflect:!1,hasChanged:re},kt=(a=wt,e,t)=>{let{kind:o,metadata:r}=t,n=globalThis.litPropertyMetadata.get(r);if(n===void 0&&globalThis.litPropertyMetadata.set(r,n=new Map),o==="setter"&&((a=Object.create(a)).wrapped=!0),n.set(t.name,a),o==="accessor"){let{name:s}=t;return{set(l){let d=e.get.call(this);e.set.call(this,l),this.requestUpdate(s,d,a,!0,l)},init(l){return l!==void 0&&this.C(s,void 0,a,l),l}}}if(o==="setter"){let{name:s}=t;return function(l){let d=this[s];e.call(this,l),this.requestUpdate(s,d,a,!0,l)}}throw Error("Unsupported decorator location: "+o)};function Q(a){return(e,t)=>typeof t=="object"?kt(a,e,t):((o,r,n)=>{let s=r.hasOwnProperty(n);return r.constructor.createProperty(n,o),s?Object.getOwnPropertyDescriptor(r,n):void 0})(a,e,t)}function w(a){return Q({...a,state:!0,attribute:!1})}var St={days:1,weeks:7,months:30.4368,years:365.25};function He(a,e){return!a||a<=0?0:a*(St[e||"days"]??1)}var Ie=5;function J(a){let e=a.getFullYear(),t=String(a.getMonth()+1).padStart(2,"0"),o=String(a.getDate()).padStart(2,"0");return`${e}-${t}-${o}`}function $t(a,e){let t=[];for(let o=0;ot.cost).filter(t=>typeof t=="number");return e.length===0?null:e.reduce((t,o)=>t+o,0)/e.length}function Ct(a){let{windowStart:e,windowEnd:t,task:o,entryId:r,objectName:n}=a,s=[],l=(c,_)=>({date:c,entry_id:r,task_id:o.id,task_name:o.name,object_name:n,status:_&&(o.status==="overdue"||o.status==="triggered")?"ok":o.status,days_until_due:_?null:o.days_until_due??null,projected:_,schedule_type:o.schedule_type,interval_days:o.interval_days??null,interval_unit:o.interval_unit??null,responsible_user_id:o.responsible_user_id??null,avg_cost:At(o.history),adaptive_enabled:!!o.adaptive_config?.enabled,prediction_confidence:o.threshold_prediction_confidence??null}),d=Math.max(1,Math.round(He(o.interval_days,o.interval_unit)));if(o.status==="overdue"||o.status==="triggered"){if(s.push(l(e,!1)),o.schedule_type==="time_based"&&o.interval_days&&o.interval_days>0){let c=se(e,d),_=1;for(;c<=t&&_=e&&u<=t)s.push(l(u,!1));else if(u>t)return s;if(o.schedule_type==="time_based"&&o.interval_days&&o.interval_days>0){let c=se(u,d),_=s.length;for(;c<=t&&_=e&&(s.push(l(c,!0)),_++),c=se(c,d)}return s}var qe={overdue:0,triggered:1,due_soon:2,ok:3};function Fe(a,e,t,o=null){let r=$t(e,t),n=r[0],s=r[r.length-1],l=[];for(let p of a){let u=p.object?.name||"",c=p.entry_id,_=p.tasks||[];for(let h of _){if(o&&h.responsible_user_id!==o||h.enabled===!1)continue;let b=Ct({windowStart:n,windowEnd:s,task:h,entryId:c,objectName:u});l.push(...b)}}let d=new Map;for(let p of r)d.set(p,[]);for(let p of l){let u=d.get(p.date);u&&u.push(p)}for(let[,p]of d)p.sort((u,c)=>{let _=qe[u.status]??99,h=qe[c.status]??99;if(_!==h)return _-h;if(u.projected!==c.projected)return u.projected?1:-1;let b=u.object_name.localeCompare(c.object_name);return b!==0?b:u.task_name.localeCompare(c.task_name)});return r.map(p=>({date:p,events:d.get(p)??[]}))}var jt={completed:"ok",reset:"ok",skipped:"due_soon",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"};function Et(a,e){let t=[];for(let o=e-1;o>=0;o--){let r=new Date(a);r.setDate(r.getDate()-o),r.setHours(0,0,0,0),t.push(J(r))}return t}function Be(a,e,t,o=null){let r=Et(e,t),n=r[0],s=r[r.length-1],l=new Map;for(let p of r)l.set(p,[]);for(let p of a){let u=p.object?.name||"",c=p.entry_id,_=p.tasks||[];for(let h of _){if(o&&h.responsible_user_id!==o)continue;let b=h.history||[];for(let y of b){if(typeof y?.timestamp!="string")continue;let R=y.timestamp.slice(0,10);if(Rs)continue;let M=l.get(R);if(!M)continue;let U=y.type??"completed";M.push({date:R,entry_id:c,task_id:h.id,task_name:h.name,object_name:u,status:jt[U]??"ok",days_until_due:null,projected:!1,schedule_type:h.schedule_type,interval_days:h.interval_days??null,responsible_user_id:h.responsible_user_id??null,avg_cost:typeof y.cost=="number"?y.cost:null,adaptive_enabled:!!h.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:y.timestamp,history_type:U,history_cost:typeof y.cost=="number"?y.cost:null,history_notes:typeof y.notes=="string"?y.notes:null,history_duration:typeof y.duration=="number"?y.duration:null})}}}let d={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,p]of l)p.sort((u,c)=>{let _=d[u.history_type??""]??99,h=d[c.history_type??""]??99;if(_!==h)return _-h;let b=u.object_name.localeCompare(c.object_name);return b!==0?b:u.task_name.localeCompare(c.task_name)});return r.map(p=>({date:p,events:l.get(p)??[]}))}var We=k` - .cal-controls { - display: flex; - gap: 12px; - align-items: center; - flex-wrap: wrap; - padding: 12px 16px; - border-bottom: 1px solid var(--divider-color); - } - .cal-window-chips { - display: flex; - gap: 4px; - background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); - border-radius: 999px; - padding: 3px; - } - .cal-window-chip { - padding: 6px 14px; - border: none; - background: transparent; - color: var(--secondary-text-color); - font-size: 13px; - font-weight: 500; - cursor: pointer; - border-radius: 999px; - transition: background 0.12s, color 0.12s; - } - .cal-window-chip:hover { color: var(--primary-text-color); } - .cal-window-chip.active { - background: var(--primary-color); - color: var(--text-primary-color, #fff); - } - /* v2.2.0 — past-window chips: visually distinguished from forward chips - so the user grasps the time-direction switch at a glance. Uses a - muted secondary tone instead of the primary blue. v2.3.x: explicit - "−N d" / "+N d" prefixes + dot separator so past vs forward groups - read at a glance instead of being two pill rows that look identical - except for a small arrow. (User feedback: *"das −30 und die + sind - noch schlecht angeordnet"*.) */ - .cal-past-chips { - /* margin-right replaced by explicit separator below */ - } - .cal-past-chip.active { - background: var(--secondary-text-color, #888); - } - .cal-chip-separator { - color: var(--divider-color); - font-size: 8px; - align-self: center; - margin: 0 2px; - line-height: 1; - } - .cal-user-filter { - margin-left: auto; - padding: 6px 10px; - background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); - color: var(--primary-text-color); - border: 1px solid var(--divider-color); - border-radius: 6px; - font-size: 13px; - cursor: pointer; - } - .cal-rolling { padding: 8px 16px 32px; } - .cal-day-row { - display: flex; - gap: 12px; - padding: 12px 0; - border-bottom: 1px solid var(--divider-color); - } - .cal-day-pill { - width: 56px; - height: 56px; - border-radius: 12px; - background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); - border: 1px solid var(--divider-color); - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex-shrink: 0; - } - .cal-day-pill.cal-today { - background: var(--primary-color); - border-color: var(--primary-color); - } - .cal-pill-weekday { - font-size: 10px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.4px; - color: var(--secondary-text-color); - } - .cal-pill-day { - font-size: 20px; - font-weight: 700; - color: var(--primary-text-color); - line-height: 1.1; - } - .cal-day-pill.cal-today .cal-pill-weekday, - .cal-day-pill.cal-today .cal-pill-day { - color: var(--text-primary-color, #fff); - } - .cal-day-content { flex: 1; min-width: 0; } - .cal-day-header { - display: flex; - align-items: baseline; - gap: 8px; - margin-bottom: 6px; - } - .cal-day-month { color: var(--secondary-text-color); font-size: 13px; } - .cal-day-today-badge { - color: var(--primary-color); - font-size: 11px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.5px; - } - .cal-empty { - color: var(--secondary-text-color); - font-size: 13px; - font-style: italic; - padding: 4px 0 4px; - } - .cal-event { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 0; - cursor: pointer; - border-radius: 4px; - transition: background 0.12s; - } - .cal-event:hover { background: var(--state-icon-color, rgba(255,255,255,0.04)); } - .cal-event-projected { opacity: 0.55; } - .cal-event-body { flex: 1; min-width: 0; } - .cal-event-title { - font-size: 14px; - color: var(--primary-text-color); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - .cal-event-recur { - display: block; - font-size: 11px; - color: var(--secondary-text-color); - margin-top: 2px; - } - .cal-event-icon { - --mdc-icon-size: 18px; - flex-shrink: 0; - } - .cal-source-time { color: var(--secondary-text-color); } - .cal-source-sensor { color: var(--primary-color); } - .cal-event-prediction { - display: inline-block; - font-size: 11px; - margin-top: 2px; - padding: 1px 6px; - border-radius: 999px; - background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); - border: 1px solid var(--divider-color); - } - .cal-conf-high { color: var(--success-color, #4caf50); border-color: #4caf5044; } - .cal-conf-medium { color: var(--warning-color, #f9a825); border-color: #f9a82544; } - .cal-conf-low { color: var(--error-color, #d32f2f); border-color: #d32f2f44; } - .cal-event-cost { - font-size: 12px; - color: var(--secondary-text-color); - flex-shrink: 0; - } - .cal-status-pill { - flex-shrink: 0; - padding: 2px 8px; - border-radius: 999px; - font-size: 10px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.4px; - color: #fff; - } - /* Same tokens as .status-badge (status-constants.ts) — the calendar used - to keep its own palette (triggered was even BLUE here) so identical - statuses wore different colors per view, and none followed the theme. */ - .cal-status-overdue { background: var(--error-color, #f44336); } - .cal-status-triggered { background: var(--deep-orange-color, #ff5722); } - .cal-status-due_soon { background: var(--warning-color, #ff9800); color: #000; } - /* Dark text — white on green is only 2.8:1 (below the 3:1 UI floor). */ - .cal-status-ok { background: var(--success-color, #4caf50); color: #000; } - - @media (max-width: 600px) { - .cal-controls { padding: 10px 12px; } - .cal-rolling { padding: 6px 12px 24px; } - .cal-day-pill { width: 48px; height: 48px; } - .cal-pill-day { font-size: 17px; } - .cal-user-filter { margin-left: 0; width: 100%; } - } -`;var Ye={maintenance:"Maintenance",objects:"Objects",tasks:"Tasks",overdue:"Overdue",due_soon:"Due Soon",triggered:"Triggered",trigger_replaced:"Trigger replaced",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter +/*! maintenance_supporter frontend 2.59.0 */ +var bt=Object.defineProperty;var Wi=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 Vi=(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?Wi(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 Ne,He,Je,xt,ye,wt,S,$t,Ze,Xe=w(()=>{Ne=globalThis,He=Ne.ShadowRoot&&(Ne.ShadyCSS===void 0||Ne.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=Ne.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 Ki,Gi,Yi,Qi,Ji,Zi,qe,kt,Xi,er,be,xe,Me,Et,K,we=w(()=>{Xe();Xe();({is:Ki,defineProperty:Gi,getOwnPropertyDescriptor:Yi,getOwnPropertyNames:Qi,getOwnPropertySymbols:Ji,getPrototypeOf:Zi}=Object),qe=globalThis,kt=qe.trustedTypes,Xi=kt?kt.emptyScript:"",er=qe.reactiveElementPolyfillSupport,be=(a,r)=>a,xe={toAttribute(a,r){switch(r){case Boolean:a=a?Xi: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)=>!Ki(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&&Gi(this.prototype,r,i)}}static getPropertyDescriptor(r,e,t){let{get:i,set:n}=Yi(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=Zi(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=[...Qi(e),...Ji(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,er?.({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,jt,tr,se,ke,Ee,ot,ir,et,$e,Tt,Ct,ie,It,Lt,Nt,lt,l,_e,rs,ae,h,Pt,re,rr,Se,tt,Ae,ue,it,rt,st,at,sr,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)}$`,jt="?"+X,tr=`<${jt}>`,se=document,ke=()=>se.createComment(""),Ee=a=>a===null||typeof a!="object"&&typeof a!="function",ot=Array.isArray,ir=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,Nt=/^(?:script|style|textarea|title)$/i,lt=a=>(r,...e)=>({_$litType$:a,strings:r,values:e}),l=lt(1),_e=lt(2),rs=lt(3),ae=Symbol.for("lit-noChange"),h=Symbol.for("lit-nothing"),Pt=new WeakMap,re=se.createTreeWalker(se,129);rr=(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,_=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+tr:m>=0?(t.push(_),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,[_,f]=rr(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=pe(this,r,e,0),o=!Ee(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 Ae(r.insertBefore(ke(),n),n,void 0,e??{})}return i._$AI(a),i}});var dt,A,ar,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});ar=dt.litElementPolyfillSupport;ar?.({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"?or(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 nr,or,ct=w(()=>{we();nr={attribute:!0,type:String,converter:xe,reflect:!1,hasChanged:Me},or=(a=nr,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(()=>{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",ok:"OK",all:"All",new_object:"+ New Object",templates_from:"From template",templates_title:"Start from a template",templates_task_count:"{n} tasks",template_created:"Created from template",onboard_hint:"Add your first object to start tracking maintenance.",edit:"Edit",duplicate:"Duplicate",task_duplicated:"Task duplicated",object_duplicated:"Object duplicated",delete:"Delete",add_task:"+ Add Task",complete:"Complete",completed:"Completed",skip:"Skip",skipped:"Skipped",missed:"Missed",reset:"Reset",snooze:"Snooze",snoozed:"Snoozed",cancel:"Cancel",bulk_select:"Select",bulk_select_all:"Select all",bulk_n_selected:"{n} selected",bulk_completed:"{n} tasks completed",bulk_archived:"{n} tasks archived",completing:"Completing\u2026",interval:"Interval",warning:"Warning",last_performed:"Last performed",next_due:"Next due",days_until_due:"Days until due",avg_duration:"Avg duration",trigger:"Trigger",trigger_type:"Trigger type",threshold_above:"Upper limit",threshold_below:"Lower limit",threshold:"Threshold",counter:"Counter",state_change:"State change",runtime:"Runtime",runtime_hours:"Target runtime (hours)",target_value:"Target value",baseline:"Baseline",target_changes:"Target changes",for_minutes:"For (minutes)",time_based:"Time-based",sensor_based:"Sensor-based",manual:"Manual",one_time:"One-time",weekdays:"Weekdays",nth_weekday:"Nth weekday of month",day_of_month:"Day of month",recurrence_on_days:"Repeat on",recurrence_occurrence:"Occurrence",recurrence_weekday:"Weekday",recurrence_day:"Day of month (1\u201331)",recurrence_last_day:"Last day of the month",recurrence_business_day:"Business days only (roll back from weekend)",recurrence_offset:"Offset (days, \xB1)",recurrence_offset_help:"Shift the date by \xB1N days, e.g. -2 = two days before.",last_day_month:"Last day of month",last_business_day_month:"Last business day",ord_1:"1st",ord_2:"2nd",ord_3:"3rd",ord_4:"4th",ord_5:"5th",ord_last:"Last",day_word:"Day",interval_value:"Interval",interval_unit:"Unit",unit_days:"Days",unit_weeks:"Weeks",unit_months:"Months",unit_years:"Years",due_date:"Due date",cleaning:"Cleaning",inspection:"Inspection",replacement:"Replacement",calibration:"Calibration",service:"Service",reading:"Reading",custom:"Custom",history:"History",cost:"Cost",report_button:"Report",report_title:"Maintenance report",report_generated:"Generated",report_times_done:"Done",report_total_cost:"Total cost",report_every:"every {n} {unit}",report_notes:"Notes",report_col_type:"Type",report_col_status:"Status",report_col_schedule:"Schedule",duration:"Duration",both:"Both",trigger_val:"Trigger value",complete_title:"Complete: ",checklist:"Checklist",require_on_completion:"Require on completion",checklist_steps_optional:"Checklist steps (optional)",checklist_placeholder:`Clean filter Replace seal -Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",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",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",my_tasks:"My Tasks",tab_calendar:"Calendar",cal_no_events:"No maintenance",cal_window_7:"7 days",cal_window_14:"14 days",cal_window_30:"30 days",cal_window_365:"1 year",cal_every_n_days:"every {n} days",cal_source_time:"Time-based",cal_source_time_adaptive:"Time-based (adaptive)",cal_source_sensor:"Sensor-based",cal_predicted:"predicted",cal_confidence_high:"high confidence",cal_confidence_medium:"medium confidence",cal_confidence_low:"low confidence",budget_monthly:"Monthly budget",budget_yearly:"Yearly budget",groups:"Groups",new_group:"New group",edit_group:"Edit group",no_groups:"No groups yet",delete_group:"Delete group",delete_group_confirm:"Delete group '{name}'?",group_select_tasks:"Select tasks",group_name_required:"Name is required",description_optional:"Description (optional)",selected:"Selected",loading_chart:"Loading chart data...",hide_outliers:"Hide outliers (sensor glitches)",was_maintenance_needed:"Was this maintenance needed?",feedback_needed:"Needed",feedback_not_needed:"Not needed",feedback_not_sure:"Not sure",suggested_interval:"Suggested interval",apply_suggestion:"Apply",reanalyze:"Re-analyze",reanalyze_result:"New analysis",reanalyze_insufficient_data:"Not enough data to produce a recommendation",data_points:"data points",dismiss_suggestion:"Dismiss",confidence_low:"Low",confidence_medium:"Medium",confidence_high:"High",recommended:"recommended",seasonal_awareness:"Seasonal Awareness",edit_seasonal_overrides:"Edit seasonal factors",seasonal_overrides_title:"Seasonal factors (override)",seasonal_overrides_hint:"Factor per month (0.1\u20135.0). Empty = learned automatically.",seasonal_override_invalid:"Invalid value",seasonal_override_range:"Factor must be between 0.1 and 5.0",clear_all:"Clear all",seasonal_chart_title:"Seasonal Factors",seasonal_learned:"Learned",seasonal_manual:"Manual",month_jan:"Jan",month_feb:"Feb",month_mar:"Mar",month_apr:"Apr",month_may:"May",month_jun:"Jun",month_jul:"Jul",month_aug:"Aug",month_sep:"Sep",month_oct:"Oct",month_nov:"Nov",month_dec:"Dec",sensor_prediction:"Sensor Prediction",degradation_trend:"Trend",trend_rising:"Rising",trend_falling:"Falling",trend_stable:"Stable",trend_insufficient_data:"Insufficient data",days_until_threshold:"Days until threshold",threshold_exceeded:"Threshold exceeded",environmental_adjustment:"Environmental factor",sensor_prediction_urgency:"Sensor predicts threshold in ~{days} days",day_short:"day",weibull_reliability_curve:"Reliability Curve",weibull_failure_probability:"Failure Probability",weibull_r_squared:"Fit R\xB2",beta_early_failures:"Early Failures",beta_random_failures:"Random Failures",beta_wear_out:"Wear-out",beta_highly_predictable:"Highly Predictable",confidence_interval:"Confidence Interval",confidence_conservative:"Conservative",confidence_aggressive:"Optimistic",current_interval_marker:"Current interval",recommended_marker:"Recommended",characteristic_life:"Characteristic life",chart_mini_sparkline:"Trend sparkline",chart_history:"Cost and duration history",chart_seasonal:"Seasonal factors, 12 months",chart_weibull:"Weibull reliability curve",chart_sparkline:"Sensor trigger value chart",days_progress:"Days progress",qr_code:"QR Code",qr_generating:"Generating QR code\u2026",qr_error:"Failed to generate QR code.",qr_error_no_url:"No HA URL configured. Please set an external or internal URL in Settings \u2192 System \u2192 Network.",save_error:"Failed to save. Please try again.",qr_print:"Print",qr_download:"Download SVG",qr_action:"Action on scan",qr_action_view:"View maintenance info",qr_action_complete:"Mark maintenance as complete",qr_url_mode:"Link type",qr_mode_companion:"Companion App",qr_mode_local:"Local (mDNS)",qr_mode_server:"Server URL",overview:"Overview",analysis:"Analysis",recent_activities:"Recent Activities",search_notes:"Search notes",avg_cost:"Avg Cost",no_advanced_features:"No advanced features enabled",no_advanced_features_hint:"Enable \u201CAdaptive Intervals\u201D or \u201CSeasonal Patterns\u201D in the integration settings to see analysis data here.",analysis_not_enough_data:"Not enough data for analysis yet.",analysis_not_enough_data_hint:"Weibull analysis requires at least 5 completed maintenances; seasonal patterns become visible after 6+ data points per month.",analysis_manual_task_hint:"Manual tasks without an interval do not generate analysis data.",completions:"completions",current:"Current",shorter:"Shorter",longer:"Longer",normal:"Normal",disabled:"Disabled",compound_logic:"Compound logic",compound:"Compound (multiple conditions)",compound_logic_and:"AND \u2014 all conditions must trigger",compound_logic_or:"OR \u2014 any condition triggers",compound_help:"Combine several sensor conditions into one trigger.",compound_no_conditions:"No conditions yet \u2014 add at least one.",compound_add_condition:"Add condition",compound_condition:"Condition",compound_remove_condition:"Remove condition",card_title:"Title",card_show_header:"Show header with statistics",card_show_actions:"Show action buttons",card_compact:"Compact mode",card_max_items:"Max items (0 = all)",card_filter_status:"Filter by status",card_filter_status_help:"Empty = show all statuses.",card_filter_objects:"Filter by objects",card_filter_objects_help:"Empty = show all objects.",card_filter_areas:"Filter by areas",card_filter_areas_help:"Empty = show all areas.",card_filter_entities:"Filter by entities (entity_ids)",card_filter_entities_help:"Pick sensor / binary_sensor entities from this integration. Empty = all.",card_loading_objects:"Loading objects\u2026",card_load_error:"Could not load objects \u2014 check the WebSocket connection.",card_no_tasks_title:"No maintenance tasks yet",card_no_tasks_cta:"\u2192 Create one in the Maintenance panel",no_objects:"No objects yet.",action_error:"Action failed. Please try again.",area_id_optional:"Area (optional)",installation_date_optional:"Installation date (optional)",warranty_expiry_optional:"Warranty expiry (optional)",warranty:"Warranty",warranty_valid_until:"valid until {date}",warranty_expires_in:"expires in {days} days",warranty_expired:"expired",cal_past_windows:"Past windows",cal_forward_windows:"Forward windows",history_edit_title:"Edit history entry",history_edit_timestamp:"Timestamp",manufacturer:"Manufacturer",model:"Model",area:"Area",actions:"Actions",view_mode_label:"View",view_cards:"Card view",view_table:"Table view",objects_table_columns_label:"Objects table columns",objects_table_columns_hint:"Choose which columns appear in the objects table view.",custom_icon_optional:"Icon (optional, e.g. mdi:wrench)",task_enabled:"Task enabled",skip_reason_prompt:"Skip this task?",reason_optional:"Reason (optional)",reset_date_prompt:"Mark task as performed?",reset_date_optional:"Last performed date (optional, defaults to today)",notes_label:"Notes",documentation_label:"Documentation",no_nfc_tag:"\u2014 No tag \u2014",dashboard:"Dashboard",tab_today:"Today",palette_placeholder:"Search objects and tasks\u2026",palette_no_results:"No matches",palette_hint:"\u2191\u2193 to navigate \xB7 Enter to open \xB7 Esc to close",today_all_caught_up:"All caught up! Nothing due this week.",today_overdue:"Overdue",today_due_today:"Due today",today_this_week:"This week",settings:"Settings",settings_features:"Advanced Features",settings_features_desc:"Enable or disable advanced features. Disabling hides them from the UI but does not delete data.",feat_adaptive:"Adaptive Scheduling",feat_adaptive_desc:"Learn optimal intervals from maintenance history",feat_predictions:"Sensor Predictions",feat_predictions_desc:"Predict trigger dates from sensor degradation",feat_seasonal:"Seasonal Adjustments",feat_seasonal_desc:"Adjust intervals based on seasonal patterns",feat_environmental:"Environmental Correlation",feat_environmental_desc:"Correlate intervals with temperature/humidity",feat_budget:"Budget Tracking",feat_budget_desc:"Track monthly and yearly maintenance spending",feat_groups:"Task Groups",feat_groups_desc:"Organize tasks into logical groups",feat_checklists:"Checklists",feat_checklists_desc:"Multi-step procedures for task completion",settings_general:"General",settings_default_warning:"Default warning days",settings_panel_enabled:"Sidebar panel",settings_panel_title:"Sidebar panel title",settings_notifications:"Notifications",settings_notify_service:"Notification service",settings_install_assist_sentences:"Install Assist sentences",settings_install_assist_sentences_hint:"Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",test_notification:"Test notification",send_test:"Send test",testing:"Sending\u2026",test_notification_success:"Test notification sent",test_notification_failed:"Test notification failed",notify_per_person:"Per-person delivery",notify_no_own_device:"No own device \u2014 uses the household service",settings_notify_due_soon:"Notify when due soon",settings_notify_overdue:"Notify when overdue",settings_notify_triggered:"Notify when triggered",settings_interval_hours:"Repeat interval (hours, 0 = once)",settings_quiet_hours:"Quiet hours",settings_quiet_start:"Start",settings_quiet_end:"End",settings_max_per_day:"Max notifications per day (0 = unlimited)",settings_bundling:"Bundle notifications",settings_bundle_threshold:"Bundle threshold",settings_reminder_leads:"Extra reminders (days before due)",settings_reminder_leads_hint:"Comma-separated lead times, e.g. 14, 3, 0 \u2014 one extra reminder fires on each matching day. Empty = off.",settings_actions:"Mobile Action Buttons",settings_action_complete:"Show 'Complete' button",settings_action_skip:"Show 'Skip' button",settings_action_snooze:"Show 'Snooze' button",settings_weekly_digest:"Weekly digest",settings_weekly_digest_hint:"A single summary notification on Monday morning when tasks are due.",settings_warranty_reminder:"Warranty expiry reminder",settings_warranty_reminder_days:"Days before expiry",settings_warranty_reminder_hint:"Notify once when an object's warranty is this many days from expiring.",settings_snooze_hours:"Snooze duration (hours)",settings_budget:"Budget",settings_currency:"Currency",settings_budget_monthly:"Monthly budget",settings_budget_yearly:"Yearly budget",settings_budget_alerts:"Budget alerts",settings_budget_threshold:"Alert threshold (%)",settings_import_export:"Import / Export",settings_export_json:"Export JSON",settings_export_yaml:"Export YAML",settings_export_csv:"Export CSV",settings_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).",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_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet"};var Ve="\u20AC",xe="en",Ke=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),N=Ke.store;N.en||(N.en=Ye);var Dt=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),Nt="/maintenance_supporter_locales",Z=Ke.inflight;function we(a){let e=(a||xe).toLowerCase();return e.startsWith("pt")&&e.endsWith("br")?"pt-br":e.substring(0,2)}function f(a,e){let t=we(e);return N[t]?.[a]??N.en[a]??a}function Qe(a){return a?.language||"en"}function Je(a){let e=we(a);return e===xe||e in N}function Ze(a){let e=we(a);return e===xe||e in N||!Dt.has(e)?Promise.resolve():(e in Z||(Z[e]=fetch(`${Nt}/${e}.json`).then(t=>t.ok?t.json():null).then(t=>{t?N[e]=t:delete Z[e]}).catch(()=>{delete Z[e]})),Z[e])}var Rt=window,Ge=Rt.__msDateTimePrefs??={};function Xe(a){a&&(Ge.date=a.date_format,Ge.time=a.time_format)}function et(a,e){if(a==null)return"\u2014";let t=e||"en";return a<0?`${Math.abs(a)} ${f("d_overdue",t)}`:a===0?f("today",t):`${a} ${f(a===1?"day":"days",t)}`}var Uo=k` +Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",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",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_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).",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_problem_configure:"Configure",history_auto:"Automatic",battery_fleet_title:"Battery fleet",battery_fleet_none_low:"All batteries OK \u2014 nothing to replace.",battery_fleet_buy_now:"Buy now",battery_fleet_soon:"Needed soon",battery_fleet_soon_hint:"Predicted from the last replacement date \u2014 order ahead.",battery_fleet_mark_all:"Mark all replaced",battery_fleet_mark_one:"Mark this battery replaced",battery_fleet_offline:"offline",battery_fleet_trigger_lost:"This task's sensor trigger was lost \u2014 it will not fire or auto-complete.",battery_fleet_repair:"Repair",battery_fleet_exclude:"Exclude from the fleet",battery_fleet_excluded:"Excluded",battery_fleet_include:"Track again",battery_fleet_all:"All tracked batteries",battery_fleet_all_hint:"Exclude a device here to drop it from the fleet before it ever reports low \u2014 a vacuum that recharges itself, or a phone that warns you on its own.",battery_fleet_status_low:"Low",battery_fleet_status_soon:"Soon",battery_fleet_status_ok:"Healthy",battery_fleet_predicted_on:"Expected around {date}",battery_fleet_predicted_trend:"Predicted from this battery's discharge trend: around {date} ({confidence})",battery_fleet_rechargeable:"Rechargeable: charge instead of replacing \u2014 never on the shopping list",battery_fleet_sort_name:"Sort by name",battery_fleet_sort_urgency:"Sort by urgency",battery_fleet_mark_recharged:"Mark as recharged",battery_fleet_sparkline_hint:"Battery level over the last 30 days \u2014 dotted: projected until the low threshold",battery_fleet_filter_type:"Show only this battery type",battery_fleet_record_replacement:"The level jumped around {date} \u2014 record this replacement in Battery Notes",battery_fleet_total:"{n} batteries tracked",battery_fleet_setup_button:"Battery fleet",battery_fleet_setup_done:"Battery fleet set up \u2014 one task tracks all your batteries.",update_banner:"A newer version of Maintenance Supporter is on the server \u2014 reload to update the panel.",update_reload:"Reload",battery_fleet_forecast_overdue:"Predicted date passed \u2014 the battery still reports healthy. If you swapped it, record the replacement; otherwise the forecast was off.",cost_from_parts:"Use \u2248 {amount} from parts",dismiss:"Dismiss",gs_label:"Getting started \u2014 these hints retire as your setup grows",gs_setups_chip:"Suggested setups found {n} devices with pre-wired triggers",gs_adopt_chip:"{n} problem sensors can become maintenance tasks",gs_fleet_chip:"One click sets up the battery fleet",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 Ce,ii=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 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 H(a){return a?.language||"en"}function Re(a){let r=We(a);return r===pt||r in ne}function je(a){let r=We(a);return r===pt||r in ne||!gr.has(r)?Promise.resolve():(r in Ie||(Ie[r]=fetch(`${mr}/${r}.json`).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 ai(a){a&&(Ue.date=a.date_format,Ue.time=a.time_format)}function ni(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 vr(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 ni(new Date(e),r)}catch{return a}}function oi(a,r){if(!a)return"\u2014";try{let e=new Date(a);return ni(e,r)+" "+vr(e,r)}catch{return a}}function li(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 di(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 ci(a,r){a.currentTarget.dispatchEvent(new CustomEvent("hass-more-info",{detail:{entityId:r},bubbles:!0,composed:!0}))}var ri,pt,si,ne,gr,mr,Ie,fr,Ue,pi,Ve,R=w(()=>{"use strict";P();ei();ii();ri="\u20AC",pt="en",si=(()=>{let a=window;return a.__msLocales||(a.__msLocales={store:{},inflight:{}}),a.__msLocales})(),ne=si.store;ne.en||(ne.en=ti);gr=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),mr="/maintenance_supporter_locales",Ie=si.inflight;fr=window,Ue=fr.__msDateTimePrefs??={};pi=S` .field { display: flex; flex-direction: column; gap: 4px; } .field-label { font-size: 12px; color: var(--secondary-text-color); } .field-input { @@ -211,7 +14,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:" font-family: inherit; width: 100%; box-sizing: border-box; } .field-input:focus { outline: none; border-color: var(--primary-color); } -`,tt=k` +`,Ve=S` :host { --maint-ok-color: var(--success-color, #4caf50); --maint-due-soon-color: var(--warning-color, #ff9800); @@ -1354,154 +1157,2976 @@ 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 ot(a){let e=window;e.customCards=e.customCards||[],e.customCards.some(t=>t.type===a.type)||e.customCards.push(a)}var v=class extends ${constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"};this._objects=[];this._stats=null;this._windowDays=30;this._pastDays=0;this._userFilter="";this._objectFilter="";this._configuredObjects=[];this._unsub=null;this._dataLoaded=!1;this._lastConnection=null}static getConfigElement(){return document.createElement("maintenance-supporter-calendar-card-editor")}static getStubConfig(){return{type:"custom:maintenance-supporter-calendar-card",window_days:30,show_window_chips:!0,show_user_filter:!0}}setConfig(t){if(this._config={...t},t.past_days&&[30,90].includes(t.past_days)?this._pastDays=t.past_days:t.window_days&&[7,14,30,365].includes(t.window_days)&&(this._windowDays=t.window_days,this._pastDays=0),typeof t.user_filter=="string"&&(this._userFilter=t.user_filter),typeof t.object_filter=="string")this._objectFilter=t.object_filter,this._configuredObjects=[];else if(Array.isArray(t.object_filter)){let o=t.object_filter.filter(r=>typeof r=="string"&&r!=="");this._objectFilter=o.length===1?o[0]:"",this._configuredObjects=o.length>1?o:[]}}getCardSize(){return 6}get _lang(){return Qe(this.hass)}disconnectedCallback(){if(super.disconnectedCallback(),this._unsub){try{this._unsub()}catch{}this._unsub=null}this._dataLoaded=!1,this._lastConnection=null}updated(t){super.updated(t),t.has("hass")&&Xe(this.hass?.locale);let o=this.hass?.language;if(o&&!Je(o)&&Ze(o).then(()=>this.requestUpdate()),t.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}}}async _loadData(){try{let[t,o]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"})]);this._objects=t.objects,this._stats=o}catch{}}async _subscribe(){try{let t=await this.hass.connection.subscribeMessage(o=>{let r=o;this._objects=r.objects},{type:"maintenance_supporter/subscribe"});if(!this.isConnected){t();return}this._unsub=t}catch{}}_onEventClick(t){if(t.history_timestamp){this.dispatchEvent(new CustomEvent("ll-custom",{detail:{type:"maintenance-supporter:edit-history",entry_id:t.entry_id,task_id:t.task_id,original_timestamp:t.history_timestamp},bubbles:!0,composed:!0}));return}this.dispatchEvent(new CustomEvent("ll-custom",{detail:{type:"maintenance-supporter:open-task",entry_id:t.entry_id,task_id:t.task_id},bubbles:!0,composed:!0}))}render(){if(!this.hass)return g;let t=this._lang,o=this._config.show_window_chips!==!1,r=this._config.show_user_filter!==!1,n=this._config.title,s=null;this._userFilter&&(s=this._userFilter==="current_user"?this.hass?.user?.id??null:this._userFilter);let l=i=>{let C=i.toLowerCase();return this._objects.find(P=>P.entry_id===i||P.object.name.toLowerCase()===C)?.entry_id??null},d=new Set(this._configuredObjects.map(l).filter(i=>i!==null)),p=d.size?this._objects.filter(i=>d.has(i.entry_id)):this._objects,u=this._config.show_object_filter!==!1&&p.length>1,c=this._objectFilter?l(this._objectFilter):null,_=c&&p.some(i=>i.entry_id===c)?p.filter(i=>i.entry_id===c):p,h=new Date;h.setHours(0,0,0,0);let b=this._pastDays>0,y=b?Be(_,h,this._pastDays,s):Fe(_,h,this._windowDays,s),R=J(h),M=this._windowDays===365||b,U=M?y.filter(i=>i.events.length>0):y,rt=i=>{let C=`cal-status-${i.status}`,X=i.projected?"cal-event-projected":"",P=i.status==="overdue"&&i.days_until_due!=null?` (${et(i.days_until_due,t)})`:"",H=i.projected&&i.interval_days?m`${i.interval_unit&&i.interval_unit!=="days"?`${i.interval_days} ${f("unit_"+i.interval_unit,t)}`:f("cal_every_n_days",t).replace("{n}",String(i.interval_days))}`:g,I=i.schedule_type==="sensor_based",ie=I?m``:m``,le=I&&i.prediction_confidence&&i.status!=="triggered"&&!i.projected?m` - ${f("cal_predicted",t)} · ${f(`cal_confidence_${i.prediction_confidence}`,t)} - `:g,nt=this._stats?.budget?.currency_symbol||Ve,st=i.history_type?f(i.history_type,t):f(i.status,t);return m` -
this._onEventClick(i)}> - ${ie} - ${st} -
-
${i.object_name} · ${i.task_name}${P}
- ${le} - ${H} -
- ${i.avg_cost!=null&&i.avg_cost>0?m`${i.avg_cost.toFixed(0)} ${nt}`:g} +`});function br(a,r){let e=yr[a];if(!e)return a;let t=s(e,r);return t&&t!==e?t:a}function xr(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=xr(i),o=n.field?br(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 yr,oe=w(()=>{"use strict";R();yr={name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"}});var N,ut=w(()=>{"use strict";P();U();N=class extends A{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return l` + + `}};N.styles=S` + :host { display: block; } + .field { + display: flex; + flex-direction: column; + gap: 4px; + } + .label { + font-size: 12px; + color: var(--secondary-text-color, #888); + font-weight: 500; + } + .req { color: var(--error-color, #f44336); margin-left: 2px; } + input { + padding: 8px 10px; + font-size: 14px; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, rgba(255,255,255,0.12)); + border-radius: 6px; + font-family: inherit; + width: 100%; + box-sizing: border-box; + outline: none; + } + input:focus { + border-color: var(--primary-color); + } + input:disabled { opacity: 0.5; cursor: not-allowed; } + .helper { + font-size: 11px; + color: var(--secondary-text-color); + font-style: italic; + } + `,d([x()],N.prototype,"label",2),d([x()],N.prototype,"value",2),d([x()],N.prototype,"placeholder",2),d([x()],N.prototype,"type",2),d([x({type:Boolean})],N.prototype,"required",2),d([x({type:Boolean})],N.prototype,"disabled",2),d([x()],N.prototype,"step",2),d([x()],N.prototype,"min",2),d([x()],N.prototype,"max",2),d([x()],N.prototype,"pattern",2),d([x()],N.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",N)});var T,_i=w(()=>{"use strict";P();U();R();oe();ut();T=class extends A{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return H(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(e,t){this._entryId=e,this._name=t.name||"",this._manufacturer=t.manufacturer||"",this._model=t.model||"",this._serialNumber=t.serial_number||"",this._areaId=t.area_id||"",this._installationDate=t.installation_date||"",this._warrantyExpiry=t.warranty_expiry||"",this._documentationUrl=t.documentation_url||"",this._notes=t.notes||"",this._haDeviceId=t.ha_device_id||"",this._parentEntryId=t.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=j(e,this._lang,s("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this._lang,t=this._entryId?s("edit_object",e):s("new_object",e);return l` + +
${t}
+
+ ${this._error?l`
${this._error}
`:h} + this._name=i.target.value} + > + this._manufacturer=i.target.value} + > + this._model=i.target.value} + > + this._serialNumber=i.target.value} + > + this._documentationUrl=i.target.value} + > + this._areaId=i.detail.value||""} + > + this._installationDate=i.target.value} + > + this._warrantyExpiry=i.target.value} + > + s("link_device_optional",e)} + @value-changed=${i=>this._haDeviceId=i.detail.value?.device||""} + > + ${this._parentChoices().length?l``:h} +
- `},at=i=>{let[C,X,P]=i.date.split("-").map(Number),H=new Date(C,X-1,P),I=i.date===R,ie=H.toLocaleDateString(t,{weekday:"short"}),le=H.toLocaleDateString(t,{month:"long"});return m` +
+ + ${s("cancel",this._lang)} + + + ${this._loading?s("saving",this._lang):s("save",this._lang)} + +
+
+ `}};T.styles=S` + .dialog-title { + font-size: 18px; + font-weight: 500; + padding-bottom: 12px; + } + .content { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 300px; + } + .dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + } + ms-textfield { + display: block; + } + .textarea-field { + display: flex; flex-direction: column; gap: 4px; + } + .textarea-label { + font-size: 12px; color: var(--secondary-text-color, #888); font-weight: 500; + } + .textarea-field textarea { + padding: 8px 10px; font-size: 14px; font-family: inherit; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + resize: vertical; + } + .textarea-field textarea:focus { + outline: none; border-color: var(--primary-color); + } + .parent-select { + padding: 8px 10px; font-size: 14px; font-family: inherit; + background: var(--secondary-background-color, rgba(0,0,0,0.06)); + color: var(--primary-text-color); + border: 1px solid var(--divider-color); border-radius: 6px; + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + `,d([x({attribute:!1})],T.prototype,"hass",2),d([x({attribute:!1})],T.prototype,"objects",2),d([u()],T.prototype,"_open",2),d([u()],T.prototype,"_loading",2),d([u()],T.prototype,"_error",2),d([u()],T.prototype,"_name",2),d([u()],T.prototype,"_manufacturer",2),d([u()],T.prototype,"_model",2),d([u()],T.prototype,"_serialNumber",2),d([u()],T.prototype,"_areaId",2),d([u()],T.prototype,"_installationDate",2),d([u()],T.prototype,"_warrantyExpiry",2),d([u()],T.prototype,"_documentationUrl",2),d([u()],T.prototype,"_notes",2),d([u()],T.prototype,"_haDeviceId",2),d([u()],T.prototype,"_parentEntryId",2),d([u()],T.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",T)});var Ke,hi=w(()=>{"use strict";Ke=class{constructor(r){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=r}updateHass(r){this.hass=r}async getUsers(r=!1){let e=Date.now();if(!r&&this.usersCache&&e-this.cacheTimestampt.id===r)?.name||null}getUser(r){return!r||!this.usersCache?null:this.usersCache.find(e=>e.id===r)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(r){return r?r===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}}});function ee(a){return`${a.entry_id??""}\0${a.part_id}`}var _t=w(()=>{"use strict";R()});var ht,gi,mi,fi=w(()=>{"use strict";ht=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock"],gi=["sensor"],mi=["temperature","humidity","pressure"]});var vi,Ge,gt=w(()=>{"use strict";vi=["notes","cost","duration","photo","user"],Ge={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"}});function Sr(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",carry:{}}}function Tr(a){return{entityIds:(a.entity_ids||(a.entity_id?[a.entity_id]:[])).join(", "),type:a.type||"threshold",attribute:a.attribute||"",above:a.trigger_above?.toString()??"",below:a.trigger_below?.toString()??"",equals:a.trigger_equals?.toString()??"",notEquals:a.trigger_not_equals?.toString()??"",forMinutes:a.trigger_for_minutes?.toString()??"0",targetValue:a.trigger_target_value?.toString()??"",deltaMode:a.trigger_delta_mode||!1,fromState:a.trigger_from_state||"",toState:a.trigger_to_state||"",targetChanges:a.trigger_target_changes?.toString()??"",runtimeHours:a.trigger_runtime_hours?.toString()??"",onStates:(a.trigger_on_states||[]).join(", "),carry:Object.fromEntries(Object.entries(a).filter(([e])=>!Ar.has(e)&&!e.startsWith("_")))}}function Cr(a){let r=a.entityIds.split(",").map(t=>t.trim()).filter(Boolean);if(r.length===0)return null;let e={...a.carry||{},entity_id:r[0],entity_ids:r,type:a.type};if(a.attribute&&(e.attribute=a.attribute),a.type==="threshold"){let t=parseFloat(a.above);isNaN(t)||(e.trigger_above=t);let i=parseFloat(a.below);isNaN(i)||(e.trigger_below=i);let n=parseFloat(a.equals);isNaN(n)||(e.trigger_equals=n);let o=parseFloat(a.notEquals);isNaN(o)||(e.trigger_not_equals=o);let p=parseInt(a.forMinutes,10);isNaN(p)||(e.trigger_for_minutes=p)}else if(a.type==="counter"){let t=parseFloat(a.targetValue);isNaN(t)||(e.trigger_target_value=t),e.trigger_delta_mode=a.deltaMode}else if(a.type==="state_change"){a.fromState&&(e.trigger_from_state=a.fromState),a.toState&&(e.trigger_to_state=a.toState);let t=parseInt(a.targetChanges,10);isNaN(t)||(e.trigger_target_changes=t)}else if(a.type==="runtime"){let t=parseFloat(a.runtimeHours);isNaN(t)||(e.trigger_runtime_hours=t);let i=(a.onStates||"").split(",").map(n=>n.trim()).filter(Boolean);i.length>0&&(e.trigger_on_states=i)}return e}function Ir(a){return Array.from({length:7},(r,e)=>Pe(e,a,"short"))}function Lr(a){let r=new Intl.DateTimeFormat(a||"en",{month:"short"});return Array.from({length:12},(e,t)=>r.format(new Date(2021,t,1)))}var wr,$r,kr,mt,yi,Er,Y,Ar,g,ft,bi=w(()=>{"use strict";P();U();R();hi();_t();fi();oe();gt();ut();wr=["cleaning","inspection","replacement","calibration","service","reading","custom"],$r=["low","normal","high"],kr=["time_based","weekdays","nth_weekday","day_of_month","sensor_based","one_time","manual"],mt=["weekdays","nth_weekday","day_of_month"],yi=["threshold","counter","state_change","runtime"],Er=[...yi,"compound"],Y={alpha:"0.3",min:"7",max:"365"};Ar=new Set(["entity_id","entity_ids","type","attribute","trigger_above","trigger_below","trigger_equals","trigger_not_equals","trigger_for_minutes","trigger_target_value","trigger_delta_mode","trigger_from_state","trigger_to_state","trigger_target_changes","trigger_runtime_hours","trigger_on_states"]);g=class g extends A{constructor(){super(...arguments);this.checklistsEnabled=!1;this.scheduleTimeEnabled=!1;this.completionActionsEnabled=!1;this.defaultWarningDays=7;this.parts=[];this._foreignOwners=[];this._open=!1;this._entityPickerFallback=!1;this._pickerProbeStrikes=0;this._loading=!1;this._error="";this._entryId="";this._taskId=null;this._objectChoices=[];this._name="";this._type="custom";this._scheduleType="time_based";this._intervalDays="30";this._intervalUnit="days";this._dueDate="";this._warningDays="7";this._earliestCompletionDays="";this._intervalAnchor="completion";this._weekdays=[];this._nth="1";this._nthWeekday="5";this._domDay="1";this._domLastDay=!1;this._domBusiness=!1;this._calOffset="0";this._seasonMonths=[];this._endsMode="never";this._endsCount="";this._endsUntil="";this._schedulePreview=[];this._schedulePreviewEnded=!1;this._previewSeq=0;this._notes="";this._documentationUrl="";this._customIcon="";this._priority="normal";this._labels="";this._enabled=!0;this._triggerEntityId="";this._triggerEntityIds=[];this._triggerEntityLogic="any";this._triggerAttribute="";this._triggerType="threshold";this._triggerAbove="";this._triggerBelow="";this._triggerEquals="";this._triggerNotEquals="";this._triggerForMinutes="0";this._triggerCombinator="any";this._triggerTargetValue="";this._triggerDeltaMode=!1;this._triggerBaselineValue="";this._liveBaselineValue=null;this._autoCompleteOnRecovery=!1;this._triggerFromState="";this._triggerToState="";this._triggerTargetChanges="";this._triggerRuntimeHours="";this._triggerOnStates="";this._compoundLogic="AND";this._compoundConditions=[];this._suggestedAttributes=[];this._availableAttributes=[];this._entityDomain="";this._lastPerformed="";this._nfcTagId="";this._readingUnit="";this._consumesParts={};this._partsLoadFailed=!1;this._availableTags=[];this._responsibleUserId=null;this._assigneePool=[];this._rotationStrategy="";this._availableUsers=[];this._checklistText="";this._requiredCompletion=[];this._scheduleTime="";this._actionService="";this._actionTargetEntity="";this._actionData={};this._actionDataJsonFallback="";this._actionTesting=!1;this._actionTestResult="";this._actionTestError="";this._qcNotes="";this._qcCost="";this._qcDuration="";this._qcFeedback="";this._environmentalEntity="";this._environmentalAttribute="";this._environmentalInitial="";this._environmentalAttributeInitial="";this._adaptiveEnabled=!1;this._adaptiveAlpha=Y.alpha;this._adaptiveMin=Y.min;this._adaptiveMax=Y.max;this._adaptiveSeasonal=!0;this._adaptivePrediction=!0;this._adaptiveInitial="";this._userService=null;this._conditionAttrOptions={};this._conditionAttrPending=new Set}_adaptiveSnapshot(){return JSON.stringify([this._adaptiveEnabled,this._adaptiveAlpha,this._adaptiveMin,this._adaptiveMax,this._adaptiveSeasonal,this._adaptivePrediction])}get _lang(){return H(this.hass)}async openCreate(e,t){this._entryId=e,this._taskId=null,this._error="",!e&&t&&t.length>0?(this._objectChoices=t.map(i=>({entry_id:i.entry_id,name:i.object.name})).sort((i,n)=>i.name.localeCompare(n.name)),this._entryId=this._objectChoices[0].entry_id):this._objectChoices=[],this._resetFields(),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}async openEdit(e,t){this._entryId=e,this._taskId=t.id,this._error="",this._name=t.name,this._type=t.type,this._scheduleType=t.schedule_type,this._intervalDays=t.interval_days!=null?String(t.interval_days):"",this._intervalUnit=t.interval_unit||"days",this._dueDate=t.due_date||"";let i=t.schedule;this._weekdays=i?.kind==="weekdays"?[...i.weekdays??[]]:[],this._nth=i?.kind==="nth_weekday"?String(i.nth??1):"1",this._nthWeekday=i?.kind==="nth_weekday"?String(i.weekday??5):"5",this._domDay=i?.kind==="day_of_month"&&(i.day??1)>=1?String(i.day??1):"1",this._domLastDay=i?.kind==="day_of_month"&&i.day===-1,this._domBusiness=i?.kind==="day_of_month"&&i.business===!0,this._calOffset=i?.offset?String(i.offset):"0",this._seasonMonths=Array.isArray(i?.season_months)?[...i.season_months]:[];let n=i?.ends;n&&typeof n.count=="number"?(this._endsMode="count",this._endsCount=String(n.count),this._endsUntil=""):n&&typeof n.until=="string"?(this._endsMode="until",this._endsUntil=n.until,this._endsCount=""):(this._endsMode="never",this._endsCount="",this._endsUntil=""),this._warningDays=t.warning_days.toString(),this._earliestCompletionDays=t.earliest_completion_days!=null?String(t.earliest_completion_days):"",this._intervalAnchor=t.interval_anchor||"completion",this._notes=t.notes||"",this._documentationUrl=t.documentation_url||"",this._customIcon=t.custom_icon||"",this._priority=t.priority||"normal",this._labels=(t.labels||[]).join(", "),this._enabled=t.enabled!==!1,this._lastPerformed=t.last_performed||"",this._nfcTagId=t.nfc_tag_id||"",this._readingUnit=t.reading_unit||"",this._consumesParts=Object.fromEntries((t.consumes_parts||[]).map(_=>[ee(_),{..._}])),this._responsibleUserId=t.responsible_user_id||null,this._assigneePool=[...t.assignee_pool||[]],this._rotationStrategy=t.rotation_strategy||"",this._checklistText=(t.checklist||[]).join(` +`),this._requiredCompletion=[...t.required_completion_fields||[]],this._scheduleTime=t.schedule_time||"";let o=t.on_complete_action;if(o&&o.service){this._actionService=o.service;let _=o.target?.entity_id;this._actionTargetEntity=Array.isArray(_)?_[0]||"":_||"",this._actionData=o.data&&typeof o.data=="object"?{...o.data}:{},this._actionDataJsonFallback=""}else this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="";let p=t.quick_complete_defaults;this._qcNotes=p?.notes||"",this._qcCost=p?.cost!=null?String(p.cost):"",this._qcDuration=p?.duration!=null?String(p.duration):"",this._qcFeedback=p?.feedback||"";let c=t.adaptive_config||{};if(this._environmentalEntity=c.environmental_entity||"",this._environmentalAttribute=c.environmental_attribute||"",this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute,this._adaptiveEnabled=!!c.enabled,this._adaptiveAlpha=c.ewa_alpha?.toString()??Y.alpha,this._adaptiveMin=c.min_interval_days?.toString()??Y.min,this._adaptiveMax=c.max_interval_days?.toString()??Y.max,this._adaptiveSeasonal=c.seasonal_enabled!==!1,this._adaptivePrediction=c.sensor_prediction_enabled!==!1,this._adaptiveInitial=this._adaptiveSnapshot(),t.trigger_config){let _=t.trigger_config;this._triggerEntityId=_.entity_id||_.entity_ids&&_.entity_ids[0]||"",this._triggerEntityIds=_.entity_ids||(_.entity_id?[_.entity_id]:[]),this._triggerEntityLogic=_.entity_logic||"any",this._triggerAttribute=_.attribute||"",this._triggerType=_.type||"threshold",this._triggerAbove=_.trigger_above?.toString()||"",this._triggerBelow=_.trigger_below?.toString()||"",this._triggerEquals=_.trigger_equals?.toString()||"",this._triggerNotEquals=_.trigger_not_equals?.toString()||"",this._triggerForMinutes=_.trigger_for_minutes?.toString()||"0",this._triggerCombinator=_.trigger_combinator==="all"?"all":"any",this._triggerTargetValue=_.trigger_target_value?.toString()||"",this._triggerDeltaMode=_.trigger_delta_mode||!1,this._triggerBaselineValue=_.trigger_baseline_value?.toString()||"",this._liveBaselineValue=t.trigger_baseline_value??null,this._autoCompleteOnRecovery=_.auto_complete_on_recovery||!1,this._triggerFromState=_.trigger_from_state||"",this._triggerToState=_.trigger_to_state||"",this._triggerTargetChanges=_.trigger_target_changes?.toString()||"",this._triggerRuntimeHours=_.trigger_runtime_hours?.toString()||"",this._triggerOnStates=(_.trigger_on_states||[]).join(", "),_.type==="compound"?(this._compoundLogic=_.compound_logic==="OR"?"OR":"AND",this._compoundConditions=(_.conditions||[]).map(Tr)):(this._compoundLogic="AND",this._compoundConditions=[])}else this._resetTriggerFields();this._triggerEntityId&&this._fetchEntityAttributes(this._triggerEntityId),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}_resetFields(){this._name="",this._type="custom",this._scheduleType="time_based",this._intervalDays="30",this._intervalUnit="days",this._dueDate="",this._warningDays=String(this.defaultWarningDays),this._earliestCompletionDays="",this._intervalAnchor="completion",this._weekdays=[],this._nth="1",this._nthWeekday="5",this._domDay="1",this._domLastDay=!1,this._domBusiness=!1,this._calOffset="0",this._seasonMonths=[],this._endsMode="never",this._endsCount="",this._endsUntil="",this._notes="",this._documentationUrl="",this._customIcon="",this._priority="normal",this._labels="",this._enabled=!0,this._lastPerformed="",this._nfcTagId="",this._readingUnit="",this._consumesParts={},this._responsibleUserId=null,this._assigneePool=[],this._rotationStrategy="",this._checklistText="",this._requiredCompletion=[],this._scheduleTime="",this._environmentalEntity="",this._environmentalAttribute="",this._environmentalInitial="",this._environmentalAttributeInitial="",this._adaptiveEnabled=!1,this._adaptiveAlpha=Y.alpha,this._adaptiveMin=Y.min,this._adaptiveMax=Y.max,this._adaptiveSeasonal=!0,this._adaptivePrediction=!0,this._adaptiveInitial=this._adaptiveSnapshot(),this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="",this._actionTesting=!1,this._actionTestResult="",this._qcNotes="",this._qcCost="",this._qcDuration="",this._qcFeedback="",this._resetTriggerFields()}_resetTriggerFields(){this._triggerEntityId="",this._triggerEntityIds=[],this._triggerEntityLogic="any",this._triggerAttribute="",this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="",this._triggerType="threshold",this._triggerAbove="",this._triggerBelow="",this._triggerEquals="",this._triggerNotEquals="",this._triggerForMinutes="0",this._triggerCombinator="any",this._triggerTargetValue="",this._triggerDeltaMode=!1,this._triggerBaselineValue="",this._liveBaselineValue=null,this._autoCompleteOnRecovery=!1,this._triggerFromState="",this._triggerToState="",this._triggerTargetChanges="",this._triggerRuntimeHours="",this._triggerOnStates="",this._compoundLogic="AND",this._compoundConditions=[]}async _loadUsers(){this._userService||(this._userService=new Ke(this.hass));try{this._availableUsers=await this._userService.getUsers()}catch(e){console.error("Failed to load users:",e),this._availableUsers=[]}}_toggleAssignee(e){this._assigneePool=this._assigneePool.includes(e)?this._assigneePool.filter(t=>t!==e):[...this._assigneePool,e]}async _testAction(){let e=this._actionService.trim();if(!e||!/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(e)){this._actionTestResult="error",this._actionTestError="Invalid service format (expected 'domain.service')",setTimeout(()=>{this._actionTestResult="",this._actionTestError=""},5e3);return}let[t,i]=e.split(".");if(!this.hass?.services?.[t]?.[i]){this._actionTestResult="error",this._actionTestError=`Service "${e}" is not registered in Home Assistant. Check spelling and that the integration providing it is loaded.`,setTimeout(()=>{this._actionTestResult="",this._actionTestError=""},8e3);return}let n=this._actionTargetEntity.trim();if(n){let o=n.split(".")[0];if(o!==t&&!new Set(["homeassistant","scene","notify","persistent_notification"]).has(t)){this._actionTestResult="error",this._actionTestError=`Service "${e}" only works on ${t}.* entities; entity "${n}" is in ${o}.* \u2014 pick a service that matches the entity domain (e.g. ${o}.${i})`,setTimeout(()=>{this._actionTestResult="",this._actionTestError=""},8e3);return}if(!this.hass.states?.[n]){this._actionTestResult="error",this._actionTestError=`Target entity "${n}" not found in Home Assistant \u2014 the entity may have been renamed or its integration removed.`,setTimeout(()=>{this._actionTestResult="",this._actionTestError=""},8e3);return}}this._actionTestResult="ok",setTimeout(()=>{this._actionTestResult="",this._actionTestError=""},5e3)}_buildActionData(){if(this._actionDataJsonFallback.trim())try{let e=JSON.parse(this._actionDataJsonFallback);if(e&&typeof e=="object"&&!Array.isArray(e))return e}catch{}return{...this._actionData}}_serviceSchema(){let e=this._actionService.trim();if(!e||!/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(e))return null;let[t,i]=e.split("."),n=this.hass?.services?.[t]?.[i]?.fields;return!n||Object.keys(n).length===0?null:Object.entries(n).map(([o,p])=>({name:o,required:!!p.required,selector:p.selector||{text:{}}}))}_renderCompletionActionsSection(e){if(!this.completionActionsEnabled)return h;let t=this._serviceSchema();return l` +
+ ${s("on_complete_action_title",e)} +

${s("on_complete_action_desc",e)}

+ {this._actionService=i.detail.value||"";let n=this._serviceSchema();if(n){let o=new Set(n.map(p=>p.name));this._actionData=Object.fromEntries(Object.entries(this._actionData).filter(([p])=>o.has(p)))}}} + > + s("on_complete_action_target",e)} + @value-changed=${i=>{let n=i.detail.value;this._actionTargetEntity=n.target_entity||""}} + > +

+ ${s("on_complete_action_target_hint",e)} +

+ ${t?l` + {this._actionData={...i.detail.value}}} + > + `:l` + {this._actionDataJsonFallback=i.target.value}} + > + `} +
+ + ${this._actionTestResult==="ok"?l`${s("on_complete_action_test_success",e)}`:h} + ${this._actionTestResult==="error"?l`
+ ${s("on_complete_action_test_failed",e)} + ${this._actionTestError?l`
${this._actionTestError}
`:h} +
`:h} +
+
+ +
+ ${s("quick_complete_defaults_title",e)} +

${s("quick_complete_defaults_desc",e)}

+ {this._qcNotes=i.target.value}} + > + {this._qcCost=i.target.value}} + > + {this._qcDuration=i.target.value}} + > + +
+ `}async _loadParts(){if(this.parts=[],!!this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this.parts=e.parts||[],this._partsLoadFailed=!1}catch{this.parts=[],this._partsLoadFailed=!0}}async _loadForeignPools(){if(this._foreignOwners=[],!!this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._foreignOwners=(e.objects||[]).filter(t=>t.entry_id!==this._entryId&&(t.parts||[]).length>0).map(t=>({entry_id:t.entry_id,name:t.object?.name||t.entry_id,parts:t.parts||[]})).sort((t,i)=>t.name.localeCompare(i.name))}catch{this._foreignOwners=[]}}async _loadTags(){try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tags/list"});this._availableTags=e.tags||[]}catch{this._availableTags=[]}}_fetchConditionAttributes(e){!e||!this.hass||this._conditionAttrOptions[e]||this._conditionAttrPending.has(e)||(this._conditionAttrPending.add(e),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/entity/attributes",entity_id:e}).then(t=>{let i=t;this._conditionAttrOptions={...this._conditionAttrOptions,[e]:{suggested:i.suggested_attributes||[],available:i.available_attributes||[]}}}).catch(()=>{this._conditionAttrOptions={...this._conditionAttrOptions,[e]:{suggested:[],available:[]}}}))}async _fetchEntityAttributes(e){if(!e||!this.hass){this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="";return}try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/entity/attributes",entity_id:e});this._entityDomain=t.domain||"",this._suggestedAttributes=t.suggested_attributes||[],this._availableAttributes=t.available_attributes||[]}catch{this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain=""}}get _hasForeignPick(){return Object.values(this._consumesParts).some(e=>!!e.entry_id)}_renderConsumesRow(e,t){let i=ee({part_id:e.id,entry_id:t}),n=this._consumesParts[i],o=t?{part_id:e.id,quantity:1,entry_id:t}:{part_id:e.id,quantity:1};return l` +
+ + ${n!==void 0?l`{let c=parseFloat(p.target.value);this._consumesParts={...this._consumesParts,[i]:{...o,quantity:Number.isFinite(c)&&c>=.01?c:1}}}} + />`:h} +
+ `}_toggleRequired(e,t){let i=new Set(this._requiredCompletion);t?i.add(e):i.delete(e),this._requiredCompletion=[...i]}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${s("adaptive_min_interval",this._lang)} > ${s("adaptive_max_interval",this._lang)}`;return}}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();if(e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):mt.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(p=>p.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.reading_unit=this._readingUnit.trim()||null,(this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(p=>p.entry_id?{part_id:p.part_id,quantity:p.quantity,entry_id:p.entry_id}:{part_id:p.part_id,quantity:p.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let p=this._compoundConditions.map(Cr).filter(c=>c!==null);if(p.length>0){let c={type:"compound",compound_logic:this._compoundLogic,conditions:p};this._autoCompleteOnRecovery&&(c.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(c.trigger_combinator="all"),e.trigger_config=c}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let p=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],c={entity_id:p[0],entity_ids:p,type:this._triggerType};if(this._triggerAttribute&&(c.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(c.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(c.trigger_combinator="all"),p.length>1&&(c.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let _=parseFloat(this._triggerAbove);isNaN(_)||(c.trigger_above=_)}if(this._triggerBelow){let _=parseFloat(this._triggerBelow);isNaN(_)||(c.trigger_below=_)}if(this._triggerEquals){let _=parseFloat(this._triggerEquals);isNaN(_)||(c.trigger_equals=_)}if(this._triggerNotEquals){let _=parseFloat(this._triggerNotEquals);isNaN(_)||(c.trigger_not_equals=_)}if(this._triggerForMinutes){let _=parseInt(this._triggerForMinutes,10);isNaN(_)||(c.trigger_for_minutes=_)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let _=parseFloat(this._triggerTargetValue);isNaN(_)||(c.trigger_target_value=_)}if(c.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let _=parseFloat(this._triggerBaselineValue);!isNaN(_)&&_>=0&&(c.trigger_baseline_value=_)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(c.trigger_from_state=this._triggerFromState),this._triggerToState&&(c.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let _=parseInt(this._triggerTargetChanges,10);isNaN(_)||(c.trigger_target_changes=_)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let f=parseFloat(this._triggerRuntimeHours);isNaN(f)||(c.trigger_runtime_hours=f)}let _=this._triggerOnStates.split(",").map(f=>f.trim()).filter(Boolean);_.length>0&&(c.trigger_on_states=_)}e.trigger_config=c}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&this._scheduleType==="time_based"){let p=this._scheduleTime.trim();e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(p)?p:null}if(this.checklistsEnabled){let p=this._checklistText.split(` +`).map(c=>c.trim()).filter(Boolean).slice(0,100);e.checklist=p.length?p:null}if(this.completionActionsEnabled){let p=this._actionService.trim();if(p&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(p)){let m={service:p},y=this._actionTargetEntity.trim();y&&(m.target={entity_id:y});let b=this._buildActionData();Object.keys(b).length>0&&(m.data=b),e.on_complete_action=m}else e.on_complete_action=null;let c={};this._qcNotes.trim()&&(c.notes=this._qcNotes.trim());let _=parseFloat(this._qcCost);!isNaN(_)&&_>=0&&(c.cost=_);let f=parseInt(this._qcDuration,10);!isNaN(f)&&f>=0&&(c.duration=f),this._qcFeedback&&(c.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(c).length?c:null}let i=await this.hass.connection.sendMessagePromise(e),n=this._taskId||i?.task_id,o=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(n&&this._scheduleType==="sensor_based"&&o)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:n,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null}),this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute}catch{}if(n&&this._adaptiveSnapshot()!==this._adaptiveInitial){let p=parseFloat(this._adaptiveAlpha),c=parseInt(this._adaptiveMin,10),_=parseInt(this._adaptiveMax,10);try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:n,enabled:this._adaptiveEnabled,...p>=.1&&p<=.9?{ewa_alpha:p}:{},...!isNaN(c)&&c>=1?{min_interval_days:c}:{},...!isNaN(_)&&_>=1?{max_interval_days:_}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction}),this._adaptiveInitial=this._adaptiveSnapshot()}catch{}}this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=j(e,this._lang,s("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return h;let e=this._lang,t=this._triggerType==="compound";return l` +

${s("trigger_configuration",e)}

+
+ + +
+ ${t?this._renderCompoundEditor():l` + ${this._entityPickerFallback?l` + 0?this._triggerEntityIds.join(", "):this._triggerEntityId} + @input=${i=>{let o=i.target.value.split(",").map(p=>p.trim()).filter(Boolean);this._triggerEntityId=o[0]||"",this._triggerEntityIds=o,o[0]&&this._fetchEntityAttributes(o[0])}} + > + `:l` + 0?this._triggerEntityIds:this._triggerEntityId?[this._triggerEntityId]:[]}} + .computeLabel=${()=>s("entity_id",e)} + @value-changed=${i=>{let n=(i.detail.value.trigger_entities||[]).filter(Boolean);this._triggerEntityId=n[0]||"",this._triggerEntityIds=n,n[0]?this._fetchEntityAttributes(n[0]):this._fetchEntityAttributes("")}} + >`} + ${this._triggerEntityIds.length>1?l` +
+ + +
+ `:h} + ${this._renderAttributeSelect({label:s("attribute_optional",e),value:this._triggerAttribute,suggested:this._suggestedAttributes,available:this._availableAttributes,onSelect:i=>this._triggerAttribute=i})} + ${this._renderTriggerTypeFields()} + ${this._renderTriggerLiveHint()} + `} + +
${s("auto_complete_on_recovery_help",e)}
+ this._intervalDays=i.target.value} + > + ${this._intervalDays?this._renderUnitSelect():h} + ${this._intervalDays?l` +
+ + +
+ `:h} + `}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Sr()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l` +
+ + +
+
${s("compound_help",e)}
+ ${this._compoundConditions.length===0?l`
${s("compound_no_conditions",e)}
`:this._compoundConditions.map((t,i)=>this._renderCondition(t,i))} + + `}_renderCondition(e,t){let i=this._lang,n=t+1;return l` +
+
+ ${s("compound_condition",i)} ${n} + +
+ ${this._entityPickerFallback?l` + this._patchCondition(t,{entityIds:o.target.value})} + > + `:l` + o.trim()).filter(Boolean)}} + .computeLabel=${()=>s("entity_id",i)} + @value-changed=${o=>{let p=(o.detail.value.condition_entities||[]).filter(Boolean);this._patchCondition(t,{entityIds:p.join(", ")})}} + >`} + ${this._renderConditionAttribute(e,t)} +
+ + +
+ ${this._renderConditionTypeFields(e,t)} +
+ `}_renderStateField(e){return this._entityPickerFallback||!e.entityId?l` + e.onInput(t.target.value)} + > + `:l` + e.label} + @value-changed=${t=>e.onInput((t.detail.value.s||"").trim())} + > + `}_renderOnStatesField(e){let t=this._lang;return this._entityPickerFallback||!e.entityId?l` + e.onInput(i.target.value)} + > + `:l` + i.trim()).filter(Boolean)}} + .computeLabel=${()=>s("runtime_on_states",t)} + @value-changed=${i=>e.onInput((i.detail.value.s||[]).join(", "))} + > + `}_renderAdaptiveSection(e){return this._scheduleType==="one_time"||this._scheduleType==="manual"?h:l` +
+ ${s("adaptive_section_title",e)} + + ${this._adaptiveEnabled?l` + this._adaptiveMin=t.target.value} + > + this._adaptiveMax=t.target.value} + > + this._adaptiveAlpha=t.target.value} + > + + + `:h} +
+ `}_renderAttributeSelect(e){let t=this._lang;return e.available.length>0?l` +
+ + +
+ `:l` + e.onSelect(i.target.value.trim())} + > + `}_renderEnvironmentalAttribute(e){this._fetchConditionAttributes(this._environmentalEntity);let t=this._conditionAttrOptions[this._environmentalEntity];return this._renderAttributeSelect({label:s("environmental_attribute_optional",e),value:this._environmentalAttribute,suggested:t?.suggested??[],available:t?.available??[],onSelect:i=>this._environmentalAttribute=i})}_renderConditionAttribute(e,t){let i=e.entityIds.split(",")[0]?.trim()||"";i&&this._fetchConditionAttributes(i);let n=i?this._conditionAttrOptions[i]:void 0;return this._renderAttributeSelect({label:s("attribute_optional",this._lang),value:e.attribute,suggested:n?.suggested??[],available:n?.available??[],onSelect:o=>this._patchCondition(t,{attribute:o})})}_renderConditionTypeFields(e,t){let i=this._lang;if(e.type==="threshold")return l` + this._patchCondition(t,{above:n.target.value})}> + this._patchCondition(t,{below:n.target.value})}> + this._patchCondition(t,{equals:n.target.value})}> + this._patchCondition(t,{notEquals:n.target.value})}> + this._patchCondition(t,{forMinutes:n.target.value})}> + `;if(e.type==="counter")return l` + this._patchCondition(t,{targetValue:n.target.value})}> + + `;if(e.type==="state_change"){let n=e.entityIds.split(",")[0]?.trim()||"";return l` + ${this._renderStateField({label:s("from_state_optional",i),value:e.fromState,entityId:n,onInput:o=>this._patchCondition(t,{fromState:o})})} + ${this._renderStateField({label:s("to_state_optional",i),value:e.toState,entityId:n,onInput:o=>this._patchCondition(t,{toState:o})})} + this._patchCondition(t,{targetChanges:o.target.value})}> + `}if(e.type==="runtime"){let n=e.entityIds.split(",")[0]?.trim()||"";return l` + this._patchCondition(t,{runtimeHours:o.target.value})}> + ${this._renderOnStatesField({value:e.onStates,entityId:n,onInput:o=>this._patchCondition(t,{onStates:o})})} + `}return h}_renderUnitSelect(){let e=this._lang;return l` +
+ + +
`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(mt.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(g._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(c,_,f=0)=>{if(!(!c||f>10)){(c.tagName?.toLowerCase()??"")==="ha-entity-picker"&&_.push(c);for(let m of[c.shadowRoot,c])if(m)for(let y of Array.from(m.children??[]))i(y,_,f+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let c of n)i(c,o);let p=o.length===0||o.some(c=>c.offsetHeight===0);if(e.offsetHeight===0||p){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return h;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,p)=>{let c=new Date(`${o}T12:00:00`).getDay();return`${Pe(c===0?6:c-1,e,"short")} ${G(o,e)}${p===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`
${s("schedule_preview_ontime",e)}
`:h;return l` +
+ ${s("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?l` ${s("schedule_preview_ends",e)}`:h} + ${n} +
+ `}_buildSchedule(){let e=i=>{let n=parseInt(this._calOffset,10)||0;return n&&(i.offset=Math.max(-15,Math.min(n,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,n)=>i-n)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||mt.includes(this._scheduleType)))return h;let i=Lr(e);return l` + +
${s("season_window_hint",e)}
+
+ ${i.map((n,o)=>l` + `)} +
+ + +
+ +
+ ${this._endsMode==="count"?l` + this._endsCount=n.target.value} + >`:h} + ${this._endsMode==="until"?l` + this._endsUntil=n.target.value} + >`:h} + `}_renderCalendarFields(){let e=this._lang,t=Ir(e);if(this._scheduleType==="weekdays")return l` + +
+ ${t.map((i,n)=>l` + `)} +
+ ${this._renderCalOffsetField()}`;if(this._scheduleType==="nth_weekday"){let i=[["1",s("ord_1",e)],["2",s("ord_2",e)],["3",s("ord_3",e)],["4",s("ord_4",e)],["5",s("ord_5",e)],["-1",s("ord_last",e)]];return l` +
+ + +
+
+ + +
+ ${this._renderCalOffsetField()}`}return this._scheduleType==="day_of_month"?l` + ${this._domLastDay?h:l` + this._domDay=i.target.value} + >`} + + + ${this._renderCalOffsetField()}`:h}_renderCalOffsetField(){let e=this._lang;return l` + this._calOffset=t.target.value} + >`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return h;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return h;let t=this.hass.states[e];if(!t)return h;let i=this._lang,n=t.attributes?.unit_of_measurement,o=typeof n=="string"&&n?` ${n}`:"",p=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,c=typeof p=="number"?p:parseFloat(String(p)),_=p!=="unknown"&&p!=="unavailable"&&p!=null&&!isNaN(c),f=y=>Number.isInteger(y)?String(y):String(Math.round(y*10)/10),m=[];if(this._triggerType==="threshold"){let y=parseFloat(this._triggerAbove),b=parseFloat(this._triggerBelow);if(isNaN(y)&&isNaN(b))return h;_&&m.push(s("trigger_hint_now",i).replace("{value}",f(c)+o)),isNaN(y)||m.push(s("trigger_hint_above",i).replace("{target}",f(y)+o)),isNaN(b)||m.push(s("trigger_hint_below",i).replace("{target}",f(b)+o))}else if(this._triggerType==="counter"){let y=parseFloat(this._triggerTargetValue);if(isNaN(y))return h;this._triggerDeltaMode?this._taskId?m.push(s("trigger_hint_counter_delta_edit",i).replace("{target}",f(y)+o)):_?m.push(s("trigger_hint_counter_delta",i).replace("{value}",f(c)+o).replace("{due}",f(c+y)+o).replace("{target}",f(y)+o)):m.push(s("trigger_hint_counter_delta_edit",i).replace("{target}",f(y)+o)):(_&&m.push(s("trigger_hint_now",i).replace("{value}",f(c)+o)),m.push(s("trigger_hint_counter_abs",i).replace("{target}",f(y)+o)))}else if(this._triggerType==="runtime"){let y=parseFloat(this._triggerRuntimeHours);if(isNaN(y))return h;m.push(s("trigger_hint_runtime",i).replace("{hours}",f(y))),m.push(s("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let y=parseInt(this._triggerTargetChanges,10)||1,b=this._triggerToState.trim();m.push((b?s("trigger_hint_state_change_to",i).replace("{state}",b):s("trigger_hint_state_change",i)).replace("{count}",String(y))),m.push(s("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return m.length?l`
${m.join(" ")}
`:h}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l` + this._triggerAbove=t.target.value} + > + this._triggerBelow=t.target.value} + > + this._triggerEquals=t.target.value} + > + this._triggerNotEquals=t.target.value} + > + this._triggerForMinutes=t.target.value} + > + `:this._triggerType==="counter"?l` + this._triggerTargetValue=t.target.value} + > + + ${this._triggerDeltaMode?l` + this._triggerBaselineValue=t.target.value} + > +
+ ${this._taskId?s("baseline_start_help_edit",e):s("baseline_start_help",e)} + ${this._taskId&&this._liveBaselineValue!=null?l`
+ ${s("baseline_current_effective",e).replace("{value}",String(this._liveBaselineValue))} +
`:h} +
+ `:h} + `:this._triggerType==="state_change"?l` + ${this._renderStateField({label:s("from_state_optional",e),value:this._triggerFromState,entityId:this._triggerEntityId,onInput:t=>this._triggerFromState=t})} +
${s("state_value_help",e)}
+ ${this._renderStateField({label:s("to_state_optional",e),value:this._triggerToState,entityId:this._triggerEntityId,onInput:t=>this._triggerToState=t})} + this._triggerTargetChanges=t.target.value} + > +
${s("target_changes_help",e)}
+ `:this._triggerType==="runtime"?l` + this._triggerRuntimeHours=t.target.value} + > + ${this._renderOnStatesField({value:this._triggerOnStates,entityId:this._triggerEntityId,onInput:t=>this._triggerOnStates=t})} +
${s("runtime_on_states_help",e)}
+ `:h}render(){if(!this._open)return l``;let e=this._lang,t=this._taskId?s("edit_task",e):s("new_task",e);return l` + +
${t}
+
+ ${this._error?l`
${this._error}
`:h} + ${this._objectChoices.length>0?l` +
+ + +
+ `:h} + this._name=i.target.value} + > +
+ + +
+ ${this._type==="reading"?l` + this._readingUnit=i.target.value} + > +
${s("reading_unit_help",e)}
+ `:h} + ${this._partsLoadFailed?l`
${s("parts_load_failed",e)}
`:h} + ${this.parts.length||this._foreignOwners.length?l` +
+ + ${this.parts.map(i=>this._renderConsumesRow(i))} + ${this._foreignOwners.length?l` +
+ ${s("shared_parts_other_objects",e)} +
${s("shared_parts_help",e)}
+ ${this._foreignOwners.map(i=>l` +
${i.name}
+ ${i.parts.map(n=>this._renderConsumesRow(n,i.entry_id))} + `)} +
+ `:h} +
+ `:h} +
+ + +
+
+ + this._labels=i.target.value} + /> +
${s("labels_help",e)}
+
+
+ + +
+ ${this._scheduleType==="time_based"?l` + this._intervalDays=i.target.value} + > + ${this._renderUnitSelect()} +
+ + +
+ ${this.scheduleTimeEnabled?l` + this._scheduleTime=i.target.value} + > + `:h} + `:h} + ${this._renderCalendarFields()} + ${this._scheduleType==="one_time"?l` + this._dueDate=i.target.value} + > + `:h} + ${this._renderRecurrenceExtras()} + ${this._renderSchedulePreview()} + this._warningDays=i.target.value} + > + this._earliestCompletionDays=i.target.value} + > + ${this.checklistsEnabled?l` +

${s("checklist_steps_optional",e)}

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

${s("require_on_completion",e)}

+
+ ${vi.map(i=>l` + + `)} +
+ this._lastPerformed=i.target.value} + > +
+ + +
+ ${this._availableUsers.length>=2?l` +
+ +
${s("shared_with_help",e)}
+
+ ${this._availableUsers.map(i=>l` + `)} +
+
+ ${this._assigneePool.length>=2?l` +
+ + +
`:h} + `:h} + ${this._renderTriggerFields()} + ${this._scheduleType==="sensor_based"?l` + ${this._entityPickerFallback?l` + this._environmentalEntity=i.target.value.trim()} + > + `:l` + s("environmental_entity_optional",e)} + .computeHelper=${()=>s("environmental_entity_helper",e)} + @value-changed=${i=>{this._environmentalEntity=(i.detail.value.environmental_entity||"").trim()}} + >`} + ${this._environmentalEntity?this._renderEnvironmentalAttribute(e):h} + `:h} + ${this._renderAdaptiveSection(e)} + this._notes=i.target.value} + > + this._documentationUrl=i.target.value} + > + this._customIcon=i.detail.value||""} + > + ${this._availableTags.length>0?l` +
+ + + +
+ `:l` + this._nfcTagId=i.target.value} + > +
+ ${s("nfc_tags_empty_help",e)} + ${s("nfc_tags_open_settings",e)} + · + +
+ `} + + ${this._renderCompletionActionsSection(e)} +
+
+ ${s("cancel",e)} + + ${this._loading?s("saving",e):s("save",e)} + +
+
+ `}};g._PREVIEW_RELEVANT=new Set(["_open","_scheduleType","_intervalDays","_intervalUnit","_intervalAnchor","_dueDate","_weekdays","_nth","_nthWeekday","_domDay","_domLastDay","_domBusiness","_calOffset","_seasonMonths","_endsMode","_endsCount","_endsUntil","_lastPerformed"]),g.styles=S` + .dialog-title { + font-size: 18px; + font-weight: 500; + padding-bottom: 12px; + } + /* #129: entity/state pickers in the trigger form (ha-form + selector) */ + .entity-picker-form, + .state-picker-form { + display: block; + margin: 8px 0; + } + /* v1.3.0: completion-action sections (.adaptive-section shares the shell + but keeps its own class — tests count .ca-section elements) */ + .ca-section, + .adaptive-section { + border: 1px solid var(--divider-color); + border-radius: 6px; + padding: 8px 12px; + margin-top: 8px; + } + .ca-section > summary, + .adaptive-section > summary { + cursor: pointer; + font-weight: 500; + } + .adaptive-section ms-textfield { + width: 100%; + margin-top: 8px; + display: block; + } + .adaptive-section label { + display: block; + margin-top: 8px; + } + .ca-section ms-textfield, + .ca-section ha-entity-picker, + .ca-section ha-service-picker, + .ca-section ha-form, + .ca-section .qc-feedback { + width: 100%; + margin-top: 8px; + display: block; + } + .ca-section .qc-feedback { + padding: 8px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + } + .ca-test-row { + display: flex; + align-items: center; + gap: 12px; + margin-top: 8px; + } + .ca-test-ok { color: var(--success-color, #4caf50); font-size: 13px; } + .ca-test-error { color: var(--error-color, #f44336); font-size: 13px; font-weight: 500; } + .ca-test-error-block { display: flex; flex-direction: column; gap: 4px; flex: 1; min-width: 0; } + .ca-test-error-detail { + font-size: 12px; + color: var(--secondary-text-color); + background: rgba(244, 67, 54, 0.08); + padding: 6px 8px; border-radius: 4px; + line-height: 1.4; + word-break: break-word; + } + .content { + display: flex; + flex-direction: column; + gap: 12px; + min-width: 350px; + max-height: 70vh; + overflow-y: auto; + } + @media (max-width: 600px) { + .content { + min-width: 0; + max-height: none; + } + } + .dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + } + ms-textfield { + display: block; + } + .field-label { + font-size: 12px; + color: var(--secondary-text-color); + } + .checklist-textarea { + width: 100%; + min-height: 88px; + padding: 8px; + font-family: inherit; + font-size: 14px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + resize: vertical; + box-sizing: border-box; + } + .consumes-row { + display: flex; + align-items: center; + gap: 8px; + padding: 2px 0; + } + .consumes-check { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + } + .consumes-qty { + width: 64px; + padding: 4px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color); + color: var(--primary-text-color); + } + /* #111: other objects' pools sit behind a disclosure so the object's OWN + parts stay the primary list; each group is headed by the owning object's + name, so which pool a checkbox means is never a guess. */ + .shared-pools { + margin-top: 6px; + } + .shared-pools > summary { + cursor: pointer; + padding: 2px 0; + font-size: 13px; + color: var(--secondary-text-color); + } + .shared-pool-owner { + margin-top: 6px; + font-size: 12px; + font-weight: 500; + color: var(--secondary-text-color); + } + .field-help { + font-size: 12px; + color: var(--secondary-text-color); + } + .baseline-effective { + margin-top: 2px; + font-weight: 500; + color: var(--primary-text-color); + } + /* Live computed trigger hint — reads the bound sensor and explains what + happens next. Info-accented so it reads as guidance, not an error. */ + .trigger-live-hint { + font-size: 12px; + color: var(--secondary-text-color); + border-left: 3px solid var(--info-color, #2196f3); + background: rgba(33, 150, 243, 0.08); + border-radius: 0 6px 6px 0; + padding: 6px 10px; + margin: 4px 0; + } + .field-help a, + .link-button { + background: none; + border: 0; + padding: 0; + color: var(--primary-color); + cursor: pointer; + font: inherit; + text-decoration: underline; + } + .field-help a:hover, + .link-button:hover { + text-decoration: none; + } + /* Smaller refresh icon-button when shown next to the dropdown. */ + .select-row .link-button { + margin-left: 8px; + text-decoration: none; + font-size: 16px; + } + .select-row .link-button:hover { + color: var(--primary-color); + opacity: 0.7; + } + h3 { + margin: 8px 0 0; + font-size: 14px; + color: var(--primary-color); + } + .select-row { + display: flex; + flex-direction: column; + gap: 4px; + } + .assignee-pool { + display: flex; + flex-wrap: wrap; + gap: 6px 14px; + margin-top: 4px; + } + .checkbox-row { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 13px; + cursor: pointer; + margin: 2px 0; + } + .checkbox-row input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; + } + .pool-item { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 13px; + cursor: pointer; + } + .pool-item input[type="checkbox"] { + width: 16px; + height: 16px; + cursor: pointer; + } + .select-row label { + font-size: 12px; + color: var(--secondary-text-color); + } + .select-row select { + padding: 8px; + border: 1px solid var(--divider-color); + border-radius: 4px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 14px; + } + .field-label { + font-size: 12px; + color: var(--secondary-text-color); + } + .weekday-chips { + display: flex; + flex-wrap: wrap; + gap: 6px; + } + .weekday-chip { + padding: 6px 12px; + border: 1px solid var(--divider-color); + border-radius: 16px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + cursor: pointer; + } + .weekday-chip.selected { + background: var(--primary-color, #03a9f4); + color: var(--text-primary-color, #fff); + border-color: var(--primary-color, #03a9f4); + } + .season-chip { + padding: 6px 10px; + border: 1px solid var(--divider-color); + border-radius: 16px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + cursor: pointer; + } + .season-chip.selected { + background: var(--primary-color, #03a9f4); + color: var(--text-primary-color, #fff); + border-color: var(--primary-color, #03a9f4); + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + .toggle-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + cursor: pointer; + } + `,d([x({attribute:!1})],g.prototype,"hass",2),d([x({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),d([x({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),d([x({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),d([x({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),d([u()],g.prototype,"parts",2),d([u()],g.prototype,"_foreignOwners",2),d([u()],g.prototype,"_open",2),d([u()],g.prototype,"_entityPickerFallback",2),d([u()],g.prototype,"_loading",2),d([u()],g.prototype,"_error",2),d([u()],g.prototype,"_entryId",2),d([u()],g.prototype,"_taskId",2),d([u()],g.prototype,"_objectChoices",2),d([u()],g.prototype,"_name",2),d([u()],g.prototype,"_type",2),d([u()],g.prototype,"_scheduleType",2),d([u()],g.prototype,"_intervalDays",2),d([u()],g.prototype,"_intervalUnit",2),d([u()],g.prototype,"_dueDate",2),d([u()],g.prototype,"_warningDays",2),d([u()],g.prototype,"_earliestCompletionDays",2),d([u()],g.prototype,"_intervalAnchor",2),d([u()],g.prototype,"_weekdays",2),d([u()],g.prototype,"_nth",2),d([u()],g.prototype,"_nthWeekday",2),d([u()],g.prototype,"_domDay",2),d([u()],g.prototype,"_domLastDay",2),d([u()],g.prototype,"_domBusiness",2),d([u()],g.prototype,"_calOffset",2),d([u()],g.prototype,"_seasonMonths",2),d([u()],g.prototype,"_endsMode",2),d([u()],g.prototype,"_endsCount",2),d([u()],g.prototype,"_endsUntil",2),d([u()],g.prototype,"_schedulePreview",2),d([u()],g.prototype,"_schedulePreviewEnded",2),d([u()],g.prototype,"_notes",2),d([u()],g.prototype,"_documentationUrl",2),d([u()],g.prototype,"_customIcon",2),d([u()],g.prototype,"_priority",2),d([u()],g.prototype,"_labels",2),d([u()],g.prototype,"_enabled",2),d([u()],g.prototype,"_triggerEntityId",2),d([u()],g.prototype,"_triggerEntityIds",2),d([u()],g.prototype,"_triggerEntityLogic",2),d([u()],g.prototype,"_triggerAttribute",2),d([u()],g.prototype,"_triggerType",2),d([u()],g.prototype,"_triggerAbove",2),d([u()],g.prototype,"_triggerBelow",2),d([u()],g.prototype,"_triggerEquals",2),d([u()],g.prototype,"_triggerNotEquals",2),d([u()],g.prototype,"_triggerForMinutes",2),d([u()],g.prototype,"_triggerCombinator",2),d([u()],g.prototype,"_triggerTargetValue",2),d([u()],g.prototype,"_triggerDeltaMode",2),d([u()],g.prototype,"_triggerBaselineValue",2),d([u()],g.prototype,"_liveBaselineValue",2),d([u()],g.prototype,"_autoCompleteOnRecovery",2),d([u()],g.prototype,"_triggerFromState",2),d([u()],g.prototype,"_triggerToState",2),d([u()],g.prototype,"_triggerTargetChanges",2),d([u()],g.prototype,"_triggerRuntimeHours",2),d([u()],g.prototype,"_triggerOnStates",2),d([u()],g.prototype,"_compoundLogic",2),d([u()],g.prototype,"_compoundConditions",2),d([u()],g.prototype,"_suggestedAttributes",2),d([u()],g.prototype,"_availableAttributes",2),d([u()],g.prototype,"_entityDomain",2),d([u()],g.prototype,"_lastPerformed",2),d([u()],g.prototype,"_nfcTagId",2),d([u()],g.prototype,"_readingUnit",2),d([u()],g.prototype,"_consumesParts",2),d([u()],g.prototype,"_partsLoadFailed",2),d([u()],g.prototype,"_availableTags",2),d([u()],g.prototype,"_responsibleUserId",2),d([u()],g.prototype,"_assigneePool",2),d([u()],g.prototype,"_rotationStrategy",2),d([u()],g.prototype,"_availableUsers",2),d([u()],g.prototype,"_checklistText",2),d([u()],g.prototype,"_requiredCompletion",2),d([u()],g.prototype,"_scheduleTime",2),d([u()],g.prototype,"_actionService",2),d([u()],g.prototype,"_actionTargetEntity",2),d([u()],g.prototype,"_actionData",2),d([u()],g.prototype,"_actionDataJsonFallback",2),d([u()],g.prototype,"_actionTesting",2),d([u()],g.prototype,"_actionTestResult",2),d([u()],g.prototype,"_actionTestError",2),d([u()],g.prototype,"_qcNotes",2),d([u()],g.prototype,"_qcCost",2),d([u()],g.prototype,"_qcDuration",2),d([u()],g.prototype,"_qcFeedback",2),d([u()],g.prototype,"_environmentalEntity",2),d([u()],g.prototype,"_environmentalAttribute",2),d([u()],g.prototype,"_adaptiveEnabled",2),d([u()],g.prototype,"_adaptiveAlpha",2),d([u()],g.prototype,"_adaptiveMin",2),d([u()],g.prototype,"_adaptiveMax",2),d([u()],g.prototype,"_adaptiveSeasonal",2),d([u()],g.prototype,"_adaptivePrediction",2),d([u()],g.prototype,"_conditionAttrOptions",2);ft=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",ft)});var $,xi=w(()=>{"use strict";P();U();R();oe();_t();gt();$=class extends A{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(){this._open||(this._open=!0,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((e,t)=>[String(t),!!this.checklistPrefill[e]]).filter(([,e])=>e)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(e=>[ee(e),{...e}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,i=t.files?.[0];if(t.value="",!!i){this._photoUploading=!0,this._error="";try{let n=new FormData;n.append("entry_id",this.entryId),n.append("tags","photo"),n.append("file",i,i.name);let o=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:n});if(!o.ok){this._error=o.status===413?s("doc_too_large",this.lang):s("doc_upload_failed",this.lang);return}let p=await o.json();p.id&&(this._photoDocId=p.id,this._photoPreview=URL.createObjectURL(i))}catch{this._error=s("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=s("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=j(e,this.lang,s("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?l``:h}_partsCostSuggestion(){if(this.restockDefault!==null){let i=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(i)||i<=0?null:Math.round(this.restockUnitCost*i*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let i of Object.values(this._usedParts)){let n=this.parts.find(o=>ee({part_id:o.id,entry_id:o.entry_id})===ee(i));n?.cost!=null&&(e+=n.cost*(i.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return h;let t=this._partsCostSuggestion();if(t==null||t<=0)return h;let i=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return l``}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en";return l` + +
${s("complete_title",e)}${this.taskName}
+
+ ${this._error?l`
${this._error}
`:h} + ${this.checklist.length>0?l` +
+ + ${this.checklist.map((t,i)=>l` + + `)} +
+ `:h} + ${this.taskType==="reading"?l` + `:h} + ${this.parts.length?l`
+ ${s("complete_parts_used",e)} + ${this.parts.map(t=>{let i=ee({part_id:t.id,entry_id:t.entry_id}),n=this._usedParts[i],o=n!==void 0,p=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return l`
+ + ${o?l`{let _=parseFloat(c.target.value);this._usedParts={...this._usedParts,[i]:{...p,quantity:Number.isFinite(_)&&_>=.01?_:1}}}} />`:h} +
`})} +
`:this.consumesInfo.length?l`
+ ${this.consumesInfo.map(t=>l`
${t}
`)} +
`:h} + ${this.restockDefault!==null?l` + `:h} + + + + + +
+ ${s("completion_photo_optional",e)}${this._req("photo")} + ${this._photoPreview?l` +
+ + +
`:l` + `} +
+ ${this.adaptiveEnabled?l` + + `:h} +
+
+ + ${s("cancel",e)} + + 0} + title=${this._missingRequired.length?this._missingRequired.map(t=>s("err_required",e).replace("{field}",s(Ge[t]??t,e))).join(" \xB7 "):""} + > + ${this._loading?s("completing",e):s("complete",e)} + +
+
+ `}};$.styles=[pi,S` + .req-mark { + color: var(--error-color, #f44336); + margin-left: 2px; + font-weight: 600; + } + /* #104: one-click cost suggestion from parts — quiet link-style chip. */ + .cost-suggestion { + align-self: flex-start; + margin-top: 4px; + padding: 0; + border: none; + background: none; + color: var(--primary-color); + font-size: 12.5px; + cursor: pointer; + text-decoration: underline dotted; + text-underline-offset: 2px; + } + .dialog-title { + font-size: 18px; + font-weight: 500; + padding-bottom: 12px; + } + .content { + display: flex; + flex-direction: column; + gap: 16px; + min-width: 300px; + } + .dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + padding-top: 16px; + } + .consumes-hint { + font-size: 13px; + color: var(--secondary-text-color); + border-left: 3px solid var(--primary-color); + padding: 4px 8px; + margin: 4px 0 8px; + } + /* #99: editable per-completion parts selection */ + .used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; } + .used-part-row { display: flex; align-items: center; gap: 8px; } + .used-part-check { + display: flex; align-items: center; gap: 6px; flex: 1; + font-size: 13px; cursor: pointer; + } + .used-part-check input { cursor: pointer; } + /* #111: whose stock this row draws on. Muted but never omitted — an + unlabelled foreign pool is indistinguishable from an own part. */ + .used-part-owner { color: var(--secondary-text-color); } + .used-part-qty { + width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px; + border: 1px solid var(--divider-color); + background: var(--card-background-color); + color: var(--primary-text-color); + } + .error { + color: var(--error-color, #f44336); + font-size: 13px; + } + /* .field/.field-label/.field-input come from nativeFieldStyles */ + .photo-pick { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border: 1px dashed var(--divider-color); + border-radius: 8px; + cursor: pointer; + font-size: 13px; + color: var(--secondary-text-color); + width: fit-content; + } + .photo-pick:hover { border-color: var(--primary-color); } + .photo-pick input[type="file"] { display: none; } + .photo-preview { + position: relative; + width: fit-content; + } + .photo-preview img { + max-width: 160px; + max-height: 160px; + border-radius: 8px; + display: block; + } + .photo-remove { + position: absolute; + top: -8px; + right: -8px; + width: 24px; + height: 24px; + border-radius: 50%; + border: none; + background: var(--error-color, #db4437); + color: #fff; + cursor: pointer; + font-size: 12px; + line-height: 1; + } + .checklist-section { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + border-bottom: 1px solid var(--divider-color); + margin-bottom: 4px; + } + .checklist-label { + font-weight: 500; + font-size: 13px; + color: var(--secondary-text-color); + } + .checklist-item { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 4px 0; + font-size: 14px; + } + .checklist-item input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + } + .feedback-section { + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + border-top: 1px solid var(--divider-color); + } + .feedback-label { + font-weight: 500; + font-size: 13px; + color: var(--secondary-text-color); + } + .feedback-buttons { + display: flex; + gap: 8px; + } + .feedback-btn { + flex: 1; + padding: 8px 12px; + border: 1px solid var(--divider-color); + border-radius: 8px; + background: var(--card-background-color, #fff); + color: var(--primary-text-color); + font-size: 13px; + cursor: pointer; + text-align: center; + transition: all 0.2s; + } + .feedback-btn:hover { + background: var(--secondary-background-color, #f5f5f5); + } + .feedback-btn.selected { + background: var(--primary-color); + color: var(--text-primary-color, #fff); + border-color: var(--primary-color); + } + `],d([x({attribute:!1})],$.prototype,"hass",2),d([x()],$.prototype,"entryId",2),d([x()],$.prototype,"taskId",2),d([x()],$.prototype,"taskName",2),d([x()],$.prototype,"lang",2),d([x({type:Array})],$.prototype,"checklist",2),d([x({type:Boolean})],$.prototype,"adaptiveEnabled",2),d([x()],$.prototype,"taskType",2),d([x()],$.prototype,"readingUnit",2),d([x({attribute:!1})],$.prototype,"restockDefault",2),d([x({attribute:!1})],$.prototype,"restockUnitCost",2),d([x()],$.prototype,"currencySymbol",2),d([x({attribute:!1})],$.prototype,"parts",2),d([x({attribute:!1})],$.prototype,"consumesParts",2),d([x({type:Array})],$.prototype,"consumesInfo",2),d([x({type:Array})],$.prototype,"requiredFields",2),d([u()],$.prototype,"_open",2),d([u()],$.prototype,"_notes",2),d([u()],$.prototype,"_cost",2),d([u()],$.prototype,"_duration",2),d([u()],$.prototype,"_loading",2),d([u()],$.prototype,"_error",2),d([u()],$.prototype,"_checklistState",2),d([u()],$.prototype,"_feedback",2),d([u()],$.prototype,"_photoDocId",2),d([u()],$.prototype,"_photoPreview",2),d([u()],$.prototype,"_photoUploading",2),d([u()],$.prototype,"_readingValue",2),d([u()],$.prototype,"_restockQty",2),d([u()],$.prototype,"_completedAt",2),d([u()],$.prototype,"_usedParts",2),d([x({attribute:!1})],$.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",$)});var z,wi=w(()=>{"use strict";P();U();R();oe();z=class extends A{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return H(this.hass)}openEdit(e){this._draft={...e},this._originalSnapshot={...e},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let e=this._draft;if(e)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),i=[];for(let o of t.parts||[]){let p=o.entry_id===e.entry_id,c=o.consumers.some(_=>_.entry_id===e.entry_id&&_.task_id===e.task_id);!p&&!c||i.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!p,object_name:o.object_name})}for(let o of e.used_parts||[]){let p=o.entry_id||e.entry_id;i.some(c=>c.part_id===o.part_id&&c.entry_id===p)||i.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:p,foreign:p!==e.entry_id,object_name:null})}let n={};for(let o of e.used_parts||[])n[`${o.entry_id||e.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={type:"maintenance_supporter/task/history/update",entry_id:this._draft.entry_id,task_id:this._draft.task_id,original_timestamp:this._originalSnapshot.original_timestamp};if(this._draft.timestamp!==this._originalSnapshot.timestamp&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),this.dispatchEvent(new CustomEvent("history-entry-saved",{detail:{entry_id:this._draft.entry_id,task_id:this._draft.task_id,new_timestamp:this._draft.timestamp},bubbles:!0,composed:!0})),this.close()}catch(e){this._error=j(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return h;let e=this._lang,t=this._draft;return l` +
+ + `}};z.styles=S` + :host { display: contents; } + .backdrop { + position: fixed; inset: 0; + background: rgba(0,0,0,0.5); + z-index: 100; + } + .dialog { + position: fixed; left: 50%; top: 50%; + transform: translate(-50%, -50%); + width: 95vw; max-width: 480px; + background: var(--card-background-color, var(--ha-card-background, #1c1c1c)); + color: var(--primary-text-color); + border-radius: 12px; + box-shadow: 0 10px 30px rgba(0,0,0,0.4); + padding: 20px; + display: flex; flex-direction: column; gap: 12px; + z-index: 101; + max-height: 90vh; overflow: auto; + } + h2 { margin: 0; font-size: 18px; } + .entry-type { + display: flex; align-items: center; gap: 6px; + color: var(--secondary-text-color); font-size: 13px; + } + label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; } + label span { color: var(--secondary-text-color); } + .row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } + input, textarea { + padding: 8px; font-size: 14px; + background: var(--secondary-background-color, #2c2c2c); + color: var(--primary-text-color); + border: 1px solid var(--divider-color, #444); + border-radius: 6px; + width: 100%; box-sizing: border-box; + font-family: inherit; + } + .actions { + display: flex; gap: 8px; justify-content: flex-end; + margin-top: 8px; + } + button { + padding: 8px 16px; font-size: 14px; + border-radius: 6px; cursor: pointer; + border: none; font-weight: 500; + } + button.cancel { + background: transparent; + color: var(--primary-text-color); + border: 1px solid var(--divider-color); + } + button.save { + background: var(--primary-color); + color: var(--text-primary-color, white); + } + button[disabled] { opacity: 0.5; cursor: wait; } + .error { + color: var(--error-color, #d32f2f); + font-size: 13px; padding: 8px; + background: rgba(211,47,47,0.1); + border-radius: 6px; + } + /* #130: parts on the entry */ + .parts-block { + display: flex; flex-direction: column; gap: 6px; + border: 1px solid var(--divider-color, #444); + border-radius: 6px; padding: 8px; + } + .parts-title { color: var(--secondary-text-color); font-size: 13px; } + .part-row-edit { + display: flex; flex-direction: row; align-items: center; gap: 8px; + font-size: 14px; + } + .part-row-edit input[type="checkbox"] { width: auto; } + .part-label { flex: 1; color: var(--primary-text-color); } + .part-qty { width: 76px; } + `,d([x({attribute:!1})],z.prototype,"hass",2),d([u()],z.prototype,"_open",2),d([u()],z.prototype,"_saving",2),d([u()],z.prototype,"_error",2),d([u()],z.prototype,"_draft",2),d([u()],z.prototype,"_partOptions",2),d([u()],z.prototype,"_partQty",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",z)});function ge(a){return a.replace(/&/g,"&").replace(//g,">").replace(/"/g,""")}function $i(a){return!a.startsWith("data:image/svg+xml,")&&!a.startsWith("data:image/png;base64,")?"":ge(a)}function Pr(a){return a.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var O,ki=w(()=>{"use strict";P();U();R();O=class extends A{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,t){this._entryId=e,this._taskId=null,this._objectName=t,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,t,i,n){this._entryId=e,this._taskId=t,this._objectName=i,this._taskName=n,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let t={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(t.task_id=this._taskId);let i=[this.hass.connection.sendMessagePromise({...t,action:"view"})];this._taskId&&i.push(this.hass.connection.sendMessagePromise({...t,action:"complete"}));let n=await Promise.all(i);if(e!==this._generateSeq)return;this._viewResult=n[0],n.length>1&&(this._completeResult=n[1])}catch(t){if(e!==this._generateSeq)return;let i=t?.code,n=t?.message;this._error=i==="no_url"||typeof n=="string"&&n.includes("No Home Assistant URL")?s("qr_error_no_url",this.lang):s("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,t=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,i=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),n=window.open("","_blank","width=600,height=500");if(!n)return;let o=this.lang||"en",p=ge(t),c=ge(i),_=!!this._completeResult,f=ge(s("qr_action_view",o)),m=ge(s("qr_action_complete",o));n.document.write(` + +${p} + +

${p}

+${c?`
${c}
`:""} +
+
+ QR Info +
${f}
+
+ ${_?`
+ QR Complete +
${m}
+
`:""} +
+
${ge(this._viewResult.url)}
+