185 files

This commit is contained in:
Home Assistant Version Control
2026-08-19 11:38:13 +00:00
parent 5f9330abd7
commit a718af6423
185 changed files with 16868 additions and 3088 deletions
+1 -1
View File
@@ -31,7 +31,7 @@
}, },
{ {
"id": "53837b51a371459b95ffc0989ce615fb", "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" "type": "module"
}, },
{ {
@@ -138,6 +138,9 @@ SERVICE_COMPLETE_SCHEMA = vol.Schema(
# #128: who did it — a person ENTITY (validated picker, no free text); # #128: who did it — a person ENTITY (validated picker, no free text);
# resolved to the linked HA user id. Omitted -> the calling user. # resolved to the linked HA user id. Omitted -> the calling user.
vol.Optional("completed_by"): cv.entity_id, 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"), duration=call.data.get("duration"),
reading_value=call.data.get("reading_value"), reading_value=call.data.get("reading_value"),
completed_by=completed_by, completed_by=completed_by,
completed_at=call.data.get("completed_at"),
) )
async def _handle_reset(call: ServiceCall) -> None: async def _handle_reset(call: ServiceCall) -> None:
@@ -59,6 +59,10 @@ class TriggerStepsMixin(TriggerConfigMixin):
parts.append(f"above: {cond['trigger_above']}") parts.append(f"above: {cond['trigger_above']}")
if cond.get("trigger_below") is not None: if cond.get("trigger_below") is not None:
parts.append(f"below: {cond['trigger_below']}") 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"): if cond.get("trigger_for_minutes"):
parts.append(f"for: {cond['trigger_for_minutes']}min") parts.append(f"for: {cond['trigger_for_minutes']}min")
elif ctype == TriggerType.COUNTER: elif ctype == TriggerType.COUNTER:
@@ -97,6 +101,10 @@ class TriggerStepsMixin(TriggerConfigMixin):
config_parts.append(f"above: {tc['trigger_above']}") config_parts.append(f"above: {tc['trigger_above']}")
if tc.get("trigger_below") is not None: if tc.get("trigger_below") is not None:
config_parts.append(f"below: {tc['trigger_below']}") 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"): if tc.get("trigger_for_minutes"):
config_parts.append(f"for: {tc['trigger_for_minutes']}min") config_parts.append(f"for: {tc['trigger_for_minutes']}min")
elif trigger_type == TriggerType.COUNTER: elif trigger_type == TriggerType.COUNTER:
@@ -44,11 +44,14 @@ from .const import (
CONF_TRIGGER_ABOVE, CONF_TRIGGER_ABOVE,
CONF_TRIGGER_ATTRIBUTE, CONF_TRIGGER_ATTRIBUTE,
CONF_TRIGGER_BELOW, CONF_TRIGGER_BELOW,
CONF_TRIGGER_COMBINATOR,
CONF_TRIGGER_DELTA_MODE, CONF_TRIGGER_DELTA_MODE,
CONF_TRIGGER_ENTITY, CONF_TRIGGER_ENTITY,
CONF_TRIGGER_ENTITY_LOGIC, CONF_TRIGGER_ENTITY_LOGIC,
CONF_TRIGGER_EQUALS,
CONF_TRIGGER_FOR_MINUTES, CONF_TRIGGER_FOR_MINUTES,
CONF_TRIGGER_FROM_STATE, CONF_TRIGGER_FROM_STATE,
CONF_TRIGGER_NOT_EQUALS,
CONF_TRIGGER_ON_STATES, CONF_TRIGGER_ON_STATES,
CONF_TRIGGER_RUNTIME_HOURS, CONF_TRIGGER_RUNTIME_HOURS,
CONF_TRIGGER_TARGET_CHANGES, 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.""" """The safety-interval + warning-days tail shared by all four type steps."""
return { return {
vol.Optional(CONF_TASK_INTERVAL_DAYS): selector.NumberSelector( 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_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( vol.Optional(
CONF_TASK_WARNING_DAYS, CONF_TASK_WARNING_DAYS,
default=get_default_warning_days(hass), 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: def _state_selector(entity_id: str | None, *, multiple: bool = False) -> Any:
"""State field bound to the trigger entity (#129 follow-up). """State field bound to the trigger entity (#129 follow-up).
@@ -463,8 +487,10 @@ class TriggerConfigMixin:
above = user_input.get(CONF_TRIGGER_ABOVE) above = user_input.get(CONF_TRIGGER_ABOVE)
below = user_input.get(CONF_TRIGGER_BELOW) 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" errors["base"] = "invalid_threshold"
else: else:
tc = self._current_task["trigger_config"] tc = self._current_task["trigger_config"]
@@ -472,8 +498,13 @@ class TriggerConfigMixin:
tc[CONF_TRIGGER_ABOVE] = above tc[CONF_TRIGGER_ABOVE] = above
if below is not None: if below is not None:
tc[CONF_TRIGGER_BELOW] = below 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) tc[CONF_TRIGGER_FOR_MINUTES] = user_input.get(CONF_TRIGGER_FOR_MINUTES, 0)
_apply_recovery_flag(tc, user_input) _apply_recovery_flag(tc, user_input)
_apply_combinator(tc, user_input)
# Multi-entity: store entity_logic if multiple entities selected # Multi-entity: store entity_logic if multiple entities selected
entity_ids = tc.get("entity_ids", []) entity_ids = tc.get("entity_ids", [])
@@ -509,13 +540,25 @@ class TriggerConfigMixin:
step="any", 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( vol.Optional(CONF_TRIGGER_FOR_MINUTES, default=0): selector.NumberSelector(
selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX)
), ),
**_recovery_field(self._current_task.get("trigger_config")), **_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(_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( return self.async_show_form(
step_id=step_id, step_id=step_id,
@@ -541,6 +584,7 @@ class TriggerConfigMixin:
tc[CONF_TRIGGER_TARGET_VALUE] = user_input[CONF_TRIGGER_TARGET_VALUE] tc[CONF_TRIGGER_TARGET_VALUE] = user_input[CONF_TRIGGER_TARGET_VALUE]
tc[CONF_TRIGGER_DELTA_MODE] = user_input.get(CONF_TRIGGER_DELTA_MODE, False) tc[CONF_TRIGGER_DELTA_MODE] = user_input.get(CONF_TRIGGER_DELTA_MODE, False)
_apply_recovery_flag(tc, user_input) _apply_recovery_flag(tc, user_input)
_apply_combinator(tc, user_input)
# Counting start value (#102/#103): editable here since the parity # Counting start value (#102/#103): editable here since the parity
# round — an omitted field keeps the value the attribute step # round — an omitted field keeps the value the attribute step
# carried over; the backend clears stale Store state on change. # carried over; the backend clears stale Store state on change.
@@ -601,7 +645,7 @@ class TriggerConfigMixin:
**_recovery_field(prev_tc), **_recovery_field(prev_tc),
} }
schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", []))) 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( return self.async_show_form(
step_id=step_id, step_id=step_id,
@@ -638,6 +682,7 @@ class TriggerConfigMixin:
tc[CONF_TRIGGER_TO_STATE] = to_state tc[CONF_TRIGGER_TO_STATE] = to_state
tc[CONF_TRIGGER_TARGET_CHANGES] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1) tc[CONF_TRIGGER_TARGET_CHANGES] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1)
_apply_recovery_flag(tc, user_input) _apply_recovery_flag(tc, user_input)
_apply_combinator(tc, user_input)
# Multi-entity: store entity_logic if multiple entities selected # Multi-entity: store entity_logic if multiple entities selected
entity_ids = tc.get("entity_ids", []) entity_ids = tc.get("entity_ids", [])
@@ -669,7 +714,7 @@ class TriggerConfigMixin:
**_recovery_field(self._current_task.get("trigger_config")), **_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(_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( return self.async_show_form(
step_id=step_id, step_id=step_id,
@@ -701,6 +746,7 @@ class TriggerConfigMixin:
else: else:
tc.pop(CONF_TRIGGER_ON_STATES, None) tc.pop(CONF_TRIGGER_ON_STATES, None)
_apply_recovery_flag(tc, user_input) _apply_recovery_flag(tc, user_input)
_apply_combinator(tc, user_input)
# Multi-entity: store entity_logic if multiple entities selected # Multi-entity: store entity_logic if multiple entities selected
entity_ids = tc.get("entity_ids", []) entity_ids = tc.get("entity_ids", [])
@@ -740,7 +786,7 @@ class TriggerConfigMixin:
**_recovery_field(current_tc), **_recovery_field(current_tc),
} }
schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", []))) 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( return self.async_show_form(
step_id=step_id, step_id=step_id,
@@ -920,6 +966,12 @@ class TriggerConfigMixin:
cond["trigger_above"] = above cond["trigger_above"] = above
if below is not None: if below is not None:
cond["trigger_below"] = below 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) for_min = user_input.get(CONF_TRIGGER_FOR_MINUTES)
if for_min: if for_min:
cond["trigger_for_minutes"] = for_min cond["trigger_for_minutes"] = for_min
@@ -957,6 +1009,12 @@ class TriggerConfigMixin:
vol.Optional(CONF_TRIGGER_BELOW): selector.NumberSelector( vol.Optional(CONF_TRIGGER_BELOW): selector.NumberSelector(
selector.NumberSelectorConfig(mode=selector.NumberSelectorMode.BOX) 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( vol.Optional(CONF_TRIGGER_FOR_MINUTES, default=0): selector.NumberSelector(
selector.NumberSelectorConfig( selector.NumberSelectorConfig(
min=0, min=0,
@@ -401,7 +401,14 @@ DEFAULT_ENTITY_LOGIC = "any"
CONF_TRIGGER_ATTRIBUTE = "trigger_attribute" CONF_TRIGGER_ATTRIBUTE = "trigger_attribute"
CONF_TRIGGER_ABOVE = "trigger_above" CONF_TRIGGER_ABOVE = "trigger_above"
CONF_TRIGGER_BELOW = "trigger_below" CONF_TRIGGER_BELOW = "trigger_below"
CONF_TRIGGER_EQUALS = "trigger_equals"
CONF_TRIGGER_NOT_EQUALS = "trigger_not_equals"
CONF_TRIGGER_FOR_MINUTES = "trigger_for_minutes" 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_TARGET_VALUE = "trigger_target_value"
CONF_TRIGGER_DELTA_MODE = "trigger_delta_mode" CONF_TRIGGER_DELTA_MODE = "trigger_delta_mode"
CONF_TRIGGER_BASELINE_VALUE = "trigger_baseline_value" CONF_TRIGGER_BASELINE_VALUE = "trigger_baseline_value"
@@ -622,6 +629,22 @@ class HistoryEntryType(StrEnum):
TRIGGER_REPLACED = "trigger_replaced" 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): class MaintenanceFeedback(StrEnum):
"""Feedback from user about whether maintenance was needed.""" """Feedback from user about whether maintenance was needed."""
@@ -4,7 +4,7 @@ from __future__ import annotations
import logging import logging
import time import time
from datetime import date, timedelta from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@@ -907,6 +907,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
used_parts: list[dict[str, Any]] | None = None, used_parts: list[dict[str, Any]] | None = None,
auto: bool = False, auto: bool = False,
unattended: bool = False, unattended: bool = False,
completed_at: datetime | None = None,
) -> None: ) -> None:
"""Mark a task as completed and persist. """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 voice command. Those paths attach a canned provenance note
("Completed via NFC tag"), which must NOT be mistaken for the note a ("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. 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() merged = self._get_merged_tasks_data()
if task_id not in merged: if task_id not in merged:
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title) _LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
return 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 # Required completion details. Checked HERE — the one point every
# surface funnels through — so a task demanding a note cannot be # surface funnels through — so a task demanding a note cannot be
# closed out from a button, the to-do list, an NFC tag, a # 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 # because the task is already completed. Deliberately a SEPARATE map
# from _recently_completed: that one is also stamped by skip/reset, # from _recently_completed: that one is also stamped by skip/reset,
# and a complete right after a date-correction reset must go through. # and a complete right after a date-correction reset must go through.
last_manual = self._recent_manual_completions.get(task_id) # An explicit completed_at is a deliberate backfill, not a double-tap
if last_manual is not None and time.monotonic() - last_manual < MANUAL_COMPLETION_DEDUP_SECONDS: # — it neither checks nor stamps the guard (a stamped guard would
_LOGGER.info( # swallow a normal completion made right after backfilling, and a
"Ignoring duplicate completion of %s within %.0fs (double-tap from a second device?)", # normal completion's stamp must not swallow the backfill).
task_id, if completed_at is None:
time.monotonic() - last_manual, last_manual = self._recent_manual_completions.get(task_id)
) if last_manual is not None and time.monotonic() - last_manual < MANUAL_COMPLETION_DEDUP_SECONDS:
return _LOGGER.info(
# Stamp the guard NOW, before any await (the photo-link below yields the "Ignoring duplicate completion of %s within %.0fs (double-tap from a second device?)",
# loop). Stamping only at the end let two photo-carrying completions in task_id,
# the same tick both pass the check and interleave → double rotation / time.monotonic() - last_manual,
# part-consume / history entry. )
self._recent_manual_completions[task_id] = time.monotonic() 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]) task = MaintenanceTask.from_dict(merged[task_id])
pre_rotation_responsible = task.responsible_user_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 actual_interval: int | None = None
if task.last_performed: if task.last_performed:
try: try:
last = date.fromisoformat(task.last_performed) last = date.fromisoformat(task.last_performed)
actual_interval = (dt_util.now().date() - last).days actual_interval = (effective_ts.date() - last).days
except (ValueError, TypeError): except (ValueError, TypeError):
actual_interval = None actual_interval = None
@@ -1029,7 +1055,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
if isinstance(link, dict) and link.get("part_id") if isinstance(link, dict) and link.get("part_id")
] ]
task.complete( is_latest = task.complete(
notes=notes, notes=notes,
cost=cost, cost=cost,
duration=duration, duration=duration,
@@ -1040,10 +1066,13 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
reading_value=reading_value, reading_value=reading_value,
used_parts=enriched_used, used_parts=enriched_used,
auto=auto, auto=auto,
completed_at=completed_at,
) )
# #73: a completed cycle retires its in-cycle checklist ticks — the # #73: a completed cycle retires its in-cycle checklist ticks — the
# snapshot that matters is in the history entry above. # snapshot that matters is in the history entry above. A pure backfill
self._store.clear_checklist_progress(task_id) # 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 # 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: # 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: if photo_doc_id:
await self._link_completion_photo(photo_doc_id, task_id) await self._link_completion_photo(photo_doc_id, task_id)
# Update adaptive scheduling if enabled # Update adaptive scheduling if enabled. Gated on is_latest: a pure
if task.adaptive_config and task.adaptive_config.get("enabled"): # 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: if actual_interval is not None and actual_interval > 0:
from .helpers.interval_analyzer import IntervalAnalyzer from .helpers.interval_analyzer import IntervalAnalyzer
@@ -1060,11 +1092,12 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# Store the base interval for blending reference # Store the base interval for blending reference
if "base_interval" not in task.adaptive_config: if "base_interval" not in task.adaptive_config:
task.adaptive_config["base_interval"] = task.interval_days or DEFAULT_INTERVAL_DAYS 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" task.adaptive_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north"
now = dt_util.now() task.adaptive_config["_current_month"] = effective_ts.month
task.adaptive_config["_current_month"] = now.month task.adaptive_config["_current_date"] = effective_ts.date().isoformat()
task.adaptive_config["_current_date"] = now.date().isoformat()
updated_config = analyzer.update_on_completion(task.adaptive_config, actual_interval, feedback) updated_config = analyzer.update_on_completion(task.adaptive_config, actual_interval, feedback)
task.adaptive_config = updated_config task.adaptive_config = updated_config
@@ -1118,6 +1151,14 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
duration=duration, duration=duration,
feedback=feedback, feedback=feedback,
completed_by=completed_by, 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,
), ),
) )
@@ -13,6 +13,7 @@ from homeassistant.util import dt as dt_util
if TYPE_CHECKING: if TYPE_CHECKING:
from ...sensor import MaintenanceSensor from ...sensor import MaintenanceSensor
from ...helpers.trigger_fallback import threshold_exceeds
from .base_trigger import BaseTrigger from .base_trigger import BaseTrigger
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -24,6 +25,7 @@ class ThresholdTrigger(BaseTrigger):
Supports: Supports:
- Above threshold (value > above) - Above threshold (value > above)
- Below threshold (value < below) - Below threshold (value < below)
- Equals / not-equals a discrete level (value = / equals)
- Duration requirement (value must exceed for X minutes) - 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._above: float | None = trigger_config.get("trigger_above")
self._below: float | None = trigger_config.get("trigger_below") 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._for_minutes: int = trigger_config.get("trigger_for_minutes", 0)
self._threshold_exceeded = False self._threshold_exceeded = False
@@ -63,11 +67,13 @@ class ThresholdTrigger(BaseTrigger):
def _value_exceeds_threshold(self, value: float) -> bool: def _value_exceeds_threshold(self, value: float) -> bool:
"""Check if the value exceeds configured thresholds.""" """Check if the value exceeds configured thresholds."""
if self._above is not None and value > self._above: return threshold_exceeds(
return True value,
if self._below is not None and value < self._below: above=self._above,
return True below=self._below,
return False equals=self._equals,
not_equals=self._not_equals,
)
def evaluate(self, value: float) -> bool: def evaluate(self, value: float) -> bool:
"""Evaluate threshold condition.""" """Evaluate threshold condition."""
@@ -101,6 +101,41 @@ describe("complete-dialog", () => {
expect("feedback" in msg).to.be.false; expect("feedback" in msg).to.be.false;
expect("checklist_state" in msg).to.be.false; expect("checklist_state" in msg).to.be.false;
expect("photo_doc_id" 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<HTMLInputElement>('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<HTMLInputElement>('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 () => { it("attaches an uploaded photo as photo_doc_id", async () => {
@@ -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 () => { 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 // The Battery Fleet task's trigger has no singular entity_id; the save
// path gates on _triggerEntityId, so before the hydration fallback an // 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", 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<Record<string, unknown>>;
expect(conds[0]).to.deep.include({
type: "threshold",
trigger_equals: 3,
trigger_not_equals: 1,
});
});
}); });
@@ -53,6 +53,8 @@ export class MaintenanceCompleteDialog extends LitElement {
@state() private _photoUploading = false; @state() private _photoUploading = false;
@state() private _readingValue = ""; @state() private _readingValue = "";
@state() private _restockQty = ""; @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 /** 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. */ * objects can carry the same part id, so part_id alone would merge pools. */
@state() private _usedParts: Record<string, TaskPartLink> = {}; @state() private _usedParts: Record<string, TaskPartLink> = {};
@@ -81,6 +83,7 @@ export class MaintenanceCompleteDialog extends LitElement {
this._photoUploading = false; this._photoUploading = false;
this._readingValue = ""; this._readingValue = "";
this._restockQty = this.restockDefault !== null ? String(this.restockDefault) : ""; this._restockQty = this.restockDefault !== null ? String(this.restockDefault) : "";
this._completedAt = "";
// #99: prefill "parts used" with the task's fixed links — the user can // #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 // untick or adjust before completing. The whole link is kept, entry_id
// included, so a shared pool survives the edit (#111). // included, so a shared pool survives the edit (#111).
@@ -167,6 +170,18 @@ export class MaintenanceCompleteDialog extends LitElement {
if (this._photoDocId) { if (this._photoDocId) {
data.photo_doc_id = 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 !== "") { if (this._readingValue !== "") {
const rv = parseFloat(this._readingValue); const rv = parseFloat(this._readingValue);
if (!isNaN(rv)) data.reading_value = rv; if (!isNaN(rv)) data.reading_value = rv;
@@ -371,6 +386,13 @@ export class MaintenanceCompleteDialog extends LitElement {
.value=${this._duration} .value=${this._duration}
@input=${(e: Event) => (this._duration = (e.target as HTMLInputElement).value)} /> @input=${(e: Event) => (this._duration = (e.target as HTMLInputElement).value)} />
</label> </label>
<label class="field">
<span class="field-label">${t("completed_at_optional", L)}</span>
<input type="datetime-local" class="field-input"
max=${new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16)}
.value=${this._completedAt}
@change=${(e: Event) => (this._completedAt = (e.target as HTMLInputElement).value)} />
</label>
<div class="field"> <div class="field">
<span class="field-label">${t("completion_photo_optional", L)}${this._req("photo")}</span> <span class="field-label">${t("completion_photo_optional", L)}${this._req("photo")}</span>
${this._photoPreview ${this._photoPreview
@@ -35,6 +35,8 @@ interface CompoundConditionDraft {
attribute: string; // "" = use the entity state attribute: string; // "" = use the entity state
above: string; above: string;
below: string; below: string;
equals: string;
notEquals: string;
forMinutes: string; forMinutes: string;
targetValue: string; targetValue: string;
deltaMode: boolean; deltaMode: boolean;
@@ -50,7 +52,8 @@ interface CompoundConditionDraft {
function emptyCondition(): CompoundConditionDraft { function emptyCondition(): CompoundConditionDraft {
return { return {
entityIds: "", type: "threshold", attribute: "", above: "", below: "", forMinutes: "0", entityIds: "", type: "threshold", attribute: "", above: "", below: "",
equals: "", notEquals: "", forMinutes: "0",
targetValue: "", deltaMode: false, fromState: "", toState: "", targetValue: "", deltaMode: false, fromState: "", toState: "",
targetChanges: "", runtimeHours: "", onStates: "", carry: {}, targetChanges: "", runtimeHours: "", onStates: "", carry: {},
}; };
@@ -60,7 +63,7 @@ function emptyCondition(): CompoundConditionDraft {
* travels through `carry` untouched. */ * travels through `carry` untouched. */
const MANAGED_CONDITION_KEYS = new Set([ const MANAGED_CONDITION_KEYS = new Set([
"entity_id", "entity_ids", "type", "attribute", "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_target_value", "trigger_delta_mode",
"trigger_from_state", "trigger_to_state", "trigger_target_changes", "trigger_from_state", "trigger_to_state", "trigger_target_changes",
"trigger_runtime_hours", "trigger_on_states", "trigger_runtime_hours", "trigger_on_states",
@@ -75,6 +78,8 @@ function conditionToDraft(c: TriggerConfig): CompoundConditionDraft {
attribute: c.attribute || "", attribute: c.attribute || "",
above: c.trigger_above?.toString() ?? "", above: c.trigger_above?.toString() ?? "",
below: c.trigger_below?.toString() ?? "", below: c.trigger_below?.toString() ?? "",
equals: c.trigger_equals?.toString() ?? "",
notEquals: c.trigger_not_equals?.toString() ?? "",
forMinutes: c.trigger_for_minutes?.toString() ?? "0", forMinutes: c.trigger_for_minutes?.toString() ?? "0",
targetValue: c.trigger_target_value?.toString() ?? "", targetValue: c.trigger_target_value?.toString() ?? "",
deltaMode: c.trigger_delta_mode || false, deltaMode: c.trigger_delta_mode || false,
@@ -99,6 +104,8 @@ function draftToCondition(d: CompoundConditionDraft): TriggerConfig | null {
if (d.type === "threshold") { if (d.type === "threshold") {
const a = parseFloat(d.above); if (!isNaN(a)) c.trigger_above = a; 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 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; const f = parseInt(d.forMinutes, 10); if (!isNaN(f)) c.trigger_for_minutes = f;
} else if (d.type === "counter") { } else if (d.type === "counter") {
const v = parseFloat(d.targetValue); if (!isNaN(v)) c.trigger_target_value = v; 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 _triggerType = "threshold";
@state() private _triggerAbove = ""; @state() private _triggerAbove = "";
@state() private _triggerBelow = ""; @state() private _triggerBelow = "";
@state() private _triggerEquals = "";
@state() private _triggerNotEquals = "";
@state() private _triggerForMinutes = "0"; @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 _triggerTargetValue = "";
@state() private _triggerDeltaMode = false; @state() private _triggerDeltaMode = false;
@state() private _triggerBaselineValue = ""; @state() private _triggerBaselineValue = "";
@@ -426,7 +437,10 @@ export class MaintenanceTaskDialog extends LitElement {
this._triggerType = tc.type || "threshold"; this._triggerType = tc.type || "threshold";
this._triggerAbove = tc.trigger_above?.toString() || ""; this._triggerAbove = tc.trigger_above?.toString() || "";
this._triggerBelow = tc.trigger_below?.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._triggerForMinutes = tc.trigger_for_minutes?.toString() || "0";
this._triggerCombinator = tc.trigger_combinator === "all" ? "all" : "any";
this._triggerTargetValue = tc.trigger_target_value?.toString() || ""; this._triggerTargetValue = tc.trigger_target_value?.toString() || "";
this._triggerDeltaMode = tc.trigger_delta_mode || false; this._triggerDeltaMode = tc.trigger_delta_mode || false;
this._triggerBaselineValue = tc.trigger_baseline_value?.toString() || ""; this._triggerBaselineValue = tc.trigger_baseline_value?.toString() || "";
@@ -530,7 +544,10 @@ export class MaintenanceTaskDialog extends LitElement {
this._triggerType = "threshold"; this._triggerType = "threshold";
this._triggerAbove = ""; this._triggerAbove = "";
this._triggerBelow = ""; this._triggerBelow = "";
this._triggerEquals = "";
this._triggerNotEquals = "";
this._triggerForMinutes = "0"; this._triggerForMinutes = "0";
this._triggerCombinator = "any";
this._triggerTargetValue = ""; this._triggerTargetValue = "";
this._triggerDeltaMode = false; this._triggerDeltaMode = false;
this._triggerBaselineValue = ""; this._triggerBaselineValue = "";
@@ -1089,6 +1106,7 @@ export class MaintenanceTaskDialog extends LitElement {
conditions, conditions,
}; };
if (this._autoCompleteOnRecovery) triggerConfig.auto_complete_on_recovery = true; if (this._autoCompleteOnRecovery) triggerConfig.auto_complete_on_recovery = true;
if (this._triggerCombinator === "all") triggerConfig.trigger_combinator = "all";
data.trigger_config = triggerConfig; data.trigger_config = triggerConfig;
} else if (this._taskId) { } else if (this._taskId) {
data.trigger_config = null; data.trigger_config = null;
@@ -1104,6 +1122,7 @@ export class MaintenanceTaskDialog extends LitElement {
}; };
if (this._triggerAttribute) triggerConfig.attribute = this._triggerAttribute; if (this._triggerAttribute) triggerConfig.attribute = this._triggerAttribute;
if (this._autoCompleteOnRecovery) triggerConfig.auto_complete_on_recovery = true; 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 // Multi-entity: store entity_logic for all trigger types
if (entityIds.length > 1) { if (entityIds.length > 1) {
@@ -1113,6 +1132,8 @@ export class MaintenanceTaskDialog extends LitElement {
if (this._triggerType === "threshold") { if (this._triggerType === "threshold") {
if (this._triggerAbove) { const v = parseFloat(this._triggerAbove); if (!isNaN(v)) triggerConfig.trigger_above = v; } 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._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; } if (this._triggerForMinutes) { const v = parseInt(this._triggerForMinutes, 10); if (!isNaN(v)) triggerConfig.trigger_for_minutes = v; }
} else if (this._triggerType === "counter") { } else if (this._triggerType === "counter") {
if (this._triggerTargetValue) { const v = parseFloat(this._triggerTargetValue); if (!isNaN(v)) triggerConfig.trigger_target_value = v; } 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)} @input=${(e: Event) => (this._intervalDays = (e.target as HTMLInputElement).value)}
></ms-textfield> ></ms-textfield>
${this._intervalDays ? this._renderUnitSelect() : nothing} ${this._intervalDays ? this._renderUnitSelect() : nothing}
${this._intervalDays
? html`
<div class="select-row">
<label>${t("trigger_combinator", L)}</label>
<select
@change=${(e: Event) => (this._triggerCombinator = (e.target as HTMLSelectElement).value as "any" | "all")}
>
<option value="any" ?selected=${this._triggerCombinator === "any"}>${t("trigger_combinator_any", L)}</option>
<option value="all" ?selected=${this._triggerCombinator === "all"}>${t("trigger_combinator_all", L)}</option>
</select>
</div>
`
: nothing}
`; `;
} }
@@ -1636,6 +1670,10 @@ export class MaintenanceTaskDialog extends LitElement {
@input=${(e: Event) => this._patchCondition(i, { above: (e.target as HTMLInputElement).value })}></ms-textfield> @input=${(e: Event) => this._patchCondition(i, { above: (e.target as HTMLInputElement).value })}></ms-textfield>
<ms-textfield label="${t("trigger_below", L)}" type="number" .value=${c.below} <ms-textfield label="${t("trigger_below", L)}" type="number" .value=${c.below}
@input=${(e: Event) => this._patchCondition(i, { below: (e.target as HTMLInputElement).value })}></ms-textfield> @input=${(e: Event) => this._patchCondition(i, { below: (e.target as HTMLInputElement).value })}></ms-textfield>
<ms-textfield label="${t("trigger_equals", L)}" type="number" .value=${c.equals}
@input=${(e: Event) => this._patchCondition(i, { equals: (e.target as HTMLInputElement).value })}></ms-textfield>
<ms-textfield label="${t("trigger_not_equals", L)}" type="number" .value=${c.notEquals}
@input=${(e: Event) => this._patchCondition(i, { notEquals: (e.target as HTMLInputElement).value })}></ms-textfield>
<ms-textfield label="${t("for_minutes", L)}" type="number" .value=${c.forMinutes} <ms-textfield label="${t("for_minutes", L)}" type="number" .value=${c.forMinutes}
@input=${(e: Event) => this._patchCondition(i, { forMinutes: (e.target as HTMLInputElement).value })}></ms-textfield> @input=${(e: Event) => this._patchCondition(i, { forMinutes: (e.target as HTMLInputElement).value })}></ms-textfield>
`; `;
@@ -2122,6 +2160,20 @@ export class MaintenanceTaskDialog extends LitElement {
.value=${this._triggerBelow} .value=${this._triggerBelow}
@input=${(e: Event) => (this._triggerBelow = (e.target as HTMLInputElement).value)} @input=${(e: Event) => (this._triggerBelow = (e.target as HTMLInputElement).value)}
></ms-textfield> ></ms-textfield>
<ms-textfield
label="${t("trigger_equals", L)}"
type="number"
step="any"
.value=${this._triggerEquals}
@input=${(e: Event) => (this._triggerEquals = (e.target as HTMLInputElement).value)}
></ms-textfield>
<ms-textfield
label="${t("trigger_not_equals", L)}"
type="number"
step="any"
.value=${this._triggerNotEquals}
@input=${(e: Event) => (this._triggerNotEquals = (e.target as HTMLInputElement).value)}
></ms-textfield>
<ms-textfield <ms-textfield
label="${t("for_at_least_minutes", L)}" label="${t("for_at_least_minutes", L)}"
type="number" type="number"
@@ -128,6 +128,8 @@
"notes_optional": "Poznámky (volitelné)", "notes_optional": "Poznámky (volitelné)",
"cost_optional": "Náklady (volitelné)", "cost_optional": "Náklady (volitelné)",
"duration_minutes": "Doba trvání v minutách (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í", "days": "dní",
"day": "den", "day": "den",
"today": "Dnes", "today": "Dnes",
@@ -192,9 +194,14 @@
"use_entity_state": "Použít stav entity (bez atributu)", "use_entity_state": "Použít stav entity (bez atributu)",
"trigger_above": "Spustit nad", "trigger_above": "Spustit nad",
"trigger_below": "Spustit pod", "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)", "for_at_least_minutes": "Po dobu alespoň (minut)",
"safety_interval_days": "Bezpečnostní interval (dny, volitelný)", "safety_interval_days": "Bezpečnostní interval (dny, volitelný)",
"safety_interval": "Bezpečnostní interval (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", "delta_mode": "Režim delta",
"from_state_optional": "Ze stavu (volitelné)", "from_state_optional": "Ze stavu (volitelné)",
"to_state_optional": "Do 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_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_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_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: \"<název>\" — nebo seznam názvů pro omezení karty na více objektů."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Noter (valgfrit)", "notes_optional": "Noter (valgfrit)",
"cost_optional": "Omkostning (valgfrit)", "cost_optional": "Omkostning (valgfrit)",
"duration_minutes": "Varighed i minutter (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", "days": "dage",
"day": "dag", "day": "dag",
"today": "I dag", "today": "I dag",
@@ -193,9 +195,14 @@
"use_entity_state": "Brug enhedstilstand (ingen attribut)", "use_entity_state": "Brug enhedstilstand (ingen attribut)",
"trigger_above": "Udløs over", "trigger_above": "Udløs over",
"trigger_below": "Udløs under", "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)", "for_at_least_minutes": "I mindst (minutter)",
"safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)", "safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)",
"safety_interval": "Sikkerhedsinterval (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", "delta_mode": "Delta-tilstand",
"from_state_optional": "Fra tilstand (valgfrit)", "from_state_optional": "Fra tilstand (valgfrit)",
"to_state_optional": "Til 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_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_setups_chip": "Foreslåede opsætninger fandt {n} enheder med forudindstillede udløsere",
"gs_adopt_chip": "{n} problemsensorer kan blive vedligeholdelsesopgaver", "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: \"<navn>\" — eller en liste af navne for at begrænse kortet til flere objekter."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notizen (optional)", "notes_optional": "Notizen (optional)",
"cost_optional": "Kosten (optional)", "cost_optional": "Kosten (optional)",
"duration_minutes": "Dauer in Minuten (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", "days": "Tage",
"day": "Tag", "day": "Tag",
"today": "Heute", "today": "Heute",
@@ -193,9 +195,14 @@
"use_entity_state": "Entitäts-Zustand verwenden (kein Attribut)", "use_entity_state": "Entitäts-Zustand verwenden (kein Attribut)",
"trigger_above": "Auslösen wenn über", "trigger_above": "Auslösen wenn über",
"trigger_below": "Auslösen wenn unter", "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)", "for_at_least_minutes": "Für mindestens (Minuten)",
"safety_interval_days": "Sicherheitsintervall (Tage, optional)", "safety_interval_days": "Sicherheitsintervall (Tage, optional)",
"safety_interval": "Sicherheitsintervall (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", "delta_mode": "Delta-Modus",
"from_state_optional": "Von Zustand (optional)", "from_state_optional": "Von Zustand (optional)",
"to_state_optional": "Zu 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_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_setups_chip": "Vorgeschlagene Setups: {n} Geräte mit vorverdrahteten Auslösern gefunden",
"gs_adopt_chip": "{n} Problem-Sensoren können zu Wartungsaufgaben werden", "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: \"<Objektname>\" — oder eine Namensliste, um die Karte auf mehrere Objekte zu beschränken."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notes (optional)", "notes_optional": "Notes (optional)",
"cost_optional": "Cost (optional)", "cost_optional": "Cost (optional)",
"duration_minutes": "Duration in minutes (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", "days": "days",
"day": "day", "day": "day",
"today": "Today", "today": "Today",
@@ -193,9 +195,14 @@
"use_entity_state": "Use entity state (no attribute)", "use_entity_state": "Use entity state (no attribute)",
"trigger_above": "Trigger above", "trigger_above": "Trigger above",
"trigger_below": "Trigger below", "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)", "for_at_least_minutes": "For at least (minutes)",
"safety_interval_days": "Safety interval (days, optional)", "safety_interval_days": "Safety interval (days, optional)",
"safety_interval": "Safety interval (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", "delta_mode": "Delta mode",
"from_state_optional": "From state (optional)", "from_state_optional": "From state (optional)",
"to_state_optional": "To state (optional)", "to_state_optional": "To state (optional)",
@@ -850,5 +857,17 @@
"gs_label": "Getting started — these hints retire as your setup grows", "gs_label": "Getting started — these hints retire as your setup grows",
"gs_setups_chip": "Suggested setups found {n} devices with pre-wired triggers", "gs_setups_chip": "Suggested setups found {n} devices with pre-wired triggers",
"gs_adopt_chip": "{n} problem sensors can become maintenance tasks", "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: \"<object name>\" — or a list of names to restrict the card to several objects."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notas (opcional)", "notes_optional": "Notas (opcional)",
"cost_optional": "Coste (opcional)", "cost_optional": "Coste (opcional)",
"duration_minutes": "Duración en minutos (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", "days": "días",
"day": "día", "day": "día",
"today": "Hoy", "today": "Hoy",
@@ -192,9 +194,14 @@
"use_entity_state": "Usar estado de la entidad (sin atributo)", "use_entity_state": "Usar estado de la entidad (sin atributo)",
"trigger_above": "Activar por encima de", "trigger_above": "Activar por encima de",
"trigger_below": "Activar por debajo 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)", "for_at_least_minutes": "Durante al menos (minutos)",
"safety_interval_days": "Intervalo de seguridad (días, opcional)", "safety_interval_days": "Intervalo de seguridad (días, opcional)",
"safety_interval": "Intervalo de seguridad (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", "delta_mode": "Modo delta",
"from_state_optional": "Desde estado (opcional)", "from_state_optional": "Desde estado (opcional)",
"to_state_optional": "Hasta 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_label": "Primeros pasos: estas sugerencias desaparecen a medida que crece tu configuración",
"gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados", "gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados",
"gs_adopt_chip": "{n} sensores de problemas pueden convertirse en tareas de mantenimiento", "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: \"<nombre>\" — o una lista de nombres para limitar la tarjeta a varios objetos."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Muistiinpanot (valinnainen)", "notes_optional": "Muistiinpanot (valinnainen)",
"cost_optional": "Kustannus (valinnainen)", "cost_optional": "Kustannus (valinnainen)",
"duration_minutes": "Kesto minuutteina (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ää", "days": "päivää",
"day": "päivä", "day": "päivä",
"today": "Tänään", "today": "Tänään",
@@ -193,9 +195,14 @@
"use_entity_state": "Käytä entiteetin tilaa (ei attribuuttia)", "use_entity_state": "Käytä entiteetin tilaa (ei attribuuttia)",
"trigger_above": "Laukaise yli", "trigger_above": "Laukaise yli",
"trigger_below": "Laukaise alle", "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)", "for_at_least_minutes": "Vähintään (minuuttia)",
"safety_interval_days": "Turvaväli (päivää, valinnainen)", "safety_interval_days": "Turvaväli (päivää, valinnainen)",
"safety_interval": "Turvaväli (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", "delta_mode": "Delta-tila",
"from_state_optional": "Lähtötilasta (valinnainen)", "from_state_optional": "Lähtötilasta (valinnainen)",
"to_state_optional": "Kohdetilaan (valinnainen)", "to_state_optional": "Kohdetilaan (valinnainen)",
@@ -850,5 +857,17 @@
"gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa", "gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa",
"gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla", "gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla",
"gs_adopt_chip": "{n} ongelma-anturia voi muuttua huoltotehtäviksi", "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: \"<nimi>\" — tai nimilista rajataksesi kortin useisiin kohteisiin."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notes (optionnel)", "notes_optional": "Notes (optionnel)",
"cost_optional": "Coût (optionnel)", "cost_optional": "Coût (optionnel)",
"duration_minutes": "Durée en minutes (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", "days": "jours",
"day": "jour", "day": "jour",
"today": "Aujourd'hui", "today": "Aujourd'hui",
@@ -192,9 +194,14 @@
"use_entity_state": "Utiliser l'état de l'entité (pas d'attribut)", "use_entity_state": "Utiliser l'état de l'entité (pas d'attribut)",
"trigger_above": "Déclencher au-dessus de", "trigger_above": "Déclencher au-dessus de",
"trigger_below": "Déclencher en dessous 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)", "for_at_least_minutes": "Pendant au moins (minutes)",
"safety_interval_days": "Intervalle de sécurité (jours, optionnel)", "safety_interval_days": "Intervalle de sécurité (jours, optionnel)",
"safety_interval": "Intervalle de sécurité (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", "delta_mode": "Mode delta",
"from_state_optional": "État source (optionnel)", "from_state_optional": "État source (optionnel)",
"to_state_optional": "État cible (optionnel)", "to_state_optional": "État cible (optionnel)",
@@ -850,5 +857,17 @@
"gs_label": "Premiers pas — ces conseils disparaissent à mesure que votre configuration grandit", "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_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_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 : \"<nom>\" — ou une liste de noms pour limiter la carte à plusieurs objets."
} }
@@ -129,6 +129,8 @@
"notes_optional": "टिप्पणियाँ (वैकल्पिक)", "notes_optional": "टिप्पणियाँ (वैकल्पिक)",
"cost_optional": "लागत (वैकल्पिक)", "cost_optional": "लागत (वैकल्पिक)",
"duration_minutes": "मिनटों में अवधि (वैकल्पिक)", "duration_minutes": "मिनटों में अवधि (वैकल्पिक)",
"completed_at_optional": "पूर्ण होने का समय (वैकल्पिक, खाली = अभी)",
"completed_at_future_error": "पूर्णता की तारीख भविष्य में नहीं हो सकती।",
"days": "दिन", "days": "दिन",
"day": "दिन", "day": "दिन",
"today": "आज", "today": "आज",
@@ -193,9 +195,14 @@
"use_entity_state": "एंटिटी स्थिति का उपयोग करें (कोई विशेषता नहीं)", "use_entity_state": "एंटिटी स्थिति का उपयोग करें (कोई विशेषता नहीं)",
"trigger_above": "इससे ऊपर ट्रिगर करें", "trigger_above": "इससे ऊपर ट्रिगर करें",
"trigger_below": "इससे नीचे ट्रिगर करें", "trigger_below": "इससे नीचे ट्रिगर करें",
"trigger_equals": "बराबर होने पर ट्रिगर करें (=)",
"trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)",
"for_at_least_minutes": "कम से कम (मिनट)", "for_at_least_minutes": "कम से कम (मिनट)",
"safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)", "safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)",
"safety_interval": "सुरक्षा अंतराल (वैकल्पिक)", "safety_interval": "सुरक्षा अंतराल (वैकल्पिक)",
"trigger_combinator": "ट्रिगर और अंतराल संयोजित करें",
"trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)",
"trigger_combinator_all": "ट्रिगर और अंतराल (दोनों आवश्यक)",
"delta_mode": "डेल्टा मोड", "delta_mode": "डेल्टा मोड",
"from_state_optional": "किस स्थिति से (वैकल्पिक)", "from_state_optional": "किस स्थिति से (वैकल्पिक)",
"to_state_optional": "किस स्थिति तक (वैकल्पिक)", "to_state_optional": "किस स्थिति तक (वैकल्पिक)",
@@ -850,5 +857,17 @@
"gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं", "gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं",
"gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले", "gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले",
"gs_adopt_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: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।"
} }
@@ -129,6 +129,8 @@
"notes_optional": "Megjegyzések (opcionális)", "notes_optional": "Megjegyzések (opcionális)",
"cost_optional": "Költség (opcionális)", "cost_optional": "Költség (opcionális)",
"duration_minutes": "Időtartam percben (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", "days": "nap",
"day": "nap", "day": "nap",
"today": "Ma", "today": "Ma",
@@ -193,9 +195,14 @@
"use_entity_state": "Entitás állapotának használata (attribútum nélkül)", "use_entity_state": "Entitás állapotának használata (attribútum nélkül)",
"trigger_above": "Kiváltás e fölött", "trigger_above": "Kiváltás e fölött",
"trigger_below": "Kiváltás ez alatt", "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)", "for_at_least_minutes": "Legalább ennyi ideig (perc)",
"safety_interval_days": "Biztonsági intervallum (nap, opcionális)", "safety_interval_days": "Biztonsági intervallum (nap, opcionális)",
"safety_interval": "Biztonsági intervallum (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", "delta_mode": "Delta mód",
"from_state_optional": "Kezdő állapot (opcionális)", "from_state_optional": "Kezdő állapot (opcionális)",
"to_state_optional": "Célá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_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_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_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: \"<név>\" — vagy névlista, hogy a kártya több objektumra korlátozódjon."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Note (opzionale)", "notes_optional": "Note (opzionale)",
"cost_optional": "Costo (opzionale)", "cost_optional": "Costo (opzionale)",
"duration_minutes": "Durata in minuti (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", "days": "giorni",
"day": "giorno", "day": "giorno",
"today": "Oggi", "today": "Oggi",
@@ -192,9 +194,14 @@
"use_entity_state": "Usa stato dell'entità (nessun attributo)", "use_entity_state": "Usa stato dell'entità (nessun attributo)",
"trigger_above": "Attivare sopra", "trigger_above": "Attivare sopra",
"trigger_below": "Attivare sotto", "trigger_below": "Attivare sotto",
"trigger_equals": "Attiva quando uguale a (=)",
"trigger_not_equals": "Attiva quando diverso da (≠)",
"for_at_least_minutes": "Per almeno (minuti)", "for_at_least_minutes": "Per almeno (minuti)",
"safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)", "safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)",
"safety_interval": "Intervallo di sicurezza (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", "delta_mode": "Modalità delta",
"from_state_optional": "Dallo stato (opzionale)", "from_state_optional": "Dallo stato (opzionale)",
"to_state_optional": "Allo 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_label": "Primi passi — questi suggerimenti scompaiono man mano che la configurazione cresce",
"gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati", "gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati",
"gs_adopt_chip": "{n} sensori di problemi possono diventare attività di manutenzione", "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: \"<nome>\" — o un elenco di nomi per limitare la scheda a più oggetti."
} }
@@ -129,6 +129,8 @@
"notes_optional": "メモ(任意)", "notes_optional": "メモ(任意)",
"cost_optional": "費用(任意)", "cost_optional": "費用(任意)",
"duration_minutes": "所要時間(分、任意)", "duration_minutes": "所要時間(分、任意)",
"completed_at_optional": "完了日時(任意・空欄 = 現在)",
"completed_at_future_error": "完了日時に未来は指定できません。",
"days": "日", "days": "日",
"day": "日", "day": "日",
"today": "今日", "today": "今日",
@@ -193,9 +195,14 @@
"use_entity_state": "エンティティの状態を使用(属性なし)", "use_entity_state": "エンティティの状態を使用(属性なし)",
"trigger_above": "この値を超えたらトリガー", "trigger_above": "この値を超えたらトリガー",
"trigger_below": "この値を下回ったらトリガー", "trigger_below": "この値を下回ったらトリガー",
"trigger_equals": "値が一致したらトリガー(=)",
"trigger_not_equals": "値が異なればトリガー(≠)",
"for_at_least_minutes": "最低継続時間(分)", "for_at_least_minutes": "最低継続時間(分)",
"safety_interval_days": "安全間隔(日、任意)", "safety_interval_days": "安全間隔(日、任意)",
"safety_interval": "安全間隔(任意)", "safety_interval": "安全間隔(任意)",
"trigger_combinator": "トリガーと間隔の組み合わせ",
"trigger_combinator_any": "トリガーまたは間隔(先に満たした方)",
"trigger_combinator_all": "トリガーと間隔(両方必須)",
"delta_mode": "差分モード", "delta_mode": "差分モード",
"from_state_optional": "変化前の状態(任意)", "from_state_optional": "変化前の状態(任意)",
"to_state_optional": "変化後の状態(任意)", "to_state_optional": "変化後の状態(任意)",
@@ -850,5 +857,17 @@
"gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます", "gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます",
"gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出", "gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出",
"gs_adopt_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: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。"
} }
@@ -129,6 +129,8 @@
"notes_optional": "메모 (선택)", "notes_optional": "메모 (선택)",
"cost_optional": "비용 (선택)", "cost_optional": "비용 (선택)",
"duration_minutes": "소요 시간(분, 선택)", "duration_minutes": "소요 시간(분, 선택)",
"completed_at_optional": "완료 시각 (선택, 비우면 지금)",
"completed_at_future_error": "완료 날짜는 미래일 수 없습니다.",
"days": "일", "days": "일",
"day": "일", "day": "일",
"today": "오늘", "today": "오늘",
@@ -193,9 +195,14 @@
"use_entity_state": "엔티티 상태 사용 (속성 없음)", "use_entity_state": "엔티티 상태 사용 (속성 없음)",
"trigger_above": "초과 시 트리거", "trigger_above": "초과 시 트리거",
"trigger_below": "미만 시 트리거", "trigger_below": "미만 시 트리거",
"trigger_equals": "값이 같으면 트리거 (=)",
"trigger_not_equals": "값이 다르면 트리거 (≠)",
"for_at_least_minutes": "최소 지속 시간 (분)", "for_at_least_minutes": "최소 지속 시간 (분)",
"safety_interval_days": "안전 주기 (일, 선택)", "safety_interval_days": "안전 주기 (일, 선택)",
"safety_interval": "안전 주기 (선택)", "safety_interval": "안전 주기 (선택)",
"trigger_combinator": "트리거와 간격 결합",
"trigger_combinator_any": "트리거 또는 간격 (먼저 충족)",
"trigger_combinator_all": "트리거와 간격 (둘 다 필요)",
"delta_mode": "델타 모드", "delta_mode": "델타 모드",
"from_state_optional": "변경 전 상태 (선택)", "from_state_optional": "변경 전 상태 (선택)",
"to_state_optional": "변경 후 상태 (선택)", "to_state_optional": "변경 후 상태 (선택)",
@@ -850,5 +857,17 @@
"gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다", "gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다",
"gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견", "gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견",
"gs_adopt_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: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notater (valgfritt)", "notes_optional": "Notater (valgfritt)",
"cost_optional": "Kostnad (valgfritt)", "cost_optional": "Kostnad (valgfritt)",
"duration_minutes": "Varighet i minutter (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", "days": "dager",
"day": "dag", "day": "dag",
"today": "I dag", "today": "I dag",
@@ -193,9 +195,14 @@
"use_entity_state": "Bruk entitetstilstand (ingen attributt)", "use_entity_state": "Bruk entitetstilstand (ingen attributt)",
"trigger_above": "Utløs over", "trigger_above": "Utløs over",
"trigger_below": "Utløs under", "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)", "for_at_least_minutes": "I minst (minutter)",
"safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)", "safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)",
"safety_interval": "Sikkerhetsintervall (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", "delta_mode": "Deltamodus",
"from_state_optional": "Fra tilstand (valgfritt)", "from_state_optional": "Fra tilstand (valgfritt)",
"to_state_optional": "Til 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_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_setups_chip": "Foreslåtte oppsett fant {n} enheter med ferdigkoblede utløsere",
"gs_adopt_chip": "{n} problemsensorer kan bli vedlikeholdsoppgaver", "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: \"<navn>\" — eller en liste med navn for å begrense kortet til flere objekter."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notities (optioneel)", "notes_optional": "Notities (optioneel)",
"cost_optional": "Kosten (optioneel)", "cost_optional": "Kosten (optioneel)",
"duration_minutes": "Duur in minuten (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", "days": "dagen",
"day": "dag", "day": "dag",
"today": "Vandaag", "today": "Vandaag",
@@ -192,9 +194,14 @@
"use_entity_state": "Entiteitsstatus gebruiken (geen attribuut)", "use_entity_state": "Entiteitsstatus gebruiken (geen attribuut)",
"trigger_above": "Activeren als boven", "trigger_above": "Activeren als boven",
"trigger_below": "Activeren als onder", "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)", "for_at_least_minutes": "Voor minstens (minuten)",
"safety_interval_days": "Veiligheidsinterval (dagen, optioneel)", "safety_interval_days": "Veiligheidsinterval (dagen, optioneel)",
"safety_interval": "Veiligheidsinterval (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", "delta_mode": "Deltamodus",
"from_state_optional": "Van status (optioneel)", "from_state_optional": "Van status (optioneel)",
"to_state_optional": "Naar 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_label": "Aan de slag — deze tips verdwijnen naarmate je installatie groeit",
"gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden", "gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden",
"gs_adopt_chip": "{n} probleemsensoren kunnen onderhoudstaken worden", "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: \"<naam>\" — of een lijst met namen om de kaart tot meerdere objecten te beperken."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notatki (opcjonalne)", "notes_optional": "Notatki (opcjonalne)",
"cost_optional": "Koszt (opcjonalne)", "cost_optional": "Koszt (opcjonalne)",
"duration_minutes": "Czas trwania w minutach (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", "days": "dni",
"day": "dzień", "day": "dzień",
"today": "Dzisiaj", "today": "Dzisiaj",
@@ -192,9 +194,14 @@
"use_entity_state": "Użyj stanu encji (bez atrybutu)", "use_entity_state": "Użyj stanu encji (bez atrybutu)",
"trigger_above": "Wyzwól powyżej", "trigger_above": "Wyzwól powyżej",
"trigger_below": "Wyzwól poniż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)", "for_at_least_minutes": "Przez co najmniej (minuty)",
"safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)", "safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)",
"safety_interval": "Interwał bezpieczeństwa (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", "delta_mode": "Tryb delta",
"from_state_optional": "Ze stanu (opcjonalne)", "from_state_optional": "Ze stanu (opcjonalne)",
"to_state_optional": "Do 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_label": "Pierwsze kroki — te wskazówki znikają wraz z rozwojem konfiguracji",
"gs_setups_chip": "Sugerowane konfiguracje: znaleziono {n} urządzeń z gotowymi wyzwalaczami", "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_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: \"<nazwa>\" — lub listę nazw, aby ograniczyć kartę do kilku obiektów."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Observações (opcional)", "notes_optional": "Observações (opcional)",
"cost_optional": "Custo (opcional)", "cost_optional": "Custo (opcional)",
"duration_minutes": "Duração em minutos (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", "days": "dias",
"day": "dia", "day": "dia",
"today": "Hoje", "today": "Hoje",
@@ -193,9 +195,14 @@
"use_entity_state": "Usar o estado da entidade (sem atributo)", "use_entity_state": "Usar o estado da entidade (sem atributo)",
"trigger_above": "Acionar acima de", "trigger_above": "Acionar acima de",
"trigger_below": "Acionar abaixo 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)", "for_at_least_minutes": "Por pelo menos (minutos)",
"safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval_days": "Intervalo de segurança (dias, opcional)",
"safety_interval": "Intervalo de segurança (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", "delta_mode": "Modo delta",
"from_state_optional": "Do estado (opcional)", "from_state_optional": "Do estado (opcional)",
"to_state_optional": "Para o 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_label": "Primeiros passos — estas dicas somem conforme a configuração cresce",
"gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados", "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_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: \"<nome>\" — ou uma lista de nomes para limitar o cartão a vários objetos."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notas (opcional)", "notes_optional": "Notas (opcional)",
"cost_optional": "Custo (opcional)", "cost_optional": "Custo (opcional)",
"duration_minutes": "Duração em minutos (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", "days": "dias",
"day": "dia", "day": "dia",
"today": "Hoje", "today": "Hoje",
@@ -192,9 +194,14 @@
"use_entity_state": "Usar estado da entidade (sem atributo)", "use_entity_state": "Usar estado da entidade (sem atributo)",
"trigger_above": "Acionar acima de", "trigger_above": "Acionar acima de",
"trigger_below": "Acionar abaixo 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)", "for_at_least_minutes": "Durante pelo menos (minutos)",
"safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval_days": "Intervalo de segurança (dias, opcional)",
"safety_interval": "Intervalo de segurança (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", "delta_mode": "Modo delta",
"from_state_optional": "Do estado (opcional)", "from_state_optional": "Do estado (opcional)",
"to_state_optional": "Para o 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_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_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_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: \"<nome>\" — ou uma lista de nomes para limitar o cartão a vários objetos."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Примечания (опционально)", "notes_optional": "Примечания (опционально)",
"cost_optional": "Стоимость (опционально)", "cost_optional": "Стоимость (опционально)",
"duration_minutes": "Длительность в минутах (опционально)", "duration_minutes": "Длительность в минутах (опционально)",
"completed_at_optional": "Выполнено (необязательно, пусто = сейчас)",
"completed_at_future_error": "Дата выполнения не может быть в будущем.",
"days": "дней", "days": "дней",
"day": "день", "day": "день",
"today": "Сегодня", "today": "Сегодня",
@@ -192,9 +194,14 @@
"use_entity_state": "Использовать состояние сущности (без атрибута)", "use_entity_state": "Использовать состояние сущности (без атрибута)",
"trigger_above": "Срабатывать выше", "trigger_above": "Срабатывать выше",
"trigger_below": "Срабатывать ниже", "trigger_below": "Срабатывать ниже",
"trigger_equals": "Срабатывать при равенстве (=)",
"trigger_not_equals": "Срабатывать при отличии от (≠)",
"for_at_least_minutes": "Не менее (минут)", "for_at_least_minutes": "Не менее (минут)",
"safety_interval_days": "Интервал безопасности (дни, опционально)", "safety_interval_days": "Интервал безопасности (дни, опционально)",
"safety_interval": "Интервал безопасности (опционально)", "safety_interval": "Интервал безопасности (опционально)",
"trigger_combinator": "Совместить триггер и интервал",
"trigger_combinator_any": "Триггер или интервал (что раньше)",
"trigger_combinator_all": "Триггер и интервал (оба условия)",
"delta_mode": "Режим дельты", "delta_mode": "Режим дельты",
"from_state_optional": "Из состояния (опционально)", "from_state_optional": "Из состояния (опционально)",
"to_state_optional": "В состояние (опционально)", "to_state_optional": "В состояние (опционально)",
@@ -850,5 +857,17 @@
"gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки", "gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки",
"gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами", "gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами",
"gs_adopt_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: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Anteckningar (valfritt)", "notes_optional": "Anteckningar (valfritt)",
"cost_optional": "Kostnad (valfritt)", "cost_optional": "Kostnad (valfritt)",
"duration_minutes": "Varaktighet i minuter (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", "days": "dagar",
"day": "dag", "day": "dag",
"today": "Idag", "today": "Idag",
@@ -192,9 +194,14 @@
"use_entity_state": "Använd entitetstillstånd (inget attribut)", "use_entity_state": "Använd entitetstillstånd (inget attribut)",
"trigger_above": "Utlös över", "trigger_above": "Utlös över",
"trigger_below": "Utlös under", "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)", "for_at_least_minutes": "Under minst (minuter)",
"safety_interval_days": "Säkerhetsintervall (dagar, valfritt)", "safety_interval_days": "Säkerhetsintervall (dagar, valfritt)",
"safety_interval": "Säkerhetsintervall (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", "delta_mode": "Delta-läge",
"from_state_optional": "Från tillstånd (valfritt)", "from_state_optional": "Från tillstånd (valfritt)",
"to_state_optional": "Till 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_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_setups_chip": "Föreslagna uppsättningar hittade {n} enheter med förkopplade utlösare",
"gs_adopt_chip": "{n} problemsensorer kan bli underhållsuppgifter", "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: \"<namn>\" — eller en lista med namn för att begränsa kortet till flera objekt."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notlar (isteğe bağlı)", "notes_optional": "Notlar (isteğe bağlı)",
"cost_optional": "Maliyet (isteğe bağlı)", "cost_optional": "Maliyet (isteğe bağlı)",
"duration_minutes": "Dakika cinsinden süre (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", "days": "gün",
"day": "gün", "day": "gün",
"today": "Bugün", "today": "Bugün",
@@ -193,9 +195,14 @@
"use_entity_state": "Varlık durumunu kullan (öznitelik yok)", "use_entity_state": "Varlık durumunu kullan (öznitelik yok)",
"trigger_above": "Üstünde tetikle", "trigger_above": "Üstünde tetikle",
"trigger_below": "Altında 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)", "for_at_least_minutes": "En az (dakika)",
"safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)", "safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)",
"safety_interval": "Güvenlik aralığı (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", "delta_mode": "Fark modu",
"from_state_optional": "Başlangıç durumu (isteğe bağlı)", "from_state_optional": "Başlangıç durumu (isteğe bağlı)",
"to_state_optional": "Hedef durum (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_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_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_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: \"<ad>\" — veya kartı birden çok nesneyle sınırlamak için ad listesi."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Примітки (необов'язково)", "notes_optional": "Примітки (необов'язково)",
"cost_optional": "Вартість (необов'язково)", "cost_optional": "Вартість (необов'язково)",
"duration_minutes": "Тривалість у хвилинах (необов'язково)", "duration_minutes": "Тривалість у хвилинах (необов'язково)",
"completed_at_optional": "Виконано (необов'язково, порожньо = зараз)",
"completed_at_future_error": "Дата виконання не може бути в майбутньому.",
"days": "днів", "days": "днів",
"day": "день", "day": "день",
"today": "Сьогодні", "today": "Сьогодні",
@@ -192,9 +194,14 @@
"use_entity_state": "Використовувати стан об'єкта (без атрибута)", "use_entity_state": "Використовувати стан об'єкта (без атрибута)",
"trigger_above": "Спрацювати, коли вище", "trigger_above": "Спрацювати, коли вище",
"trigger_below": "Спрацювати, коли нижче", "trigger_below": "Спрацювати, коли нижче",
"trigger_equals": "Спрацьовувати при рівності (=)",
"trigger_not_equals": "Спрацьовувати при відмінності від (≠)",
"for_at_least_minutes": "Протягом не менше (хвилин)", "for_at_least_minutes": "Протягом не менше (хвилин)",
"safety_interval_days": "Страховий інтервал (дні, необов'язково)", "safety_interval_days": "Страховий інтервал (дні, необов'язково)",
"safety_interval": "Страховий інтервал (необов'язково)", "safety_interval": "Страховий інтервал (необов'язково)",
"trigger_combinator": "Поєднати тригер та інтервал",
"trigger_combinator_any": "Тригер або інтервал (що раніше)",
"trigger_combinator_all": "Тригер та інтервал (обидва потрібні)",
"delta_mode": "Режим дельти", "delta_mode": "Режим дельти",
"from_state_optional": "З стану (необов'язково)", "from_state_optional": "З стану (необов'язково)",
"to_state_optional": "До стану (необов'язково)", "to_state_optional": "До стану (необов'язково)",
@@ -850,5 +857,17 @@
"gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування", "gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування",
"gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами", "gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами",
"gs_adopt_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: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами."
} }
@@ -129,6 +129,8 @@
"notes_optional": "备注 (可选)", "notes_optional": "备注 (可选)",
"cost_optional": "成本 (可选)", "cost_optional": "成本 (可选)",
"duration_minutes": "耗时 (分钟, 可选)", "duration_minutes": "耗时 (分钟, 可选)",
"completed_at_optional": "完成时间(可选,留空 = 现在)",
"completed_at_future_error": "完成日期不能是未来时间。",
"days": "天", "days": "天",
"day": "天", "day": "天",
"today": "今天", "today": "今天",
@@ -193,9 +195,14 @@
"use_entity_state": "使用实体状态 (不使用属性)", "use_entity_state": "使用实体状态 (不使用属性)",
"trigger_above": "高于此值触发", "trigger_above": "高于此值触发",
"trigger_below": "低于此值触发", "trigger_below": "低于此值触发",
"trigger_equals": "等于时触发(=",
"trigger_not_equals": "不等于时触发(≠)",
"for_at_least_minutes": "持续至少 (分钟)", "for_at_least_minutes": "持续至少 (分钟)",
"safety_interval_days": "安全间隔 (天, 可选)", "safety_interval_days": "安全间隔 (天, 可选)",
"safety_interval": "安全间隔 (可选)", "safety_interval": "安全间隔 (可选)",
"trigger_combinator": "组合触发器与间隔",
"trigger_combinator_any": "触发器或间隔(先到者)",
"trigger_combinator_all": "触发器与间隔(两者皆需)",
"delta_mode": "增量模式", "delta_mode": "增量模式",
"from_state_optional": "起始状态 (可选)", "from_state_optional": "起始状态 (可选)",
"to_state_optional": "目标状态 (可选)", "to_state_optional": "目标状态 (可选)",
@@ -850,5 +857,17 @@
"gs_label": "入门提示——随着配置的完善,这些提示会自动消失", "gs_label": "入门提示——随着配置的完善,这些提示会自动消失",
"gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)", "gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)",
"gs_adopt_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: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。"
} }
@@ -5,11 +5,12 @@
* (clock vs trending-up), prediction-confidence pill, projected recurrences * (clock vs trending-up), prediction-confidence pill, projected recurrences
* at 55% opacity, today-pill highlight, empty-day collapsing in the year view. * at 55% opacity, today-pill highlight, empty-day collapsing in the year view.
* *
* Click on an event fires an ``ll-custom`` event with payload * Click on an event opens the task quick-actions dialog (future events) or
* ``{type: "maintenance-supporter:open-task", entry_id, task_id}``. The * the history-edit dialog (past events) DIRECTLY via the shared dialog-mount
* dashboard-strategy bundle's document-level handler picks that up and * so clicks work on any dashboard, with or without the strategy bundle.
* either opens the task dialog in-place (preferred) or deep-links into the * When the dialog helper cannot mount, an ``ll-custom`` event
* panel as a fallback. * (``{type: "maintenance-supporter:open-task", entry_id, task_id}``) is
* dispatched as the fallback for the strategy bundle's document listener.
* *
* Card config: * Card config:
* *
@@ -36,6 +37,7 @@ import {
import { calendarStyles } from "./calendar-styles"; import { calendarStyles } from "./calendar-styles";
import { sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles"; import { sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles";
import { registerCustomCard } from "./helpers/register-card"; import { registerCustomCard } from "./helpers/register-card";
import { openHistoryEditDialog, openTaskQuickActions } from "./dialog-mount";
import type { import type {
HomeAssistant, HomeAssistant,
MaintenanceObjectResponse, MaintenanceObjectResponse,
@@ -200,23 +202,17 @@ export class MaintenanceCalendarCard extends LitElement {
private _onEventClick(ev: CalendarEvent): void { private _onEventClick(ev: CalendarEvent): void {
// Past events carry a history_timestamp — those open the history-edit // Past events carry a history_timestamp — those open the history-edit
// dialog instead of the task editor. Future / next_due events open // dialog instead of the task editor. Future / next_due events open the
// the task editor as usual. // 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) { if (ev.history_timestamp) {
this.dispatchEvent( void this._openHistoryEntry(ev);
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,
}),
);
return; return;
} }
if (openTaskQuickActions(ev.entry_id, ev.task_id)) return;
this.dispatchEvent( this.dispatchEvent(
new CustomEvent("ll-custom", { new CustomEvent("ll-custom", {
detail: { 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<void> {
try {
const resp = await this.hass.connection.sendMessagePromise<{
tasks?: Array<{ id: string; history?: Array<Record<string, unknown>> }>;
}>({ 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() { render() {
if (!this.hass) return nothing; if (!this.hass) return nothing;
@@ -456,11 +494,11 @@ export class MaintenanceCalendarCard extends LitElement {
// and user-filter on/off. Same pattern as MaintenanceSupporterCardEditor — // and user-filter on/off. Same pattern as MaintenanceSupporterCardEditor —
// LitElement with setConfig + dispatched config-changed. // LitElement with setConfig + dispatched config-changed.
const WINDOW_DAY_OPTIONS: Array<{ value: WindowDays; label: string }> = [ const WINDOW_DAY_KEYS: Array<{ value: WindowDays; key: string }> = [
{ value: 7, label: "Week (7 days)" }, { value: 7, key: "cal_editor_window_week" },
{ value: 14, label: "Fortnight (14 days)" }, { value: 14, key: "cal_editor_window_fortnight" },
{ value: 30, label: "Month (30 days, default)" }, { value: 30, key: "cal_editor_window_month" },
{ value: 365, label: "Year (365 days, empty days collapsed)" }, { value: 365, key: "cal_editor_window_year" },
]; ];
class MaintenanceCalendarCardEditor extends LitElement { class MaintenanceCalendarCardEditor extends LitElement {
@@ -469,10 +507,21 @@ class MaintenanceCalendarCardEditor extends LitElement {
type: "custom:maintenance-supporter-calendar-card", type: "custom:maintenance-supporter-calendar-card",
}; };
private get _lang(): string {
return langOf(this.hass);
}
setConfig(config: CalendarCardConfig): void { setConfig(config: CalendarCardConfig): void {
this._config = { ...config }; 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 { private _valueChanged(key: keyof CalendarCardConfig, value: unknown): void {
const newConfig = { ...this._config, [key]: value } as CalendarCardConfig; const newConfig = { ...this._config, [key]: value } as CalendarCardConfig;
// Drop default-equivalent values so saved YAML stays minimal // Drop default-equivalent values so saved YAML stays minimal
@@ -502,6 +551,7 @@ class MaintenanceCalendarCardEditor extends LitElement {
} }
render() { render() {
const L = this._lang;
const currentWindow = this._config.window_days ?? 30; const currentWindow = this._config.window_days ?? 30;
const showChips = this._config.show_window_chips !== false; const showChips = this._config.show_window_chips !== false;
const showUserFilter = this._config.show_user_filter !== false; const showUserFilter = this._config.show_user_filter !== false;
@@ -510,7 +560,7 @@ class MaintenanceCalendarCardEditor extends LitElement {
return html` return html`
<div class="editor"> <div class="editor">
<div class="row"> <div class="row">
<label for="title">Title (optional)</label> <label for="title">${t("card_title", L)}</label>
<input <input
id="title" id="title"
type="text" type="text"
@@ -520,7 +570,7 @@ class MaintenanceCalendarCardEditor extends LitElement {
/> />
</div> </div>
<div class="row"> <div class="row">
<label for="window">Default window</label> <label for="window">${t("cal_editor_window", L)}</label>
<select <select
id="window" id="window"
@change=${(e: Event) => @change=${(e: Event) =>
@@ -529,14 +579,14 @@ class MaintenanceCalendarCardEditor extends LitElement {
Number((e.target as HTMLSelectElement).value) as WindowDays, Number((e.target as HTMLSelectElement).value) as WindowDays,
)} )}
> >
${WINDOW_DAY_OPTIONS.map( ${WINDOW_DAY_KEYS.map(
(o) => (o) =>
html`<option value="${o.value}" ?selected=${o.value === currentWindow}>${o.label}</option>`, html`<option value="${o.value}" ?selected=${o.value === currentWindow}>${t(o.key, L)}</option>`,
)} )}
</select> </select>
</div> </div>
<div class="row toggle"> <div class="row toggle">
<label for="chips">Show window chips inside the card</label> <label for="chips">${t("cal_editor_show_chips", L)}</label>
<input <input
id="chips" id="chips"
type="checkbox" type="checkbox"
@@ -548,12 +598,9 @@ class MaintenanceCalendarCardEditor extends LitElement {
)} )}
/> />
</div> </div>
<div class="hint"> <div class="hint">${t("cal_editor_chips_hint", L)}</div>
Hide the chips when the card is embedded in a strategy view that
already serves as the window selector.
</div>
<div class="row toggle"> <div class="row toggle">
<label for="userf">Show user filter dropdown</label> <label for="userf">${t("cal_editor_show_user_filter", L)}</label>
<input <input
id="userf" id="userf"
type="checkbox" type="checkbox"
@@ -566,7 +613,7 @@ class MaintenanceCalendarCardEditor extends LitElement {
/> />
</div> </div>
<div class="row"> <div class="row">
<label for="userv">Default user filter</label> <label for="userv">${t("cal_editor_default_user", L)}</label>
<select <select
id="userv" id="userv"
@change=${(e: Event) => @change=${(e: Event) =>
@@ -575,14 +622,14 @@ class MaintenanceCalendarCardEditor extends LitElement {
(e.target as HTMLSelectElement).value, (e.target as HTMLSelectElement).value,
)} )}
> >
<option value="" ?selected=${userFilter === ""}>All users</option> <option value="" ?selected=${userFilter === ""}>${t("all_users", L)}</option>
<option value="current_user" ?selected=${userFilter === "current_user"}> <option value="current_user" ?selected=${userFilter === "current_user"}>
My tasks (current user) ${t("cal_editor_my_tasks", L)}
</option> </option>
</select> </select>
</div> </div>
<div class="row toggle"> <div class="row toggle">
<label for="objf">Show object filter dropdown</label> <label for="objf">${t("cal_editor_show_object_filter", L)}</label>
<input <input
id="objf" id="objf"
type="checkbox" type="checkbox"
@@ -594,10 +641,7 @@ class MaintenanceCalendarCardEditor extends LitElement {
)} )}
/> />
</div> </div>
<div class="hint"> <div class="hint">${t("cal_editor_object_hint", L)}</div>
Pre-select one object via YAML: object_filter: "&lt;object name&gt;" or a
list of names to restrict the card to several objects.
</div>
</div> </div>
`; `;
} }
@@ -50,6 +50,12 @@ export function renderTriggerProgress(row: TaskRow | MaintenanceTask) {
const range = high - below || 1; const range = high - below || 1;
pct = Math.min(100, Math.max(0, ((high - val) / range) * 100)); pct = Math.min(100, Math.max(0, ((high - val) / range) * 100));
label = `${val.toFixed(1)} / ${below} ${unit}`; 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 { } else {
return nothing; return nothing;
} }
@@ -98,6 +98,8 @@ export function renderTriggerSection(task: MaintenanceTask, ctx: SparklineContex
${triggerType === "threshold" ? html` ${triggerType === "threshold" ? html`
${tc.trigger_above != null ? html`<span class="trigger-limit-item"><span class="dot warn" aria-hidden="true"></span> ${t("threshold_above", L)}: ${tc.trigger_above} ${unit}</span>` : nothing} ${tc.trigger_above != null ? html`<span class="trigger-limit-item"><span class="dot warn" aria-hidden="true"></span> ${t("threshold_above", L)}: ${tc.trigger_above} ${unit}</span>` : nothing}
${tc.trigger_below != null ? html`<span class="trigger-limit-item"><span class="dot warn" aria-hidden="true"></span> ${t("threshold_below", L)}: ${tc.trigger_below} ${unit}</span>` : nothing} ${tc.trigger_below != null ? html`<span class="trigger-limit-item"><span class="dot warn" aria-hidden="true"></span> ${t("threshold_below", L)}: ${tc.trigger_below} ${unit}</span>` : nothing}
${tc.trigger_equals != null ? html`<span class="trigger-limit-item"><span class="dot warn" aria-hidden="true"></span> = ${tc.trigger_equals} ${unit}</span>` : nothing}
${tc.trigger_not_equals != null ? html`<span class="trigger-limit-item"><span class="dot warn" aria-hidden="true"></span> ≠ ${tc.trigger_not_equals} ${unit}</span>` : nothing}
${tc.trigger_for_minutes ? html`<span class="trigger-limit-item"><span class="dot range" aria-hidden="true"></span> ${t("for_minutes", L)}: ${tc.trigger_for_minutes}</span>` : nothing} ${tc.trigger_for_minutes ? html`<span class="trigger-limit-item"><span class="dot range" aria-hidden="true"></span> ${t("for_minutes", L)}: ${tc.trigger_for_minutes}</span>` : nothing}
` : nothing} ` : nothing}
${triggerType === "state_change" ? html` ${triggerType === "state_change" ? html`
@@ -56,6 +56,10 @@ export interface TriggerConfig {
type?: string; // "threshold" | "counter" | "state_change" | "runtime" type?: string; // "threshold" | "counter" | "state_change" | "runtime"
trigger_above?: number | null; trigger_above?: number | null;
trigger_below?: 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_for_minutes?: number;
trigger_target_value?: number; trigger_target_value?: number;
trigger_delta_mode?: boolean; trigger_delta_mode?: boolean;
@@ -48,6 +48,8 @@ const FIELD_LABEL_KEYS: Record<string, string> = {
environmental_attribute: "environmental_attribute_optional", environmental_attribute: "environmental_attribute_optional",
trigger_above: "trigger_above", trigger_above: "trigger_above",
trigger_below: "trigger_below", trigger_below: "trigger_below",
trigger_equals: "trigger_equals",
trigger_not_equals: "trigger_not_equals",
trigger_for_minutes: "trigger_for_minutes", trigger_for_minutes: "trigger_for_minutes",
}; };
@@ -128,6 +128,8 @@
"notes_optional": "Poznámky (volitelné)", "notes_optional": "Poznámky (volitelné)",
"cost_optional": "Náklady (volitelné)", "cost_optional": "Náklady (volitelné)",
"duration_minutes": "Doba trvání v minutách (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í", "days": "dní",
"day": "den", "day": "den",
"today": "Dnes", "today": "Dnes",
@@ -192,9 +194,14 @@
"use_entity_state": "Použít stav entity (bez atributu)", "use_entity_state": "Použít stav entity (bez atributu)",
"trigger_above": "Spustit nad", "trigger_above": "Spustit nad",
"trigger_below": "Spustit pod", "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)", "for_at_least_minutes": "Po dobu alespoň (minut)",
"safety_interval_days": "Bezpečnostní interval (dny, volitelný)", "safety_interval_days": "Bezpečnostní interval (dny, volitelný)",
"safety_interval": "Bezpečnostní interval (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", "delta_mode": "Režim delta",
"from_state_optional": "Ze stavu (volitelné)", "from_state_optional": "Ze stavu (volitelné)",
"to_state_optional": "Do 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_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_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_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: \"<název>\" — nebo seznam názvů pro omezení karty na více objektů."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Noter (valgfrit)", "notes_optional": "Noter (valgfrit)",
"cost_optional": "Omkostning (valgfrit)", "cost_optional": "Omkostning (valgfrit)",
"duration_minutes": "Varighed i minutter (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", "days": "dage",
"day": "dag", "day": "dag",
"today": "I dag", "today": "I dag",
@@ -193,9 +195,14 @@
"use_entity_state": "Brug enhedstilstand (ingen attribut)", "use_entity_state": "Brug enhedstilstand (ingen attribut)",
"trigger_above": "Udløs over", "trigger_above": "Udløs over",
"trigger_below": "Udløs under", "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)", "for_at_least_minutes": "I mindst (minutter)",
"safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)", "safety_interval_days": "Sikkerhedsinterval (dage, valgfrit)",
"safety_interval": "Sikkerhedsinterval (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", "delta_mode": "Delta-tilstand",
"from_state_optional": "Fra tilstand (valgfrit)", "from_state_optional": "Fra tilstand (valgfrit)",
"to_state_optional": "Til 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_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_setups_chip": "Foreslåede opsætninger fandt {n} enheder med forudindstillede udløsere",
"gs_adopt_chip": "{n} problemsensorer kan blive vedligeholdelsesopgaver", "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: \"<navn>\" — eller en liste af navne for at begrænse kortet til flere objekter."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notizen (optional)", "notes_optional": "Notizen (optional)",
"cost_optional": "Kosten (optional)", "cost_optional": "Kosten (optional)",
"duration_minutes": "Dauer in Minuten (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", "days": "Tage",
"day": "Tag", "day": "Tag",
"today": "Heute", "today": "Heute",
@@ -193,9 +195,14 @@
"use_entity_state": "Entitäts-Zustand verwenden (kein Attribut)", "use_entity_state": "Entitäts-Zustand verwenden (kein Attribut)",
"trigger_above": "Auslösen wenn über", "trigger_above": "Auslösen wenn über",
"trigger_below": "Auslösen wenn unter", "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)", "for_at_least_minutes": "Für mindestens (Minuten)",
"safety_interval_days": "Sicherheitsintervall (Tage, optional)", "safety_interval_days": "Sicherheitsintervall (Tage, optional)",
"safety_interval": "Sicherheitsintervall (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", "delta_mode": "Delta-Modus",
"from_state_optional": "Von Zustand (optional)", "from_state_optional": "Von Zustand (optional)",
"to_state_optional": "Zu 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_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_setups_chip": "Vorgeschlagene Setups: {n} Geräte mit vorverdrahteten Auslösern gefunden",
"gs_adopt_chip": "{n} Problem-Sensoren können zu Wartungsaufgaben werden", "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: \"<Objektname>\" — oder eine Namensliste, um die Karte auf mehrere Objekte zu beschränken."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notes (optional)", "notes_optional": "Notes (optional)",
"cost_optional": "Cost (optional)", "cost_optional": "Cost (optional)",
"duration_minutes": "Duration in minutes (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", "days": "days",
"day": "day", "day": "day",
"today": "Today", "today": "Today",
@@ -193,9 +195,14 @@
"use_entity_state": "Use entity state (no attribute)", "use_entity_state": "Use entity state (no attribute)",
"trigger_above": "Trigger above", "trigger_above": "Trigger above",
"trigger_below": "Trigger below", "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)", "for_at_least_minutes": "For at least (minutes)",
"safety_interval_days": "Safety interval (days, optional)", "safety_interval_days": "Safety interval (days, optional)",
"safety_interval": "Safety interval (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", "delta_mode": "Delta mode",
"from_state_optional": "From state (optional)", "from_state_optional": "From state (optional)",
"to_state_optional": "To state (optional)", "to_state_optional": "To state (optional)",
@@ -850,5 +857,17 @@
"gs_label": "Getting started — these hints retire as your setup grows", "gs_label": "Getting started — these hints retire as your setup grows",
"gs_setups_chip": "Suggested setups found {n} devices with pre-wired triggers", "gs_setups_chip": "Suggested setups found {n} devices with pre-wired triggers",
"gs_adopt_chip": "{n} problem sensors can become maintenance tasks", "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: \"<object name>\" — or a list of names to restrict the card to several objects."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notas (opcional)", "notes_optional": "Notas (opcional)",
"cost_optional": "Coste (opcional)", "cost_optional": "Coste (opcional)",
"duration_minutes": "Duración en minutos (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", "days": "días",
"day": "día", "day": "día",
"today": "Hoy", "today": "Hoy",
@@ -192,9 +194,14 @@
"use_entity_state": "Usar estado de la entidad (sin atributo)", "use_entity_state": "Usar estado de la entidad (sin atributo)",
"trigger_above": "Activar por encima de", "trigger_above": "Activar por encima de",
"trigger_below": "Activar por debajo 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)", "for_at_least_minutes": "Durante al menos (minutos)",
"safety_interval_days": "Intervalo de seguridad (días, opcional)", "safety_interval_days": "Intervalo de seguridad (días, opcional)",
"safety_interval": "Intervalo de seguridad (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", "delta_mode": "Modo delta",
"from_state_optional": "Desde estado (opcional)", "from_state_optional": "Desde estado (opcional)",
"to_state_optional": "Hasta 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_label": "Primeros pasos: estas sugerencias desaparecen a medida que crece tu configuración",
"gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados", "gs_setups_chip": "Configuraciones sugeridas: {n} dispositivos con disparadores preconfigurados",
"gs_adopt_chip": "{n} sensores de problemas pueden convertirse en tareas de mantenimiento", "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: \"<nombre>\" — o una lista de nombres para limitar la tarjeta a varios objetos."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Muistiinpanot (valinnainen)", "notes_optional": "Muistiinpanot (valinnainen)",
"cost_optional": "Kustannus (valinnainen)", "cost_optional": "Kustannus (valinnainen)",
"duration_minutes": "Kesto minuutteina (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ää", "days": "päivää",
"day": "päivä", "day": "päivä",
"today": "Tänään", "today": "Tänään",
@@ -193,9 +195,14 @@
"use_entity_state": "Käytä entiteetin tilaa (ei attribuuttia)", "use_entity_state": "Käytä entiteetin tilaa (ei attribuuttia)",
"trigger_above": "Laukaise yli", "trigger_above": "Laukaise yli",
"trigger_below": "Laukaise alle", "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)", "for_at_least_minutes": "Vähintään (minuuttia)",
"safety_interval_days": "Turvaväli (päivää, valinnainen)", "safety_interval_days": "Turvaväli (päivää, valinnainen)",
"safety_interval": "Turvaväli (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", "delta_mode": "Delta-tila",
"from_state_optional": "Lähtötilasta (valinnainen)", "from_state_optional": "Lähtötilasta (valinnainen)",
"to_state_optional": "Kohdetilaan (valinnainen)", "to_state_optional": "Kohdetilaan (valinnainen)",
@@ -850,5 +857,17 @@
"gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa", "gs_label": "Aloitus — nämä vihjeet poistuvat asennuksen kasvaessa",
"gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla", "gs_setups_chip": "Ehdotetut asetukset löysivät {n} laitetta valmiilla laukaisimilla",
"gs_adopt_chip": "{n} ongelma-anturia voi muuttua huoltotehtäviksi", "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: \"<nimi>\" — tai nimilista rajataksesi kortin useisiin kohteisiin."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notes (optionnel)", "notes_optional": "Notes (optionnel)",
"cost_optional": "Coût (optionnel)", "cost_optional": "Coût (optionnel)",
"duration_minutes": "Durée en minutes (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", "days": "jours",
"day": "jour", "day": "jour",
"today": "Aujourd'hui", "today": "Aujourd'hui",
@@ -192,9 +194,14 @@
"use_entity_state": "Utiliser l'état de l'entité (pas d'attribut)", "use_entity_state": "Utiliser l'état de l'entité (pas d'attribut)",
"trigger_above": "Déclencher au-dessus de", "trigger_above": "Déclencher au-dessus de",
"trigger_below": "Déclencher en dessous 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)", "for_at_least_minutes": "Pendant au moins (minutes)",
"safety_interval_days": "Intervalle de sécurité (jours, optionnel)", "safety_interval_days": "Intervalle de sécurité (jours, optionnel)",
"safety_interval": "Intervalle de sécurité (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", "delta_mode": "Mode delta",
"from_state_optional": "État source (optionnel)", "from_state_optional": "État source (optionnel)",
"to_state_optional": "État cible (optionnel)", "to_state_optional": "État cible (optionnel)",
@@ -850,5 +857,17 @@
"gs_label": "Premiers pas — ces conseils disparaissent à mesure que votre configuration grandit", "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_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_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 : \"<nom>\" — ou une liste de noms pour limiter la carte à plusieurs objets."
} }
@@ -129,6 +129,8 @@
"notes_optional": "टिप्पणियाँ (वैकल्पिक)", "notes_optional": "टिप्पणियाँ (वैकल्पिक)",
"cost_optional": "लागत (वैकल्पिक)", "cost_optional": "लागत (वैकल्पिक)",
"duration_minutes": "मिनटों में अवधि (वैकल्पिक)", "duration_minutes": "मिनटों में अवधि (वैकल्पिक)",
"completed_at_optional": "पूर्ण होने का समय (वैकल्पिक, खाली = अभी)",
"completed_at_future_error": "पूर्णता की तारीख भविष्य में नहीं हो सकती।",
"days": "दिन", "days": "दिन",
"day": "दिन", "day": "दिन",
"today": "आज", "today": "आज",
@@ -193,9 +195,14 @@
"use_entity_state": "एंटिटी स्थिति का उपयोग करें (कोई विशेषता नहीं)", "use_entity_state": "एंटिटी स्थिति का उपयोग करें (कोई विशेषता नहीं)",
"trigger_above": "इससे ऊपर ट्रिगर करें", "trigger_above": "इससे ऊपर ट्रिगर करें",
"trigger_below": "इससे नीचे ट्रिगर करें", "trigger_below": "इससे नीचे ट्रिगर करें",
"trigger_equals": "बराबर होने पर ट्रिगर करें (=)",
"trigger_not_equals": "भिन्न होने पर ट्रिगर करें (≠)",
"for_at_least_minutes": "कम से कम (मिनट)", "for_at_least_minutes": "कम से कम (मिनट)",
"safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)", "safety_interval_days": "सुरक्षा अंतराल (दिन, वैकल्पिक)",
"safety_interval": "सुरक्षा अंतराल (वैकल्पिक)", "safety_interval": "सुरक्षा अंतराल (वैकल्पिक)",
"trigger_combinator": "ट्रिगर और अंतराल संयोजित करें",
"trigger_combinator_any": "ट्रिगर या अंतराल (जो पहले हो)",
"trigger_combinator_all": "ट्रिगर और अंतराल (दोनों आवश्यक)",
"delta_mode": "डेल्टा मोड", "delta_mode": "डेल्टा मोड",
"from_state_optional": "किस स्थिति से (वैकल्पिक)", "from_state_optional": "किस स्थिति से (वैकल्पिक)",
"to_state_optional": "किस स्थिति तक (वैकल्पिक)", "to_state_optional": "किस स्थिति तक (वैकल्पिक)",
@@ -850,5 +857,17 @@
"gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं", "gs_label": "शुरुआत — सेटअप बढ़ने पर ये संकेत हट जाते हैं",
"gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले", "gs_setups_chip": "सुझाए गए सेटअप: पूर्व-निर्धारित ट्रिगर वाले {n} उपकरण मिले",
"gs_adopt_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: \"<नाम>\" — या कार्ड को कई ऑब्जेक्ट तक सीमित करने हेतु नामों की सूची।"
} }
@@ -129,6 +129,8 @@
"notes_optional": "Megjegyzések (opcionális)", "notes_optional": "Megjegyzések (opcionális)",
"cost_optional": "Költség (opcionális)", "cost_optional": "Költség (opcionális)",
"duration_minutes": "Időtartam percben (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", "days": "nap",
"day": "nap", "day": "nap",
"today": "Ma", "today": "Ma",
@@ -193,9 +195,14 @@
"use_entity_state": "Entitás állapotának használata (attribútum nélkül)", "use_entity_state": "Entitás állapotának használata (attribútum nélkül)",
"trigger_above": "Kiváltás e fölött", "trigger_above": "Kiváltás e fölött",
"trigger_below": "Kiváltás ez alatt", "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)", "for_at_least_minutes": "Legalább ennyi ideig (perc)",
"safety_interval_days": "Biztonsági intervallum (nap, opcionális)", "safety_interval_days": "Biztonsági intervallum (nap, opcionális)",
"safety_interval": "Biztonsági intervallum (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", "delta_mode": "Delta mód",
"from_state_optional": "Kezdő állapot (opcionális)", "from_state_optional": "Kezdő állapot (opcionális)",
"to_state_optional": "Célá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_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_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_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: \"<név>\" — vagy névlista, hogy a kártya több objektumra korlátozódjon."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Note (opzionale)", "notes_optional": "Note (opzionale)",
"cost_optional": "Costo (opzionale)", "cost_optional": "Costo (opzionale)",
"duration_minutes": "Durata in minuti (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", "days": "giorni",
"day": "giorno", "day": "giorno",
"today": "Oggi", "today": "Oggi",
@@ -192,9 +194,14 @@
"use_entity_state": "Usa stato dell'entità (nessun attributo)", "use_entity_state": "Usa stato dell'entità (nessun attributo)",
"trigger_above": "Attivare sopra", "trigger_above": "Attivare sopra",
"trigger_below": "Attivare sotto", "trigger_below": "Attivare sotto",
"trigger_equals": "Attiva quando uguale a (=)",
"trigger_not_equals": "Attiva quando diverso da (≠)",
"for_at_least_minutes": "Per almeno (minuti)", "for_at_least_minutes": "Per almeno (minuti)",
"safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)", "safety_interval_days": "Intervallo di sicurezza (giorni, opzionale)",
"safety_interval": "Intervallo di sicurezza (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", "delta_mode": "Modalità delta",
"from_state_optional": "Dallo stato (opzionale)", "from_state_optional": "Dallo stato (opzionale)",
"to_state_optional": "Allo 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_label": "Primi passi — questi suggerimenti scompaiono man mano che la configurazione cresce",
"gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati", "gs_setups_chip": "Configurazioni suggerite: trovati {n} dispositivi con trigger preconfigurati",
"gs_adopt_chip": "{n} sensori di problemi possono diventare attività di manutenzione", "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: \"<nome>\" — o un elenco di nomi per limitare la scheda a più oggetti."
} }
@@ -129,6 +129,8 @@
"notes_optional": "メモ(任意)", "notes_optional": "メモ(任意)",
"cost_optional": "費用(任意)", "cost_optional": "費用(任意)",
"duration_minutes": "所要時間(分、任意)", "duration_minutes": "所要時間(分、任意)",
"completed_at_optional": "完了日時(任意・空欄 = 現在)",
"completed_at_future_error": "完了日時に未来は指定できません。",
"days": "日", "days": "日",
"day": "日", "day": "日",
"today": "今日", "today": "今日",
@@ -193,9 +195,14 @@
"use_entity_state": "エンティティの状態を使用(属性なし)", "use_entity_state": "エンティティの状態を使用(属性なし)",
"trigger_above": "この値を超えたらトリガー", "trigger_above": "この値を超えたらトリガー",
"trigger_below": "この値を下回ったらトリガー", "trigger_below": "この値を下回ったらトリガー",
"trigger_equals": "値が一致したらトリガー(=)",
"trigger_not_equals": "値が異なればトリガー(≠)",
"for_at_least_minutes": "最低継続時間(分)", "for_at_least_minutes": "最低継続時間(分)",
"safety_interval_days": "安全間隔(日、任意)", "safety_interval_days": "安全間隔(日、任意)",
"safety_interval": "安全間隔(任意)", "safety_interval": "安全間隔(任意)",
"trigger_combinator": "トリガーと間隔の組み合わせ",
"trigger_combinator_any": "トリガーまたは間隔(先に満たした方)",
"trigger_combinator_all": "トリガーと間隔(両方必須)",
"delta_mode": "差分モード", "delta_mode": "差分モード",
"from_state_optional": "変化前の状態(任意)", "from_state_optional": "変化前の状態(任意)",
"to_state_optional": "変化後の状態(任意)", "to_state_optional": "変化後の状態(任意)",
@@ -850,5 +857,17 @@
"gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます", "gs_label": "はじめに — セットアップが進むとこれらのヒントは消えます",
"gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出", "gs_setups_chip": "推奨セットアップ:トリガー設定済みのデバイスを{n}台検出",
"gs_adopt_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: \"<名前>\" — 複数指定はカードを複数オブジェクトに限定します。"
} }
@@ -129,6 +129,8 @@
"notes_optional": "메모 (선택)", "notes_optional": "메모 (선택)",
"cost_optional": "비용 (선택)", "cost_optional": "비용 (선택)",
"duration_minutes": "소요 시간(분, 선택)", "duration_minutes": "소요 시간(분, 선택)",
"completed_at_optional": "완료 시각 (선택, 비우면 지금)",
"completed_at_future_error": "완료 날짜는 미래일 수 없습니다.",
"days": "일", "days": "일",
"day": "일", "day": "일",
"today": "오늘", "today": "오늘",
@@ -193,9 +195,14 @@
"use_entity_state": "엔티티 상태 사용 (속성 없음)", "use_entity_state": "엔티티 상태 사용 (속성 없음)",
"trigger_above": "초과 시 트리거", "trigger_above": "초과 시 트리거",
"trigger_below": "미만 시 트리거", "trigger_below": "미만 시 트리거",
"trigger_equals": "값이 같으면 트리거 (=)",
"trigger_not_equals": "값이 다르면 트리거 (≠)",
"for_at_least_minutes": "최소 지속 시간 (분)", "for_at_least_minutes": "최소 지속 시간 (분)",
"safety_interval_days": "안전 주기 (일, 선택)", "safety_interval_days": "안전 주기 (일, 선택)",
"safety_interval": "안전 주기 (선택)", "safety_interval": "안전 주기 (선택)",
"trigger_combinator": "트리거와 간격 결합",
"trigger_combinator_any": "트리거 또는 간격 (먼저 충족)",
"trigger_combinator_all": "트리거와 간격 (둘 다 필요)",
"delta_mode": "델타 모드", "delta_mode": "델타 모드",
"from_state_optional": "변경 전 상태 (선택)", "from_state_optional": "변경 전 상태 (선택)",
"to_state_optional": "변경 후 상태 (선택)", "to_state_optional": "변경 후 상태 (선택)",
@@ -850,5 +857,17 @@
"gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다", "gs_label": "시작하기 — 설정이 늘어나면 이 힌트는 사라집니다",
"gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견", "gs_setups_chip": "추천 설정: 트리거가 준비된 기기 {n}대 발견",
"gs_adopt_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: \"<이름>\" — 이름 목록으로 카드를 여러 객체로 제한할 수 있습니다."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notater (valgfritt)", "notes_optional": "Notater (valgfritt)",
"cost_optional": "Kostnad (valgfritt)", "cost_optional": "Kostnad (valgfritt)",
"duration_minutes": "Varighet i minutter (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", "days": "dager",
"day": "dag", "day": "dag",
"today": "I dag", "today": "I dag",
@@ -193,9 +195,14 @@
"use_entity_state": "Bruk entitetstilstand (ingen attributt)", "use_entity_state": "Bruk entitetstilstand (ingen attributt)",
"trigger_above": "Utløs over", "trigger_above": "Utløs over",
"trigger_below": "Utløs under", "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)", "for_at_least_minutes": "I minst (minutter)",
"safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)", "safety_interval_days": "Sikkerhetsintervall (dager, valgfritt)",
"safety_interval": "Sikkerhetsintervall (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", "delta_mode": "Deltamodus",
"from_state_optional": "Fra tilstand (valgfritt)", "from_state_optional": "Fra tilstand (valgfritt)",
"to_state_optional": "Til 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_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_setups_chip": "Foreslåtte oppsett fant {n} enheter med ferdigkoblede utløsere",
"gs_adopt_chip": "{n} problemsensorer kan bli vedlikeholdsoppgaver", "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: \"<navn>\" — eller en liste med navn for å begrense kortet til flere objekter."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notities (optioneel)", "notes_optional": "Notities (optioneel)",
"cost_optional": "Kosten (optioneel)", "cost_optional": "Kosten (optioneel)",
"duration_minutes": "Duur in minuten (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", "days": "dagen",
"day": "dag", "day": "dag",
"today": "Vandaag", "today": "Vandaag",
@@ -192,9 +194,14 @@
"use_entity_state": "Entiteitsstatus gebruiken (geen attribuut)", "use_entity_state": "Entiteitsstatus gebruiken (geen attribuut)",
"trigger_above": "Activeren als boven", "trigger_above": "Activeren als boven",
"trigger_below": "Activeren als onder", "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)", "for_at_least_minutes": "Voor minstens (minuten)",
"safety_interval_days": "Veiligheidsinterval (dagen, optioneel)", "safety_interval_days": "Veiligheidsinterval (dagen, optioneel)",
"safety_interval": "Veiligheidsinterval (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", "delta_mode": "Deltamodus",
"from_state_optional": "Van status (optioneel)", "from_state_optional": "Van status (optioneel)",
"to_state_optional": "Naar 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_label": "Aan de slag — deze tips verdwijnen naarmate je installatie groeit",
"gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden", "gs_setups_chip": "Voorgestelde setups: {n} apparaten met vooraf ingestelde triggers gevonden",
"gs_adopt_chip": "{n} probleemsensoren kunnen onderhoudstaken worden", "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: \"<naam>\" — of een lijst met namen om de kaart tot meerdere objecten te beperken."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notatki (opcjonalne)", "notes_optional": "Notatki (opcjonalne)",
"cost_optional": "Koszt (opcjonalne)", "cost_optional": "Koszt (opcjonalne)",
"duration_minutes": "Czas trwania w minutach (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", "days": "dni",
"day": "dzień", "day": "dzień",
"today": "Dzisiaj", "today": "Dzisiaj",
@@ -192,9 +194,14 @@
"use_entity_state": "Użyj stanu encji (bez atrybutu)", "use_entity_state": "Użyj stanu encji (bez atrybutu)",
"trigger_above": "Wyzwól powyżej", "trigger_above": "Wyzwól powyżej",
"trigger_below": "Wyzwól poniż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)", "for_at_least_minutes": "Przez co najmniej (minuty)",
"safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)", "safety_interval_days": "Interwał bezpieczeństwa (dni, opcjonalny)",
"safety_interval": "Interwał bezpieczeństwa (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", "delta_mode": "Tryb delta",
"from_state_optional": "Ze stanu (opcjonalne)", "from_state_optional": "Ze stanu (opcjonalne)",
"to_state_optional": "Do 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_label": "Pierwsze kroki — te wskazówki znikają wraz z rozwojem konfiguracji",
"gs_setups_chip": "Sugerowane konfiguracje: znaleziono {n} urządzeń z gotowymi wyzwalaczami", "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_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: \"<nazwa>\" — lub listę nazw, aby ograniczyć kartę do kilku obiektów."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Observações (opcional)", "notes_optional": "Observações (opcional)",
"cost_optional": "Custo (opcional)", "cost_optional": "Custo (opcional)",
"duration_minutes": "Duração em minutos (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", "days": "dias",
"day": "dia", "day": "dia",
"today": "Hoje", "today": "Hoje",
@@ -193,9 +195,14 @@
"use_entity_state": "Usar o estado da entidade (sem atributo)", "use_entity_state": "Usar o estado da entidade (sem atributo)",
"trigger_above": "Acionar acima de", "trigger_above": "Acionar acima de",
"trigger_below": "Acionar abaixo 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)", "for_at_least_minutes": "Por pelo menos (minutos)",
"safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval_days": "Intervalo de segurança (dias, opcional)",
"safety_interval": "Intervalo de segurança (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", "delta_mode": "Modo delta",
"from_state_optional": "Do estado (opcional)", "from_state_optional": "Do estado (opcional)",
"to_state_optional": "Para o 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_label": "Primeiros passos — estas dicas somem conforme a configuração cresce",
"gs_setups_chip": "Configurações sugeridas: {n} dispositivos com gatilhos pré-configurados", "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_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: \"<nome>\" — ou uma lista de nomes para limitar o cartão a vários objetos."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Notas (opcional)", "notes_optional": "Notas (opcional)",
"cost_optional": "Custo (opcional)", "cost_optional": "Custo (opcional)",
"duration_minutes": "Duração em minutos (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", "days": "dias",
"day": "dia", "day": "dia",
"today": "Hoje", "today": "Hoje",
@@ -192,9 +194,14 @@
"use_entity_state": "Usar estado da entidade (sem atributo)", "use_entity_state": "Usar estado da entidade (sem atributo)",
"trigger_above": "Acionar acima de", "trigger_above": "Acionar acima de",
"trigger_below": "Acionar abaixo 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)", "for_at_least_minutes": "Durante pelo menos (minutos)",
"safety_interval_days": "Intervalo de segurança (dias, opcional)", "safety_interval_days": "Intervalo de segurança (dias, opcional)",
"safety_interval": "Intervalo de segurança (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", "delta_mode": "Modo delta",
"from_state_optional": "Do estado (opcional)", "from_state_optional": "Do estado (opcional)",
"to_state_optional": "Para o 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_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_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_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: \"<nome>\" — ou uma lista de nomes para limitar o cartão a vários objetos."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Примечания (опционально)", "notes_optional": "Примечания (опционально)",
"cost_optional": "Стоимость (опционально)", "cost_optional": "Стоимость (опционально)",
"duration_minutes": "Длительность в минутах (опционально)", "duration_minutes": "Длительность в минутах (опционально)",
"completed_at_optional": "Выполнено (необязательно, пусто = сейчас)",
"completed_at_future_error": "Дата выполнения не может быть в будущем.",
"days": "дней", "days": "дней",
"day": "день", "day": "день",
"today": "Сегодня", "today": "Сегодня",
@@ -192,9 +194,14 @@
"use_entity_state": "Использовать состояние сущности (без атрибута)", "use_entity_state": "Использовать состояние сущности (без атрибута)",
"trigger_above": "Срабатывать выше", "trigger_above": "Срабатывать выше",
"trigger_below": "Срабатывать ниже", "trigger_below": "Срабатывать ниже",
"trigger_equals": "Срабатывать при равенстве (=)",
"trigger_not_equals": "Срабатывать при отличии от (≠)",
"for_at_least_minutes": "Не менее (минут)", "for_at_least_minutes": "Не менее (минут)",
"safety_interval_days": "Интервал безопасности (дни, опционально)", "safety_interval_days": "Интервал безопасности (дни, опционально)",
"safety_interval": "Интервал безопасности (опционально)", "safety_interval": "Интервал безопасности (опционально)",
"trigger_combinator": "Совместить триггер и интервал",
"trigger_combinator_any": "Триггер или интервал (что раньше)",
"trigger_combinator_all": "Триггер и интервал (оба условия)",
"delta_mode": "Режим дельты", "delta_mode": "Режим дельты",
"from_state_optional": "Из состояния (опционально)", "from_state_optional": "Из состояния (опционально)",
"to_state_optional": "В состояние (опционально)", "to_state_optional": "В состояние (опционально)",
@@ -850,5 +857,17 @@
"gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки", "gs_label": "Первые шаги — эти подсказки исчезнут по мере роста настройки",
"gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами", "gs_setups_chip": "Рекомендуемые настройки: найдено {n} устройств с готовыми триггерами",
"gs_adopt_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: \"<имя>\" — или список имён, чтобы ограничить карточку несколькими объектами."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Anteckningar (valfritt)", "notes_optional": "Anteckningar (valfritt)",
"cost_optional": "Kostnad (valfritt)", "cost_optional": "Kostnad (valfritt)",
"duration_minutes": "Varaktighet i minuter (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", "days": "dagar",
"day": "dag", "day": "dag",
"today": "Idag", "today": "Idag",
@@ -192,9 +194,14 @@
"use_entity_state": "Använd entitetstillstånd (inget attribut)", "use_entity_state": "Använd entitetstillstånd (inget attribut)",
"trigger_above": "Utlös över", "trigger_above": "Utlös över",
"trigger_below": "Utlös under", "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)", "for_at_least_minutes": "Under minst (minuter)",
"safety_interval_days": "Säkerhetsintervall (dagar, valfritt)", "safety_interval_days": "Säkerhetsintervall (dagar, valfritt)",
"safety_interval": "Säkerhetsintervall (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", "delta_mode": "Delta-läge",
"from_state_optional": "Från tillstånd (valfritt)", "from_state_optional": "Från tillstånd (valfritt)",
"to_state_optional": "Till 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_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_setups_chip": "Föreslagna uppsättningar hittade {n} enheter med förkopplade utlösare",
"gs_adopt_chip": "{n} problemsensorer kan bli underhållsuppgifter", "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: \"<namn>\" — eller en lista med namn för att begränsa kortet till flera objekt."
} }
@@ -129,6 +129,8 @@
"notes_optional": "Notlar (isteğe bağlı)", "notes_optional": "Notlar (isteğe bağlı)",
"cost_optional": "Maliyet (isteğe bağlı)", "cost_optional": "Maliyet (isteğe bağlı)",
"duration_minutes": "Dakika cinsinden süre (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", "days": "gün",
"day": "gün", "day": "gün",
"today": "Bugün", "today": "Bugün",
@@ -193,9 +195,14 @@
"use_entity_state": "Varlık durumunu kullan (öznitelik yok)", "use_entity_state": "Varlık durumunu kullan (öznitelik yok)",
"trigger_above": "Üstünde tetikle", "trigger_above": "Üstünde tetikle",
"trigger_below": "Altında 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)", "for_at_least_minutes": "En az (dakika)",
"safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)", "safety_interval_days": "Güvenlik aralığı (gün, isteğe bağlı)",
"safety_interval": "Güvenlik aralığı (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", "delta_mode": "Fark modu",
"from_state_optional": "Başlangıç durumu (isteğe bağlı)", "from_state_optional": "Başlangıç durumu (isteğe bağlı)",
"to_state_optional": "Hedef durum (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_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_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_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: \"<ad>\" — veya kartı birden çok nesneyle sınırlamak için ad listesi."
} }
@@ -128,6 +128,8 @@
"notes_optional": "Примітки (необов'язково)", "notes_optional": "Примітки (необов'язково)",
"cost_optional": "Вартість (необов'язково)", "cost_optional": "Вартість (необов'язково)",
"duration_minutes": "Тривалість у хвилинах (необов'язково)", "duration_minutes": "Тривалість у хвилинах (необов'язково)",
"completed_at_optional": "Виконано (необов'язково, порожньо = зараз)",
"completed_at_future_error": "Дата виконання не може бути в майбутньому.",
"days": "днів", "days": "днів",
"day": "день", "day": "день",
"today": "Сьогодні", "today": "Сьогодні",
@@ -192,9 +194,14 @@
"use_entity_state": "Використовувати стан об'єкта (без атрибута)", "use_entity_state": "Використовувати стан об'єкта (без атрибута)",
"trigger_above": "Спрацювати, коли вище", "trigger_above": "Спрацювати, коли вище",
"trigger_below": "Спрацювати, коли нижче", "trigger_below": "Спрацювати, коли нижче",
"trigger_equals": "Спрацьовувати при рівності (=)",
"trigger_not_equals": "Спрацьовувати при відмінності від (≠)",
"for_at_least_minutes": "Протягом не менше (хвилин)", "for_at_least_minutes": "Протягом не менше (хвилин)",
"safety_interval_days": "Страховий інтервал (дні, необов'язково)", "safety_interval_days": "Страховий інтервал (дні, необов'язково)",
"safety_interval": "Страховий інтервал (необов'язково)", "safety_interval": "Страховий інтервал (необов'язково)",
"trigger_combinator": "Поєднати тригер та інтервал",
"trigger_combinator_any": "Тригер або інтервал (що раніше)",
"trigger_combinator_all": "Тригер та інтервал (обидва потрібні)",
"delta_mode": "Режим дельти", "delta_mode": "Режим дельти",
"from_state_optional": "З стану (необов'язково)", "from_state_optional": "З стану (необов'язково)",
"to_state_optional": "До стану (необов'язково)", "to_state_optional": "До стану (необов'язково)",
@@ -850,5 +857,17 @@
"gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування", "gs_label": "Перші кроки — ці підказки зникнуть у міру зростання налаштування",
"gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами", "gs_setups_chip": "Рекомендовані налаштування: знайдено {n} пристроїв із готовими тригерами",
"gs_adopt_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: \"<назва>\" — або список назв, щоб обмежити картку кількома об'єктами."
} }
@@ -129,6 +129,8 @@
"notes_optional": "备注 (可选)", "notes_optional": "备注 (可选)",
"cost_optional": "成本 (可选)", "cost_optional": "成本 (可选)",
"duration_minutes": "耗时 (分钟, 可选)", "duration_minutes": "耗时 (分钟, 可选)",
"completed_at_optional": "完成时间(可选,留空 = 现在)",
"completed_at_future_error": "完成日期不能是未来时间。",
"days": "天", "days": "天",
"day": "天", "day": "天",
"today": "今天", "today": "今天",
@@ -193,9 +195,14 @@
"use_entity_state": "使用实体状态 (不使用属性)", "use_entity_state": "使用实体状态 (不使用属性)",
"trigger_above": "高于此值触发", "trigger_above": "高于此值触发",
"trigger_below": "低于此值触发", "trigger_below": "低于此值触发",
"trigger_equals": "等于时触发(=",
"trigger_not_equals": "不等于时触发(≠)",
"for_at_least_minutes": "持续至少 (分钟)", "for_at_least_minutes": "持续至少 (分钟)",
"safety_interval_days": "安全间隔 (天, 可选)", "safety_interval_days": "安全间隔 (天, 可选)",
"safety_interval": "安全间隔 (可选)", "safety_interval": "安全间隔 (可选)",
"trigger_combinator": "组合触发器与间隔",
"trigger_combinator_any": "触发器或间隔(先到者)",
"trigger_combinator_all": "触发器与间隔(两者皆需)",
"delta_mode": "增量模式", "delta_mode": "增量模式",
"from_state_optional": "起始状态 (可选)", "from_state_optional": "起始状态 (可选)",
"to_state_optional": "目标状态 (可选)", "to_state_optional": "目标状态 (可选)",
@@ -850,5 +857,17 @@
"gs_label": "入门提示——随着配置的完善,这些提示会自动消失", "gs_label": "入门提示——随着配置的完善,这些提示会自动消失",
"gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)", "gs_setups_chip": "推荐配置发现 {n} 台设备(含预设触发器)",
"gs_adopt_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: \"<对象名>\" — 或名称列表,将卡片限定为多个对象。"
} }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.58.0 */ /*! maintenance_supporter frontend 2.59.0 */
var S="2.58.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})(); var S="2.59.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
@@ -0,0 +1,222 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DZAYWHWN.js";import{a as d}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import{a,b as _,c as t,f as l,g as h,i as g,j as n,n as i,o as v,q as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._localeReady=!1;this._userService=null;this._toggle=s=>{let o=new Set(this._selected);o.has(s)?o.delete(s):o.add(s),this._selected=o};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0})),o=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:o})),this._open=!1}catch(s){this._error=d(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return v(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,u(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="";try{this._userService?this._userService.updateHass(this.hass):this._userService=new m(this.hass);let[s,o]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=o}catch(s){this._error=d(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return t``;let s=this._lang,o=this._sensors.length>0&&this._selected.size===this._sensors.length;return t`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${e=>e.stopPropagation()}>
<div class="title">${i("adopt_problem_title",s)}</div>
<div class="hint">${i("adopt_problem_hint",s)}</div>
${this._error?t`<div class="error">${this._error}</div>`:l}
${this._loading?t`<div class="loading">…</div>`:this._sensors.length===0?t`<div class="empty">${i("adopt_problem_none",s)}</div>`:t`
<label class="select-all">
<input
type="checkbox"
.checked=${o}
@change=${this._toggleAll}
/>
<span>${i("selected",s)}: ${this._selected.size} / ${this._sensors.length}</span>
</label>
<div class="list">
${this._sensors.map(e=>{let f=this._selected.has(e.entity_id),p=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return t`
<label class="row">
<input
type="checkbox"
.checked=${f}
@change=${()=>this._toggle(e.entity_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${e.name}</span>
<span class="chip ${p?"chip-active":"chip-ok"}">
${p?i("adopt_problem_active",s):i("adopt_problem_ok",s)}
</span>
</div>
${c?t`<div class="row-sub">${c}</div>`:l}
<div class="row-target">
${e.suggested_object_name}${e.suggested_entry_id?l:t` <span class="new-tag">${i("adopt_problem_new_object",s)}</span>`}
</div>
${e.suggested_part_name?t`<div class="row-part">
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
${i("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
</div>`:l}
</div>
</label>
`})}
</div>
`}
${!this._loading&&this._sensors.length>0&&this._users.length>0?t`
<label class="responsible">
<span>${i("adopt_problem_responsible",s)}</span>
<select
.value=${this._responsible}
@change=${e=>{this._responsible=e.target.value}}
>
<option value="" ?selected=${!this._responsible}>${i("no_user_assigned",s)}</option>
${this._users.map(e=>t`<option value=${e.id} ?selected=${e.id===this._responsible}>${e.name}</option>`)}
</select>
</label>
`:l}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${i("cancel",s)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${i("adopt_problem_adopt",s)}
</ha-button>
</div>
</div>
</div>
`}};r.styles=_`
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.card {
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: min(360px, calc(100vw - 24px));
max-width: 560px;
width: 90vw;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.title {
font-size: 18px;
font-weight: 500;
}
.hint {
color: var(--secondary-text-color);
font-size: 13px;
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
.loading,
.empty {
color: var(--secondary-text-color);
font-size: 14px;
padding: 12px 0;
}
.select-all {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--secondary-text-color);
cursor: pointer;
}
.select-all input {
cursor: pointer;
}
.list {
display: flex;
flex-direction: column;
gap: 6px;
overflow-y: auto;
max-height: 50vh;
}
.row {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px;
border: 1px solid var(--divider-color);
border-radius: 6px;
cursor: pointer;
}
.row input {
margin-top: 2px;
cursor: pointer;
}
.row-main {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
flex: 1;
}
.row-top {
display: flex;
align-items: center;
gap: 8px;
}
.row-name {
font-weight: 500;
font-size: 13px;
}
.row-sub {
color: var(--secondary-text-color);
font-size: 12px;
}
.row-target {
color: var(--secondary-text-color);
font-size: 12px;
}
.row-part {
color: var(--secondary-text-color);
font-size: 12px;
display: flex;
align-items: center;
gap: 4px;
}
.row-part ha-icon {
--mdc-icon-size: 14px;
}
.new-tag {
font-style: italic;
}
.chip {
font-size: 11px;
padding: 1px 8px;
border-radius: 10px;
white-space: nowrap;
}
.chip-active {
background: var(--error-color, #f44336);
color: #fff;
}
.chip-ok {
background: var(--divider-color);
color: var(--secondary-text-color);
}
.responsible {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: var(--secondary-text-color);
flex-wrap: wrap;
}
.responsible select {
flex: 1;
min-width: 140px;
padding: 4px 6px;
border-radius: 4px;
border: 1px solid var(--divider-color);
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
}
.actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 8px;
}
`,a([g({attribute:!1})],r.prototype,"hass",2),a([n()],r.prototype,"_open",2),a([n()],r.prototype,"_loading",2),a([n()],r.prototype,"_adopting",2),a([n()],r.prototype,"_error",2),a([n()],r.prototype,"_sensors",2),a([n()],r.prototype,"_selected",2),a([n()],r.prototype,"_users",2),a([n()],r.prototype,"_responsible",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{n as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";var s={name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"};function c(r,o){let e=s[r];if(!e)return r;let t=a(e,o);return t&&t!==e?t:r}function d(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function g(r,o,e){if(e=e??a("action_error",o),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,_=t.message||t.error?.message||"";if(!_)return e;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||e}}export{g as a};
@@ -0,0 +1,299 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as _,e as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FMV3K4OT.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import{a as s,b,c as a,f as d,g as v,i as n,j as l,n as r,z as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(){this._open||(this._open=!0,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((e,t)=>[String(t),!!this.checklistPrefill[e]]).filter(([,e])=>e)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(e=>[_(e),{...e}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,o=t.files?.[0];if(t.value="",!!o){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",o,o.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(o))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=r("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=g(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let o=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(o)||o<=0?null:Math.round(this.restockUnitCost*o*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let o of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(o));c?.cost!=null&&(e+=c.cost*(o.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let o=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return a`<button
type="button"
class="cost-suggestion"
@click=${()=>this._cost=t.toFixed(2)}
>${r("cost_from_parts",e).replace("{amount}",o)}</button>`}_close(){this._open=!1}render(){if(!this._open)return a``;let e=this.lang||this.hass?.language||"en";return a`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div>
<div class="content">
${this._error?a`<div class="error">${this._error}</div>`:d}
${this.checklist.length>0?a`
<div class="checklist-section">
<label class="checklist-label">${r("checklist",e)}</label>
${this.checklist.map((t,o)=>a`
<label class="checklist-item" @click=${()=>this._toggleCheck(o)}>
<input type="checkbox" .checked=${!!this._checklistState[String(o)]} />
<span>${t}</span>
</label>
`)}
</div>
`:d}
${this.taskType==="reading"?a`
<label class="field">
<span class="field-label">${r("reading_value_label",e)}${this.readingUnit?` (${this.readingUnit})`:""}</span>
<input type="number" step="any" class="field-input"
.value=${this._readingValue}
@input=${t=>this._readingValue=t.target.value} />
</label>`:d}
${this.parts.length?a`<div class="used-parts">
<span class="field-label">${r("complete_parts_used",e)}</span>
${this.parts.map(t=>{let o=_({part_id:t.id,entry_id:t.entry_id}),c=this._usedParts[o],p=c!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return a`<div class="used-part-row">
<label class="used-part-check">
<input type="checkbox" .checked=${p}
@change=${f=>{let h={...this._usedParts};f.target.checked?h[o]=h[o]||u:delete h[o],this._usedParts=h}} />
<span
>${t.name}${t.owner_name?a`<span class="used-part-owner"> (${t.owner_name})</span>`:d}${t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:""}</span
>
</label>
${p?a`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(c.quantity)}
@input=${f=>{let h=parseFloat(f.target.value);this._usedParts={...this._usedParts,[o]:{...u,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:d}
</div>`})}
</div>`:this.consumesInfo.length?a`<div class="consumes-hint">
${this.consumesInfo.map(t=>a`<div>${t}</div>`)}
</div>`:d}
${this.restockDefault!==null?a`
<label class="field">
<span class="field-label">${r("restock_quantity_label",e)}</span>
<input type="number" step="0.01" min="0.01" class="field-input"
.value=${this._restockQty}
@input=${t=>this._restockQty=t.target.value} />
</label>`:d}
<!-- Native <input>s rather than <ha-textfield>: when this dialog
is opened from a Lovelace card via dialog-mount, ha-textfield
isn't yet registered (HA loads it lazily when its own panels
need it) so the elements render with zero height and the user
only sees the title + Cancel/Complete buttons the original
bug report. Native inputs always render. -->
<label class="field">
<span class="field-label">${r("notes_optional",e)}${this._req("notes")}</span>
<input type="text" class="field-input"
.value=${this._notes}
@input=${t=>this._notes=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("cost_optional",e)}${this._req("cost")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._cost}
@input=${t=>this._cost=t.target.value} />
${this._renderCostSuggestion(e)}
</label>
<label class="field">
<span class="field-label">${r("duration_minutes",e)}${this._req("duration")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._duration}
@input=${t=>this._duration=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("completed_at_optional",e)}</span>
<input type="datetime-local" class="field-input"
max=${new Date(Date.now()-new Date().getTimezoneOffset()*6e4).toISOString().slice(0,16)}
.value=${this._completedAt}
@change=${t=>this._completedAt=t.target.value} />
</label>
<div class="field">
<span class="field-label">${r("completion_photo_optional",e)}${this._req("photo")}</span>
${this._photoPreview?a`
<div class="photo-preview">
<img src=${this._photoPreview} alt="" />
<button type="button" class="photo-remove" @click=${this._removePhoto}
title="${r("remove",e)}"></button>
</div>`:a`
<label class="photo-pick">
<ha-icon icon="mdi:camera"></ha-icon>
<span>${this._photoUploading?r("uploading",e):r("add_photo",e)}</span>
<input type="file" accept="image/*" capture="environment"
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>`}
</div>
${this.adaptiveEnabled?a`
<div class="feedback-section">
<label class="feedback-label">${r("was_maintenance_needed",e)}</label>
<div class="feedback-buttons">
<button
class="feedback-btn ${this._feedback==="needed"?"selected":""}"
@click=${()=>this._setFeedback("needed")}
>${r("feedback_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_needed"?"selected":""}"
@click=${()=>this._setFeedback("not_needed")}
>${r("feedback_not_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_sure"?"selected":""}"
@click=${()=>this._setFeedback("not_sure")}
>${r("feedback_not_sure",e)}</button>
</div>
</div>
`:d}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${r("cancel",e)}
</ha-button>
<ha-button
@click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(k[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};i.styles=[m,b`
.req-mark {
color: var(--error-color, #f44336);
margin-left: 2px;
font-weight: 600;
}
/* #104: one-click cost suggestion from parts — quiet link-style chip. */
.cost-suggestion {
align-self: flex-start;
margin-top: 4px;
padding: 0;
border: none;
background: none;
color: var(--primary-color);
font-size: 12.5px;
cursor: pointer;
text-decoration: underline dotted;
text-underline-offset: 2px;
}
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.consumes-hint {
font-size: 13px;
color: var(--secondary-text-color);
border-left: 3px solid var(--primary-color);
padding: 4px 8px;
margin: 4px 0 8px;
}
/* #99: editable per-completion parts selection */
.used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; }
.used-part-row { display: flex; align-items: center; gap: 8px; }
.used-part-check {
display: flex; align-items: center; gap: 6px; flex: 1;
font-size: 13px; cursor: pointer;
}
.used-part-check input { cursor: pointer; }
/* #111: whose stock this row draws on. Muted but never omitted an
unlabelled foreign pool is indistinguishable from an own part. */
.used-part-owner { color: var(--secondary-text-color); }
.used-part-qty {
width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px;
border: 1px solid var(--divider-color);
background: var(--card-background-color);
color: var(--primary-text-color);
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
/* .field/.field-label/.field-input come from nativeFieldStyles */
.photo-pick {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px dashed var(--divider-color);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
width: fit-content;
}
.photo-pick:hover { border-color: var(--primary-color); }
.photo-pick input[type="file"] { display: none; }
.photo-preview {
position: relative;
width: fit-content;
}
.photo-preview img {
max-width: 160px;
max-height: 160px;
border-radius: 8px;
display: block;
}
.photo-remove {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: var(--error-color, #db4437);
color: #fff;
cursor: pointer;
font-size: 12px;
line-height: 1;
}
.checklist-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid var(--divider-color);
margin-bottom: 4px;
}
.checklist-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
font-size: 14px;
}
.checklist-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.feedback-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-top: 1px solid var(--divider-color);
}
.feedback-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.feedback-buttons {
display: flex;
gap: 8px;
}
.feedback-btn {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.feedback-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
}
.feedback-btn.selected {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
`],s([n({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"entryId",2),s([n()],i.prototype,"taskId",2),s([n()],i.prototype,"taskName",2),s([n()],i.prototype,"lang",2),s([n({type:Array})],i.prototype,"checklist",2),s([n({type:Boolean})],i.prototype,"adaptiveEnabled",2),s([n()],i.prototype,"taskType",2),s([n()],i.prototype,"readingUnit",2),s([n({attribute:!1})],i.prototype,"restockDefault",2),s([n({attribute:!1})],i.prototype,"restockUnitCost",2),s([n()],i.prototype,"currencySymbol",2),s([n({attribute:!1})],i.prototype,"parts",2),s([n({attribute:!1})],i.prototype,"consumesParts",2),s([n({type:Array})],i.prototype,"consumesInfo",2),s([n({type:Array})],i.prototype,"requiredFields",2),s([l()],i.prototype,"_open",2),s([l()],i.prototype,"_notes",2),s([l()],i.prototype,"_cost",2),s([l()],i.prototype,"_duration",2),s([l()],i.prototype,"_loading",2),s([l()],i.prototype,"_error",2),s([l()],i.prototype,"_checklistState",2),s([l()],i.prototype,"_feedback",2),s([l()],i.prototype,"_photoDocId",2),s([l()],i.prototype,"_photoPreview",2),s([l()],i.prototype,"_photoUploading",2),s([l()],i.prototype,"_readingValue",2),s([l()],i.prototype,"_restockQty",2),s([l()],i.prototype,"_completedAt",2),s([l()],i.prototype,"_usedParts",2),s([n({attribute:!1})],i.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",i);export{i as a};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
var r=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestamp<this.CACHE_TTL_MS)return this.usersCache;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/users/list"});return this.usersCache=t.users,this.cacheTimestamp=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,user_id:t})}async getTasksByUser(s){return(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tasks/by_user",user_id:s})).tasks}getUserName(s){return!s||!this.usersCache?null:this.usersCache.find(t=>t.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{r as a};
@@ -0,0 +1,213 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a,b as v,c as n,f as g,g as b,i as m,j as c,n as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";function p(l){return l.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function x(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":p(l)}function $(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var r=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,o,s){this._entryId=e,this._taskId=i,this._objectName=o,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let o=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&o.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(o);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let o=i?.code,s=i?.message;this._error=o==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,o=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let h=this.lang||"en",d=p(i),u=p(o),_=!!this._completeResult,f=p(t("qr_action_view",h)),w=p(t("qr_action_complete",h));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="color-scheme" content="light">
<title>${d}</title>
<style>
/* Printable sheet \u2014 must not inherit the phone's dark theme. The QR images
carry their own white quiet zone and stay scannable either way, but the
labels below are explicit dark greys and would vanish on a WebView's dark
canvas. Same reasoning as helpers/report.ts. */
:root{color-scheme:light}
body{font-family:sans-serif;text-align:center;padding:20px;background:#fff;color:#1a1a1a}
h2{margin:0 0 4px}
.sub{color:#666;font-size:14px;margin-bottom:16px}
.qr-row{display:flex;justify-content:center;gap:24px;margin:12px 0}
.qr-col{display:flex;flex-direction:column;align-items:center;gap:6px}
.qr-col img{width:${_?"200px":"280px"}}
.qr-label{font-size:13px;font-weight:500;color:#333}
.url{font-size:10px;color:#999;word-break:break-all;margin-top:8px;max-width:480px}
</style></head><body>
<h2>${d}</h2>
${u?`<div class="sub">${u}</div>`:""}
<div class="qr-row">
<div class="qr-col">
<img src="${x(this._viewResult.svg_data_uri)}" alt="QR Info" />
<div class="qr-label">${f}</div>
</div>
${_?`<div class="qr-col">
<img src="${x(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${w}</div>
</div>`:""}
</div>
<div class="url">${p(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
</body></html>`),s.document.close()}_downloadSvg(e,i){let o=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),s=new Blob([o],{type:"image/svg+xml"}),h=URL.createObjectURL(s),d=document.createElement("a");d.href=h;let u=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;d.download=`qr-${$(u)}-${i}.svg`,d.click(),URL.revokeObjectURL(h)}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return n``;let e=this.lang||this.hass?.language||"en",i=this._taskName?`${t("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${t("qr_code",e)}: ${this._objectName}`,o=!!this._viewResult;return n`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${i}</div>
<div class="content">
${this._loading?n`<div class="loading">${t("qr_generating",e)}</div>`:this._error?n`<div class="error">${this._error}</div>`:o?n`
<div class="qr-pair">
<div class="qr-item">
<img
class="qr-image ${this._completeResult?"small":""}"
src="${this._viewResult.svg_data_uri}"
alt="QR Info"
/>
<div class="qr-item-label">${t("qr_action_view",e)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._viewResult,"info")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download",e)}
</button>
</div>
${this._completeResult?n`
<div class="qr-item">
<img
class="qr-image small"
src="${this._completeResult.svg_data_uri}"
alt="QR Complete"
/>
<div class="qr-item-label">${t("qr_action_complete",e)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._completeResult,"complete")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download",e)}
</button>
</div>
`:g}
</div>
<div class="url-display">${this._viewResult.url}</div>
`:g}
<div class="action-row">
<label>${t("qr_url_mode",e)}</label>
<div class="action-toggle">
<button class="toggle-btn ${this._urlMode==="companion"?"active":""}"
@click=${()=>this._setUrlMode("companion")}>${t("qr_mode_companion",e)}</button>
<button class="toggle-btn ${this._urlMode==="local"?"active":""}"
@click=${()=>this._setUrlMode("local")}>${t("qr_mode_local",e)}</button>
<button class="toggle-btn ${this._urlMode==="server"?"active":""}"
@click=${()=>this._setUrlMode("server")}>${t("qr_mode_server",e)}</button>
</div>
</div>
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel",e)}
</ha-button>
<ha-button
@click=${this._print}
.disabled=${!o}
>
${t("qr_print",e)}
</ha-button>
</div>
</ha-dialog>
`}};r.styles=v`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.qr-pair {
display: flex;
gap: 20px;
justify-content: center;
width: 100%;
}
.qr-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.qr-image {
width: 240px;
height: 240px;
image-rendering: pixelated;
}
.qr-image.small {
width: 180px;
height: 180px;
}
.qr-item-label {
font-size: 12px;
font-weight: 500;
color: var(--secondary-text-color);
text-align: center;
}
.dl-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: none;
border: 1px solid var(--divider-color, #e0e0e0);
cursor: pointer;
font-size: 13px;
color: var(--primary-text-color);
padding: 6px 14px;
border-radius: 18px;
transition: background 0.2s, border-color 0.2s;
}
.dl-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
border-color: var(--primary-color);
}
.dl-btn ha-icon {
--mdc-icon-size: 18px;
}
.url-display {
font-size: 11px;
color: var(--secondary-text-color);
word-break: break-all;
text-align: center;
max-width: 400px;
}
.loading {
padding: 40px 0;
color: var(--secondary-text-color);
}
.error {
padding: 20px 0;
color: var(--error-color, #f44336);
}
.action-row {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.action-row label {
font-size: 13px;
color: var(--secondary-text-color);
}
.action-toggle {
display: flex;
gap: 4px;
background: var(--divider-color, #e0e0e0);
border-radius: 6px;
padding: 3px;
}
.toggle-btn {
flex: 1;
padding: 8px 12px;
border: none;
background: transparent;
color: var(--primary-text-color);
cursor: pointer;
border-radius: 4px;
font-size: 13px;
transition: all 0.2s;
line-height: 1.3;
}
.toggle-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.toggle-btn.active {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
`,a([m({attribute:!1})],r.prototype,"hass",2),a([m()],r.prototype,"lang",2),a([c()],r.prototype,"_open",2),a([c()],r.prototype,"_loading",2),a([c()],r.prototype,"_error",2),a([c()],r.prototype,"_viewResult",2),a([c()],r.prototype,"_completeResult",2),a([c()],r.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",r);export{r as a};
@@ -0,0 +1,54 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as t,b as a,c as i,f as l,g as p,i as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";var e=class extends p{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(n){let o=n.target.value;this.value=o,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:o}}))}render(){return i`
<label class="field">
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:l}</span>`:l}
<input
.value=${this.value??""}
.type=${this.type}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
step=${this.step??l}
min=${this.min??l}
max=${this.max??l}
pattern=${this.pattern??l}
@input=${this._onInput}
@change=${this._onInput}
/>
${this.helper?i`<span class="helper">${this.helper}</span>`:l}
</label>
`}};e.styles=a`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
input {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
}
input:focus {
border-color: var(--primary-color);
}
input:disabled { opacity: 0.5; cursor: not-allowed; }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,t([r()],e.prototype,"label",2),t([r()],e.prototype,"value",2),t([r()],e.prototype,"placeholder",2),t([r()],e.prototype,"type",2),t([r({type:Boolean})],e.prototype,"required",2),t([r({type:Boolean})],e.prototype,"disabled",2),t([r()],e.prototype,"step",2),t([r()],e.prototype,"min",2),t([r()],e.prototype,"max",2),t([r()],e.prototype,"pattern",2),t([r()],e.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e);
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{n as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";function u(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let n=!!e.entry_id&&e.entry_id!==r,o=n?e.entry_id:r,a=s.find(p=>p.entry_id===o),t=(a?.parts||[]).find(p=>p.id===e.part_id)||null,d=n&&a?.object?.name||"",i=t?.name||_("shared_part_unknown",c);return{part:t,foreign:n,ownerName:d,label:d?`${i} (${d})`:i}}function P(e,r,s,c){let{part:n,label:o}=l(e,r,s,c),a=n&&n.stock!==null&&n.stock!==void 0?` (${n.stock}${n.unit?" "+n.unit:""})`:"",t=n?.storage_location?` \u2014 ${n.storage_location}`:"";return`${e.quantity}\xD7 ${o}${a}${t}`}function g(e,r,s,c){let o=(s.find(t=>t.entry_id===r)?.parts||[]).map(t=>({...t})),a=new Set(o.map(t=>u({part_id:t.id})));for(let t of e?.consumes_parts||[]){if(!t.entry_id||t.entry_id===r)continue;let d=u(t);if(a.has(d))continue;a.add(d);let{part:i,ownerName:p}=l(t,r,s,c);o.push({id:t.part_id,name:i?.name||_("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:t.entry_id,owner_name:p})}return o}var f=["notes","cost","duration","photo","user"],m={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{u as a,P as b,g as c,f as d,m as e};
@@ -0,0 +1,143 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import{a as r,b as _,c as l,f as o,g as p,i as d,j as s,n as i,o as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";var e=class extends p{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return h(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(a,n){this._entryId=a,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(a){this._error=u(a,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(a=>a.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let a=this._lang,n=this._entryId?i("edit_object",a):i("new_object",a);return l`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${n}</div>
<div class="content">
${this._error?l`<div class="error">${this._error}</div>`:o}
<ms-textfield
label="${i("name",a)}"
required
.value=${this._name}
@input=${t=>this._name=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("manufacturer_optional",a)}"
.value=${this._manufacturer}
@input=${t=>this._manufacturer=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("model_optional",a)}"
.value=${this._model}
@input=${t=>this._model=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("serial_number_optional",a)}"
.value=${this._serialNumber}
@input=${t=>this._serialNumber=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("documentation_url_optional",a)}"
type="url"
.value=${this._documentationUrl}
@input=${t=>this._documentationUrl=t.target.value}
></ms-textfield>
<ha-area-picker
.hass=${this.hass}
label="${i("area_id_optional",a)}"
.value=${this._areaId}
@value-changed=${t=>this._areaId=t.detail.value||""}
></ha-area-picker>
<ms-textfield
label="${i("installation_date_optional",a)}"
type="date"
.value=${this._installationDate}
@input=${t=>this._installationDate=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("warranty_expiry_optional",a)}"
type="date"
.value=${this._warrantyExpiry}
@input=${t=>this._warrantyExpiry=t.target.value}
></ms-textfield>
<ha-form
.hass=${this.hass}
.data=${{device:this._haDeviceId||void 0}}
.schema=${[{name:"device",selector:{device:{}}}]}
.computeLabel=${()=>i("link_device_optional",a)}
@value-changed=${t=>this._haDeviceId=t.detail.value?.device||""}
></ha-form>
${this._parentChoices().length?l`<label class="textarea-field">
<span class="textarea-label">${i("parent_object_optional",a)}</span>
<select
class="parent-select"
.value=${this._parentEntryId}
@change=${t=>this._parentEntryId=t.target.value}
>
<option value="" ?selected=${!this._parentEntryId}>
${i("parent_none",a)}
</option>
${this._parentChoices().map(t=>l`<option
value=${t.entry_id}
?selected=${this._parentEntryId===t.entry_id}
>${t.object.name}</option>`)}
</select>
</label>`:o}
<label class="textarea-field">
<span class="textarea-label">${i("object_notes_optional",a)}</span>
<textarea
rows="3"
.value=${this._notes}
@input=${t=>this._notes=t.target.value}
></textarea>
</label>
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${i("cancel",this._lang)}
</ha-button>
<ha-button
@click=${this._save}
.disabled=${this._loading||!this._name.trim()}
>
${this._loading?i("saving",this._lang):i("save",this._lang)}
</ha-button>
</div>
</ha-dialog>
`}};e.styles=_`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
ms-textfield {
display: block;
}
.textarea-field {
display: flex; flex-direction: column; gap: 4px;
}
.textarea-label {
font-size: 12px; color: var(--secondary-text-color, #888); font-weight: 500;
}
.textarea-field textarea {
padding: 8px 10px; font-size: 14px; font-family: inherit;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
resize: vertical;
}
.textarea-field textarea:focus {
outline: none; border-color: var(--primary-color);
}
.parent-select {
padding: 8px 10px; font-size: 14px; font-family: inherit;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
`,r([d({attribute:!1})],e.prototype,"hass",2),r([d({attribute:!1})],e.prototype,"objects",2),r([s()],e.prototype,"_open",2),r([s()],e.prototype,"_loading",2),r([s()],e.prototype,"_error",2),r([s()],e.prototype,"_name",2),r([s()],e.prototype,"_manufacturer",2),r([s()],e.prototype,"_model",2),r([s()],e.prototype,"_serialNumber",2),r([s()],e.prototype,"_areaId",2),r([s()],e.prototype,"_installationDate",2),r([s()],e.prototype,"_warrantyExpiry",2),r([s()],e.prototype,"_documentationUrl",2),r([s()],e.prototype,"_notes",2),r([s()],e.prototype,"_haDeviceId",2),r([s()],e.prototype,"_parentEntryId",2),r([s()],e.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",e);export{e as a};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
function m(n,t,e){let o=new Blob([n],{type:e}),r=URL.createObjectURL(o),a=document.createElement("a");a.href=r,a.download=t,a.target="_blank",a.rel="noopener",a.style.display="none",document.body.appendChild(a),a.dispatchEvent(new MouseEvent("click")),document.body.removeChild(a),setTimeout(()=>URL.revokeObjectURL(r),6e4)}function i(n,t){let e=document.createElement("a");e.href=n,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}async function c(n,t,e=300){return(await n.connection.sendMessagePromise({type:"auth/sign_path",path:t,expires:e})).path}async function s(n,t,e=300){return c(n,`/api/maintenance_supporter/document/${t}`,e)}async function b(n,t,e=""){let o=window.open("about:blank","_blank");try{let r=await s(n,t);o&&(o.location.href=new URL(r+e,window.location.origin).href)}catch(r){throw o&&o.close(),r}}async function g(n,t,e){i(await s(n,t,30),e)}var d=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],u=d.map(n=>n.key),l=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function _(n){if(!Array.isArray(n))return[...l];let t=new Set,e=[];for(let o of n)typeof o=="string"&&u.includes(o)&&!t.has(o)&&(t.add(o),e.push(o));return e.length?(e.includes("name")||e.unshift("name"),e):[...l]}export{m as a,i as b,c,s as d,b as e,g as f,d as g,l as h,_ as i};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-CWIVA4GR.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FMV3K4OT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";export{a as MaintenanceCompleteDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{g as a,h as b,i as c,j as d,k as e,l as f,m as g,n as h,o as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NJUM55WH.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-GF2CJ2A7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-WP2TGE7S.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FBSXXMGM.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-CWIVA4GR.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FMV3K4OT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EIEW3O3I.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DZAYWHWN.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";export{f as openCompleteDialog,a as openCreateObjectDialog,c as openCreateTaskDialog,b as openEditObjectDialog,d as openEditTaskDialog,e as openHistoryEditDialog,i as openObjectQuickActions,g as openQrDialog,h as openTaskQuickActions};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GF2CJ2A7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FBSXXMGM.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";export{a as MaintenanceObjectDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-EIEW3O3I.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";export{a as MaintenanceQrDialog};
@@ -0,0 +1,146 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import{a as l,b as m,c as i,f as h,g as v,i as u,j as d,n as p,o as f,q as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";var a=class extends v{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._setups=[];this._selected=new Set;this._baselines=new Map;this._targets=new Map;this._objects=[];this._localeReady=!1;this._toggle=t=>{let e=new Set(this._selected);e.has(t)?e.delete(t):e.add(t),this._selected=e};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/adopt",selections:[...this._selected].map(e=>{let r={device_id:e},c=this._targets.get(e);c&&(r.entry_id=c);let s=this._setups.find(n=>n.device_id===e);for(let n of s?.tasks??[]){let o=this._baselines.get(`${e} ${n.task_name}`),_=o?parseFloat(o):NaN;!isNaN(_)&&_>=0&&((r.baselines??={})[n.task_name]=_)}return r})});this.dispatchEvent(new CustomEvent("integration-setups-adopted",{bubbles:!0,composed:!0,detail:t})),this._open=!1}catch(t){this._error=g(t,this._lang)}finally{this._adopting=!1}}}}get _lang(){return f(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,x(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._setups=[],this._selected=new Set;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/discover"});this._setups=t.setups||[],this._selected=new Set(this._setups.map(e=>e.device_id)),this._baselines=new Map,this._targets=new Map;try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._objects=(e.objects||[]).map(r=>({entry_id:r.entry_id,name:r.object?.name||r.entry_id})).sort((r,c)=>r.name.localeCompare(c.name))}catch{this._objects=[]}}catch(t){this._error=g(t,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let t=this._lang;return i`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${e=>e.stopPropagation()}>
<div class="title">${p("setups_title",t)}</div>
<div class="hint">${p("setups_hint",t)}</div>
${this._error?i`<div class="error">${this._error}</div>`:h}
${this._loading?i`<div class="loading">…</div>`:this._setups.length===0?i`<div class="empty">${p("setups_none",t)}</div>`:i`
<div class="list">
${this._setups.map(e=>{let r=this._selected.has(e.device_id),c=[e.integration_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
<label class="row">
<input
type="checkbox"
.checked=${r}
@change=${()=>this._toggle(e.device_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${e.device_name}</span>
</div>
<div class="row-sub">${c}</div>
<div class="row-target" @click=${s=>s.preventDefault()}>
${r&&this._objects.length>0?i`
<select
class="target-select"
@change=${s=>{let n=new Map(this._targets),o=s.target.value;o?n.set(e.device_id,o):n.delete(e.device_id),this._targets=n}}
>
<option value="" ?selected=${!this._targets.get(e.device_id)}>
${e.suggested_entry_id?e.suggested_object_name:p("setups_target_new",t).replace("{name}",e.suggested_object_name)}
</option>
${this._objects.filter(s=>s.entry_id!==e.suggested_entry_id).map(s=>i`<option
value=${s.entry_id}
?selected=${this._targets.get(e.device_id)===s.entry_id}
>
${s.name}
</option>`)}
</select>
`:i`${e.suggested_object_name}${e.suggested_entry_id?h:i` <span class="new-tag">${p("adopt_problem_new_object",t)}</span>`}`}
</div>
<div class="row-tasks">
${e.tasks.map(s=>i`<span class="chip" title=${s.entity_ids.join(", ")}>
<ha-icon icon="mdi:link-variant"></ha-icon>${s.task_name_localized||s.task_name}
</span>`)}
</div>
${r?e.tasks.filter(s=>s.direction==="usage_delta").map(s=>{let n=`${e.device_id} ${s.task_name}`;return i`
<div class="baseline-field" @click=${o=>o.preventDefault()}>
<span class="baseline-label"
>${s.task_name_localized||s.task_name}
${p("setups_baseline_hint",t)}</span
>
<input
type="number"
step="any"
min="0"
.value=${this._baselines.get(n)??""}
@click=${o=>o.preventDefault()}
@input=${o=>{let _=new Map(this._baselines);_.set(n,o.target.value),this._baselines=_}}
/>
</div>
`}):h}
</div>
</label>
`})}
</div>
`}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${p("cancel",t)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${p("setups_adopt",t)}
</ha-button>
</div>
</div>
</div>
`}};a.styles=m`
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.card {
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: min(360px, calc(100vw - 24px));
max-width: 560px;
width: 90vw;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.title { font-size: 18px; font-weight: 500; }
.hint { color: var(--secondary-text-color); font-size: 13px; }
.error { color: var(--error-color, #f44336); font-size: 13px; }
.loading, .empty { color: var(--secondary-text-color); font-size: 14px; padding: 12px 0; }
.list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; max-height: 50vh; }
.row {
display: flex; align-items: flex-start; gap: 10px; padding: 8px;
border: 1px solid var(--divider-color); border-radius: 6px; cursor: pointer;
}
.row input { margin-top: 2px; cursor: pointer; }
.row-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.row-name { font-weight: 500; font-size: 13px; }
.row-sub, .row-target { color: var(--secondary-text-color); font-size: 12px; }
.new-tag { font-style: italic; }
.row-tasks { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.chip {
display: inline-flex; align-items: center; gap: 4px;
font-size: 11px; padding: 2px 8px; border-radius: 10px;
background: var(--secondary-background-color, rgba(0, 0, 0, 0.06));
color: var(--primary-text-color); white-space: nowrap;
}
.chip ha-icon { --mdc-icon-size: 12px; color: var(--primary-color); }
.target-select {
font-size: 12px; padding: 2px 4px; max-width: 100%;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.baseline-field {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
margin-top: 4px; font-size: 12px; color: var(--secondary-text-color);
}
.baseline-field input {
width: 110px; padding: 3px 6px; font-size: 12px;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
`,l([u({attribute:!1})],a.prototype,"hass",2),l([d()],a.prototype,"_open",2),l([d()],a.prototype,"_loading",2),l([d()],a.prototype,"_adopting",2),l([d()],a.prototype,"_error",2),l([d()],a.prototype,"_setups",2),l([d()],a.prototype,"_selected",2),l([d()],a.prototype,"_baselines",2),l([d()],a.prototype,"_targets",2),l([d()],a.prototype,"_objects",2);customElements.get("maintenance-suggested-setups-dialog")||customElements.define("maintenance-suggested-setups-dialog",a);export{a as MaintenanceSuggestedSetupsDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WP2TGE7S.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FBSXXMGM.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FMV3K4OT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DZAYWHWN.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7ZFDRUH2.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-DD2OCRNH.js";export{a as MaintenanceTaskDialog};
@@ -0,0 +1,125 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as m,b as f}from"./chunk-KBMB3TAQ.js";import{a as u,b as l,d as h,e as b,f as y,g as o,h as g,i as a,j as v,l as _,u as p}from"./chunk-QC7DYHKY.js";import{a as n}from"./chunk-DSAXFRSJ.js";var k=80,s=class extends b{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return v(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),_(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"});this._status=t,this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1}catch(t){this._error=p(t,this._lang)}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=parseFloat(this._localMonthly),r=parseFloat(this._localYearly),i={};!isNaN(t)&&t>=0&&(i.budget_monthly=t),!isNaN(r)&&r>=0&&(i.budget_yearly=r),await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:i}),await this._load()}catch(t){this._error=p(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,r=this._status;if(!r)return l`<ha-card><div class="loading">${a("loading",t)||"Loading\u2026"}</div></ha-card>`;let i=r.currency_symbol||g,x=r.alert_threshold_pct??k,$=[{label:a("budget_monthly",t)||"Monthly",spent:r.monthly_spent||0,budget:r.monthly_budget||0},{label:a("budget_yearly",t)||"Yearly",spent:r.yearly_spent||0,budget:r.yearly_budget||0}];return l`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">💰</span>
<span>${this._config.title||a("settings_budget",t)||"Budget"}</span>
</div>
<span class="currency">${i}</span>
</div>
${this._error?l`<div class="error">${this._error}</div>`:h}
${$.map(e=>{if(!(e.budget>0))return l`
<div class="track spent-only">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ok">${e.spent.toFixed(0)} ${i}</span>
</div>
</div>
`;let d=Math.min(100,Math.max(0,e.spent/e.budget*100)),c=d>=100?"danger":d>=x?"warning":"ok";return l`
<div class="track">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ${c}">
${e.spent.toFixed(0)} / ${e.budget.toFixed(0)} ${i}
</span>
</div>
<div class="bar"><div class="bar-fill ${c}" style="width:${d}%"></div></div>
</div>
`})}
${this._isAdmin?l`
<div class="inputs-row">
<div class="input-field">
<label>${a("budget_monthly_set",t)||"Set monthly"}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localMonthly}
?disabled=${this._busy}
@input=${e=>{this._localMonthly=e.target.value,this._dirty=!0}} />
<span class="input-suffix">${i}</span>
</div>
</div>
<div class="input-field">
<label>${a("budget_yearly_set",t)||"Set yearly"}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localYearly}
?disabled=${this._busy}
@input=${e=>{this._localYearly=e.target.value,this._dirty=!0}} />
<span class="input-suffix">${i}</span>
</div>
</div>
</div>
<div class="actions">
<button class="btn ${this._dirty?"primary":"muted"}"
@click=${this._save}
?disabled=${this._busy||!this._dirty}>
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
${this._dirty?a("save",t)||"Save":a("saved",t)||"Saved"}
</button>
<button class="btn link" @click=${this._onDeepLink}>
${a("budget_advanced",t)||"Currency, alerts\u2026"}
</button>
</div>
`:l`
<button class="btn link" @click=${this._onDeepLink}>
${a("budget_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};s.styles=[f,u`
.currency {
font-size: 14px; font-weight: 600;
color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 10px; border-radius: 999px;
}
.track { display: flex; flex-direction: column; gap: 4px; }
.track-label-row {
display: flex; align-items: center; justify-content: space-between;
}
.track-label-row label {
font-size: 12px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.5px;
}
.track-numbers { font-size: 13px; font-weight: 600; }
.track-numbers.ok { color: var(--primary-text-color); }
.track-numbers.warning { color: #ff9800; }
.track-numbers.danger { color: var(--error-color, #f44336); }
.bar {
height: 6px; background: var(--secondary-background-color);
border-radius: 3px; overflow: hidden;
}
.bar-fill { height: 100%; transition: width 0.3s; border-radius: 3px; }
.bar-fill.ok { background: var(--primary-color); }
.bar-fill.warning { background: #ff9800; }
.bar-fill.danger { background: var(--error-color, #f44336); }
.inputs-row {
display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
padding-top: 4px; border-top: 1px solid var(--divider-color);
}
.input-field { display: flex; flex-direction: column; gap: 4px; }
.input-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.input-wrap { position: relative; display: flex; align-items: center; }
.input-wrap input {
flex: 1; padding: 6px 32px 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
.input-suffix {
position: absolute; right: 8px;
color: var(--secondary-text-color); font-size: 13px;
pointer-events: none;
}
.actions { display: flex; gap: 8px; align-items: center; }
`],n([y({attribute:!1})],s.prototype,"hass",2),n([o()],s.prototype,"_config",2),n([o()],s.prototype,"_status",2),n([o()],s.prototype,"_busy",2),n([o()],s.prototype,"_error",2),n([o()],s.prototype,"_localMonthly",2),n([o()],s.prototype,"_localYearly",2),n([o()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);m({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Inline monthly + yearly budget editor",preview:!1});export{s as MaintenanceBudgetSectionCard};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.59.0 */
var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var t=(a,r,c,o)=>{for(var e=o>1?void 0:o?l(r,c):r,i=a.length-1,d;i>=0;i--)(d=a[i])&&(e=(o?d(r,c,e):d(e))||e);return o&&e&&s(r,c,e),e};var m={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"},v={ok:"mdi:check-circle",due_soon:"mdi:alert-circle",overdue:"mdi:alert-octagon",triggered:"mdi:bell-alert",archived:"mdi:archive-outline",paused:"mdi:pause-circle-outline",completed:"mdi:check-circle",skipped:"mdi:skip-next",missed:"mdi:calendar-remove",reset:"mdi:refresh"};export{t as a,m as b,v as c};
@@ -0,0 +1,60 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as t}from"./chunk-QC7DYHKY.js";function e(o){let r=window;r.customCards=r.customCards||[],r.customCards.some(a=>a.type===o.type)||r.customCards.push(o)}var d=t`
ha-card { overflow: hidden; }
.card-content {
padding: 16px;
display: flex; flex-direction: column;
gap: 12px;
}
.header {
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
}
.title {
display: flex; align-items: center; gap: 8px;
font-size: 16px; font-weight: 500;
}
.emoji { font-size: 20px; }
/* Button family — primary action / muted-saved-state / link / icon-with-text */
.btn {
padding: 6px 12px; font-size: 13px;
border-radius: 6px; cursor: pointer;
border: 1px solid var(--divider-color);
background: var(--secondary-background-color, transparent);
color: var(--primary-text-color);
font-weight: 500;
display: inline-flex; align-items: center; gap: 4px;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn.primary {
background: var(--primary-color);
color: var(--text-primary-color, white);
border-color: var(--primary-color);
}
.btn.primary[disabled] { opacity: 0.6; }
.btn.muted {
background: transparent;
color: var(--secondary-text-color);
border-style: dashed;
}
.btn.muted[disabled] { opacity: 1; cursor: default; }
.btn.muted ha-icon, .btn.primary ha-icon { --mdc-icon-size: 14px; }
.btn.link {
background: transparent; border: none; padding: 6px 4px;
color: var(--primary-color); margin-left: auto;
}
.btn.link:hover { background: transparent; text-decoration: underline; }
/* Error + loading states */
.error {
padding: 8px; border-radius: 6px;
background: rgba(211, 47, 47, 0.1);
color: var(--error-color, #d32f2f); font-size: 13px;
}
.loading {
padding: 24px; text-align: center;
color: var(--secondary-text-color);
}
`;export{e as a,d as b};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,137 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as v,b}from"./chunk-KBMB3TAQ.js";import{a as u,b as s,d as c,e as h,f as g,g as o,i,j as _,l as m,u as p}from"./chunk-QC7DYHKY.js";import{a}from"./chunk-DSAXFRSJ.js";var e=class extends h{constructor(){super(...arguments);this._config={type:""};this._groups={};this._loaded=!1;this._busy=!1;this._error="";this._newName="";this._editingId=null;this._editingName="";this._hasInitiallyLoaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return _(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._hasInitiallyLoaded&&(this._hasInitiallyLoaded=!0,this._load(),m(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"});this._groups=t.groups||{},this._loaded=!0}catch(t){this._error=p(t,this._lang)}}async _addGroup(){if(!this._isAdmin)return;let t=this._newName.trim();if(t){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/create",name:t}),this._newName="",await this._load()}catch(r){this._error=p(r,this._lang)}finally{this._busy=!1}}}_startEdit(t){this._editingId=t,this._editingName=this._groups[t]?.name||""}async _saveEdit(){if(!this._isAdmin||!this._editingId)return;let t=this._editingName.trim();if(t){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/update",group_id:this._editingId,name:t}),this._editingId=null,this._editingName="",await this._load()}catch(r){this._error=p(r,this._lang)}finally{this._busy=!1}}}async _deleteGroup(t,r){if(!this._isAdmin)return;let n=(i("group_delete_confirm",this._lang)||'Delete group "{name}"?').replace("{name}",r);if(window.confirm(n)){this._busy=!0;try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/delete",group_id:t}),await this._load()}catch(d){this._error=p(d,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_groups"),window.dispatchEvent(new CustomEvent("location-changed"))}_onKeyDown(t,r){t.key==="Enter"?(t.preventDefault(),r()):t.key==="Escape"&&(t.preventDefault(),this._editingId=null,this._editingName="")}render(){let t=this._lang;if(!this._loaded)return s`<ha-card><div class="loading">${i("loading",t)||"Loading\u2026"}</div></ha-card>`;let r=Object.keys(this._groups);return s`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏷</span>
<span>${this._config.title||i("groups",t)||"Groups"}</span>
<span class="count">${r.length}</span>
</div>
</div>
${this._error?s`<div class="error">${this._error}</div>`:c}
${r.length===0?s`<div class="empty">${i("groups_empty",t)||"No groups yet."}</div>`:s`
<div class="group-list">
${r.map(n=>{let d=this._groups[n],y=d.task_refs?.length??0,f=this._editingId===n;return s`
<div class="group-row">
${f?s`
<input class="edit-input" type="text"
.value=${this._editingName}
?disabled=${this._busy}
@input=${l=>{this._editingName=l.target.value}}
@keydown=${l=>this._onKeyDown(l,this._saveEdit.bind(this))} />
<button class="btn small primary"
@click=${this._saveEdit}
?disabled=${this._busy||!this._editingName.trim()}>
${i("save",t)||"Save"}
</button>
<button class="btn small"
@click=${()=>{this._editingId=null}}>
${i("cancel",t)||"Cancel"}
</button>
`:s`
<span class="group-name">${d.name||"Unnamed"}</span>
<span class="task-count">${y}</span>
${this._isAdmin?s`
<button class="icon-btn"
title="${i("edit",t)||"Edit"}"
@click=${()=>this._startEdit(n)}
?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
</button>
<button class="icon-btn danger"
title="${i("delete",t)||"Delete"}"
@click=${()=>this._deleteGroup(n,d.name||"Unnamed")}
?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
</button>
`:c}
`}
</div>
`})}
</div>
`}
${this._isAdmin?s`
<div class="add-row">
<input type="text"
placeholder="${i("group_new_placeholder",t)||"Add group\u2026"}"
.value=${this._newName}
?disabled=${this._busy}
@input=${n=>{this._newName=n.target.value}}
@keydown=${n=>this._onKeyDown(n,this._addGroup.bind(this))} />
<button class="btn primary"
@click=${this._addGroup}
?disabled=${this._busy||!this._newName.trim()}>
<ha-icon icon="mdi:plus"></ha-icon>
${i("add",t)||"Add"}
</button>
</div>
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_manage_tasks",t)||"Manage task assignments\u2026"}
</button>
`:s`
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};e.styles=[b,u`
.count {
font-size: 12px; color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 8px; border-radius: 999px;
}
.empty {
padding: 16px; text-align: center;
color: var(--secondary-text-color); font-style: italic;
}
.group-list { display: flex; flex-direction: column; gap: 4px; }
.group-row {
display: flex; align-items: center; gap: 8px;
padding: 6px 8px; border-radius: 6px;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
}
.group-name { flex: 1; font-size: 14px; }
.task-count {
font-size: 11px; color: var(--secondary-text-color);
background: var(--card-background-color, rgba(0,0,0,0.2));
padding: 1px 8px; border-radius: 999px;
font-weight: 500;
}
.edit-input {
flex: 1; padding: 4px 8px; font-size: 14px;
background: var(--card-background-color, #1c1c1c);
color: var(--primary-text-color);
border: 1px solid var(--primary-color); border-radius: 4px;
font-family: inherit;
}
.icon-btn {
background: transparent; border: none; cursor: pointer;
color: var(--secondary-text-color); padding: 4px;
border-radius: 4px;
}
.icon-btn:hover {
background: var(--state-icon-color, rgba(255,255,255,0.06));
color: var(--primary-text-color);
}
.icon-btn.danger:hover { color: var(--error-color); }
.icon-btn ha-icon { --mdc-icon-size: 18px; }
.add-row {
display: flex; gap: 6px;
padding-top: 8px; border-top: 1px solid var(--divider-color);
}
.add-row input {
flex: 1; padding: 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
/* Card-specific overrides on the shared .btn */
.btn.small { padding: 4px 8px; font-size: 12px; }
.btn ha-icon { --mdc-icon-size: 16px; }
`],a([g({attribute:!1})],e.prototype,"hass",2),a([o()],e.prototype,"_config",2),a([o()],e.prototype,"_groups",2),a([o()],e.prototype,"_loaded",2),a([o()],e.prototype,"_busy",2),a([o()],e.prototype,"_error",2),a([o()],e.prototype,"_newName",2),a([o()],e.prototype,"_editingId",2),a([o()],e.prototype,"_editingName",2);customElements.get("maintenance-groups-section-card")||customElements.define("maintenance-groups-section-card",e);v({type:"maintenance-groups-section-card",name:"Maintenance Supporter \u2014 Groups",description:"Inline group CRUD",preview:!1});export{e as MaintenanceGroupsSectionCard};
@@ -0,0 +1,122 @@
/*! maintenance_supporter frontend 2.59.0 */
import{a as m,b as g}from"./chunk-KBMB3TAQ.js";import{a as h,b as n,d as c,e as _,f as v,g as r,i as e,j as f,l as b,u as l}from"./chunk-QC7DYHKY.js";import{a as i}from"./chunk-DSAXFRSJ.js";var a=class extends _{constructor(){super(...arguments);this._config={type:""};this._state=null;this._busy=!1;this._error="";this._localStart="";this._localEnd="";this._localBuffer=7;this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return f(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),b(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/state"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||"",this._localBuffer=t.buffer_days??7,this._dirty=!1}catch(t){this._error=l(t,this._lang)}}async _toggleEnabled(t){this._busy=!0,this._error="";try{let s=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",enabled:t});this._state=s}catch(s){this._error=l(s,this._lang)}finally{this._busy=!1}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",start:this._localStart||null,end:this._localEnd||null,buffer_days:this._localBuffer});this._state=t,this._dirty=!1}catch(t){this._error=l(t,this._lang)}finally{this._busy=!1}}}async _endNow(){if(this._isAdmin&&window.confirm(e("vacation_end_now_confirm",this._lang)||"End vacation immediately?")){this._busy=!0;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/end_now"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||""}catch(t){this._error=l(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_vacation"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,s=this._state;if(!s)return n`<ha-card><div class="loading">${e("loading",t)||"Loading\u2026"}</div></ha-card>`;let p=s.is_active===!0,d=s.enabled===!0,u=s.exempt_task_ids?.length??0,y=p?e("vacation_status_active",t)||"Active now":d?e("vacation_status_scheduled",t)||"Scheduled":e("vacation_status_inactive",t)||"Inactive",$=p?"active":d?"scheduled":"inactive";return n`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏖</span>
<span>${this._config.title||e("vacation_mode",t)||"Vacation mode"}</span>
</div>
<span class="status-pill ${$}">${y}</span>
</div>
${this._error?n`<div class="error">${this._error}</div>`:c}
${this._isAdmin?n`
<div class="row toggle-row">
<label>${e("enable",t)||"Enable"}</label>
<ha-switch
.checked=${d}
.disabled=${this._busy}
@change=${o=>this._toggleEnabled(o.target.checked)}
></ha-switch>
</div>
<div class="dates-row">
<div class="date-field">
<label>${e("vacation_start",t)||"Start"}</label>
<input type="date" .value=${this._localStart}
?disabled=${this._busy}
@input=${o=>{this._localStart=o.target.value,this._dirty=!0}} />
</div>
<div class="date-field">
<label>${e("vacation_end",t)||"End"}</label>
<input type="date" .value=${this._localEnd}
?disabled=${this._busy}
@input=${o=>{this._localEnd=o.target.value,this._dirty=!0}} />
</div>
<div class="date-field buffer">
<label>${e("vacation_buffer",t)||"Buffer days"}</label>
<input type="number" min="0" max="14"
.value=${String(this._localBuffer)}
?disabled=${this._busy}
@input=${o=>{this._localBuffer=parseInt(o.target.value,10)||0,this._dirty=!0}} />
</div>
</div>
<div class="actions">
<button class="btn ${this._dirty?"primary":"muted"}"
@click=${this._save}
?disabled=${this._busy||!this._dirty}>
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
${this._dirty?e("save",t)||"Save":e("saved",t)||"Saved"}
</button>
${p?n`<button class="btn"
@click=${this._endNow}
?disabled=${this._busy}>
${e("vacation_end_now",t)||"End now"}
</button>`:c}
${u>0?n`<button class="btn link"
@click=${this._onDeepLink}>
${u} ${e("vacation_exempt_count",t)||"exempt"}
</button>`:n`<button class="btn link"
@click=${this._onDeepLink}>
${e("vacation_advanced",t)||"Advanced\u2026"}
</button>`}
</div>
`:n`
<div class="readonly">
${d&&s.start&&s.end?n`<div>${s.start}${s.end}</div>`:c}
<button class="btn link" @click=${this._onDeepLink}>
${e("vacation_open_panel",t)||"Open in panel"}
</button>
</div>
`}
</div>
</ha-card>
`}};a.styles=[g,h`
.status-pill {
font-size: 11px; font-weight: 600;
padding: 3px 8px; border-radius: 999px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.status-pill.active {
background: rgba(76, 175, 80, 0.15);
color: #4caf50;
}
.status-pill.scheduled {
background: rgba(255, 152, 0, 0.15);
color: #ff9800;
}
.status-pill.inactive {
background: rgba(158, 158, 158, 0.15);
color: var(--secondary-text-color);
}
.row.toggle-row {
display: flex; align-items: center; justify-content: space-between;
}
.row.toggle-row label {
font-size: 14px; color: var(--primary-text-color);
}
.dates-row {
display: grid; grid-template-columns: 1fr 1fr 100px; gap: 10px;
}
.date-field.buffer label { white-space: nowrap; }
.date-field { display: flex; flex-direction: column; gap: 4px; }
.date-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.date-field input {
padding: 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
.date-field input:disabled { opacity: 0.5; cursor: not-allowed; }
.actions {
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.readonly { display: flex; flex-direction: column; gap: 8px; }
`],i([v({attribute:!1})],a.prototype,"hass",2),i([r()],a.prototype,"_config",2),i([r()],a.prototype,"_state",2),i([r()],a.prototype,"_busy",2),i([r()],a.prototype,"_error",2),i([r()],a.prototype,"_localStart",2),i([r()],a.prototype,"_localEnd",2),i([r()],a.prototype,"_localBuffer",2),i([r()],a.prototype,"_dirty",2);customElements.get("maintenance-vacation-section-card")||customElements.define("maintenance-vacation-section-card",a);m({type:"maintenance-vacation-section-card",name:"Maintenance Supporter \u2014 Vacation",description:"Inline vacation mode toggle + dates",preview:!1});export{a as MaintenanceVacationSectionCard};
File diff suppressed because one or more lines are too long
@@ -102,6 +102,11 @@ def register_action_listener(hass: HomeAssistant) -> Callable[[], None]:
task_id = event.data.get("task_id") task_id = event.data.get("task_id")
if not entry_id or not task_id: if not entry_id or not task_id:
return return
# #133: a pure backfill records maintenance that happened long ago —
# running the on_complete_action NOW (reset a device counter, toggle
# a helper) would act on the live device for stale work.
if event.data.get("backfill"):
return
action = _resolve_task_action(hass, entry_id, task_id) action = _resolve_task_action(hass, entry_id, task_id)
if action is None: if action is None:
return return
@@ -35,13 +35,23 @@ def compute_status_from_task_dict(task: dict[str, Any]) -> str:
# between refreshes. # between refreshes.
if task.get("_paused"): if task.get("_paused"):
return MaintenanceStatus.PAUSED return MaintenanceStatus.PAUSED
if task.get("_trigger_active", False): days = task.get("_days_until_due")
# "all" combinator: trigger AND elapsed safety interval must both be met
# before the task actions (model twin: MaintenanceTask.status).
all_mode = (task.get("trigger_config") or {}).get("trigger_combinator") == "all"
time_met = days is None or days <= 0
trigger_active = task.get("_trigger_active", False)
if trigger_active and (not all_mode or time_met):
return MaintenanceStatus.TRIGGERED return MaintenanceStatus.TRIGGERED
days = task.get("_days_until_due")
if days is None: if days is None:
return MaintenanceStatus.OK return MaintenanceStatus.OK
if all_mode and not trigger_active:
return MaintenanceStatus.OK
warning_days = task.get("warning_days", DEFAULT_WARNING_DAYS) warning_days = task.get("warning_days", DEFAULT_WARNING_DAYS)
if days < 0: if days < 0:
return MaintenanceStatus.OVERDUE return MaintenanceStatus.OVERDUE
@@ -14,6 +14,7 @@ every rule is individually testable.
from __future__ import annotations from __future__ import annotations
import math
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
@@ -44,6 +45,30 @@ def _aggregate(per_entity: list[bool], entity_logic: str) -> bool:
return all(per_entity) if entity_logic == "all" else any(per_entity) return all(per_entity) if entity_logic == "all" else any(per_entity)
def threshold_exceeds(
value: float,
*,
above: float | None,
below: float | None,
equals: float | None = None,
not_equals: float | None = None,
) -> bool:
"""Whether *value* violates any configured threshold limit.
The single predicate shared by the event-driven ThresholdTrigger and the
refresh-time fallback below keep both surfaces on this one rule.
Equality uses ``math.isclose`` so float round-trips (state strings, JSON)
can't miss a discrete level like 3.0.
"""
if above is not None and value > above:
return True
if below is not None and value < below:
return True
if equals is not None and math.isclose(value, equals, rel_tol=1e-9, abs_tol=1e-9):
return True
return not_equals is not None and not math.isclose(value, not_equals, rel_tol=1e-9, abs_tol=1e-9)
def _numeric_entity_value(get_state: StateGetter, entity_id: str, attribute: str | None) -> float | None: def _numeric_entity_value(get_state: StateGetter, entity_id: str, attribute: str | None) -> float | None:
"""Read a numeric value from an entity state/attribute (None when unusable).""" """Read a numeric value from an entity state/attribute (None when unusable)."""
state = get_state(entity_id) state = get_state(entity_id)
@@ -69,6 +94,8 @@ def evaluate_threshold(
for_minutes = trigger_config.get("trigger_for_minutes", 0) for_minutes = trigger_config.get("trigger_for_minutes", 0)
above = trigger_config.get("trigger_above") above = trigger_config.get("trigger_above")
below = trigger_config.get("trigger_below") below = trigger_config.get("trigger_below")
equals = trigger_config.get("trigger_equals")
not_equals = trigger_config.get("trigger_not_equals")
per_entity: list[bool] = [] per_entity: list[bool] = []
last_value: float | None = None last_value: float | None = None
@@ -78,8 +105,7 @@ def evaluate_threshold(
per_entity.append(False) per_entity.append(False)
continue continue
last_value = value last_value = value
exceeds = (above is not None and value > above) or (below is not None and value < below) per_entity.append(threshold_exceeds(value, above=above, below=below, equals=equals, not_equals=not_equals))
per_entity.append(exceeds)
aggregated = _aggregate(per_entity, entity_logic) if per_entity else False aggregated = _aggregate(per_entity, entity_logic) if per_entity else False
@@ -22,5 +22,5 @@
"requirements": [ "requirements": [
"pypdf>=4.3.0" "pypdf>=4.3.0"
], ],
"version": "2.58.0" "version": "2.59.0"
} }
@@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import date, time from datetime import date, datetime, time
from typing import Any from typing import Any
from uuid import uuid4 from uuid import uuid4
@@ -12,6 +12,7 @@ from homeassistant.util import dt as dt_util
from ..const import ( from ..const import (
DEFAULT_MAX_HISTORY_ENTRIES, DEFAULT_MAX_HISTORY_ENTRIES,
DEFAULT_WARNING_DAYS, DEFAULT_WARNING_DAYS,
LIFECYCLE_HISTORY_TYPES,
HistoryEntryType, HistoryEntryType,
MaintenanceStatus, MaintenanceStatus,
MaintenanceTypeEnum, MaintenanceTypeEnum,
@@ -192,15 +193,28 @@ class MaintenanceTask:
if self.archived_at is not None: if self.archived_at is not None:
return MaintenanceStatus.ARCHIVED return MaintenanceStatus.ARCHIVED
days = self.days_until_due
# Trigger ∧/ safety interval: with the "all" combinator BOTH legs must
# be met — the trigger must have fired AND the due date been reached
# (the interval is a minimum age, not a deadline). Default "any" keeps
# the historical whichever-first behaviour. Mirror any change in
# helpers/status.compute_status_from_task_dict (the dict twin).
all_mode = (self.trigger_config or {}).get("trigger_combinator") == "all"
time_met = days is None or days <= 0
# Trigger takes precedence # Trigger takes precedence
if self._trigger_active: if self._trigger_active and (not all_mode or time_met):
return MaintenanceStatus.TRIGGERED return MaintenanceStatus.TRIGGERED
days = self.days_until_due
if days is None: if days is None:
# Manual task or no schedule: always OK unless triggered # Manual task or no schedule: always OK unless triggered
return MaintenanceStatus.OK return MaintenanceStatus.OK
if all_mode and not self._trigger_active:
# The elapsed interval alone never actions an "all" task.
return MaintenanceStatus.OK
if days < 0: if days < 0:
return MaintenanceStatus.OVERDUE return MaintenanceStatus.OVERDUE
# Sub-day refinement: same-day past schedule_time also counts as overdue. # Sub-day refinement: same-day past schedule_time also counts as overdue.
@@ -300,24 +314,54 @@ class MaintenanceTask:
reading_value: float | None = None, reading_value: float | None = None,
used_parts: list[dict[str, Any]] | None = None, used_parts: list[dict[str, Any]] | None = None,
auto: bool = False, auto: bool = False,
) -> None: completed_at: datetime | None = None,
) -> bool:
"""Mark this task as completed. """Mark this task as completed.
``auto`` marks a completion nobody performed in the UI (a trigger ``auto`` marks a completion nobody performed in the UI (a trigger
recovering on its own): the history entry is flagged so surfaces can recovering on its own): the history entry is flagged so surfaces can
label it, and the rotation pointer stays put advancing it would label it, and the rotation pointer stays put advancing it would
credit/skip a pool member for work nobody attributed.""" credit/skip a pool member for work nobody attributed.
# Save current next_due as anchor for planned mode before resetting
if self.interval_anchor == "planned" and self.next_due is not None:
self.last_planned_due = self.next_due.isoformat()
now = dt_util.now() ``completed_at`` (#133) records the completion at a past moment
self.last_performed = now.date().isoformat() instead of now. When that moment is still the LATEST lifecycle entry
self._trigger_active = False ("did it three days ago, logging it now") the cycle advances exactly
self._trigger_current_value = None like a normal completion. When it is OLDER than the latest lifecycle
# A postponed occurrence is consumed by completing it — the next cycle entry it is a pure backfill: only the history entry is written the
# returns to the normal cadence. cycle anchor, trigger latch, postpone override, planned anchor and
self.due_override = None rotation pointer all stay put (moving them would throw the live cycle
backwards). Mirrors the history-edit reconciliation's
max-by-timestamp rule (websocket/tasks_history.py).
Returns True when the completion advanced the cycle (it was the
latest lifecycle entry), False for a pure backfill.
"""
ts = completed_at if completed_at is not None else dt_util.now()
ts_iso = ts.isoformat()
# String comparison, deliberately — history timestamps mix TZ-aware
# (live completions) and naive (hand-edited) values, and the edit
# reconciliation already compares them as strings. last_performed
# (date-only ISO) joins the anchors: an imported or history-trimmed
# task has a cycle anchor but no lifecycle entries, and a backfill
# must not drag that anchor backwards either. A full timestamp on the
# same day sorts after the bare date, so a same-day completion still
# counts as latest.
anchors = [h.get("timestamp") or "" for h in self.history if h.get("type") in LIFECYCLE_HISTORY_TYPES]
if self.last_performed:
anchors.append(self.last_performed)
is_latest = ts_iso >= max(anchors, default="")
if is_latest:
# Save current next_due as anchor for planned mode before resetting
if self.interval_anchor == "planned" and self.next_due is not None:
self.last_planned_due = self.next_due.isoformat()
self.last_performed = ts.date().isoformat()
self._trigger_active = False
self._trigger_current_value = None
# A postponed occurrence is consumed by completing it — the next
# cycle returns to the normal cadence.
self.due_override = None
self.add_history_entry( self.add_history_entry(
entry_type=HistoryEntryType.COMPLETED, entry_type=HistoryEntryType.COMPLETED,
@@ -331,14 +375,19 @@ class MaintenanceTask:
reading_value=reading_value, reading_value=reading_value,
used_parts=used_parts, used_parts=used_parts,
auto=auto, auto=auto,
timestamp=ts_iso,
) )
# Shared tasks: rotate the "currently responsible" pointer to the next # Shared tasks: rotate the "currently responsible" pointer to the next
# assignee for the coming cycle (after this completion is recorded, so # assignee for the coming cycle (after this completion is recorded, so
# least_completed sees it). Auto-completions don't rotate — see above. # least_completed sees it). Auto-completions don't rotate — see above.
if not auto: # Pure backfills don't either: the coming cycle's assignee was already
# decided by the real latest completion.
if not auto and is_latest:
self.advance_rotation() self.advance_rotation()
return is_latest
def advance_rotation(self) -> None: def advance_rotation(self) -> None:
"""Advance ``responsible_user_id`` to the next pool member. """Advance ``responsible_user_id`` to the next pool member.
@@ -420,10 +469,15 @@ class MaintenanceTask:
reading_value: float | None = None, reading_value: float | None = None,
used_parts: list[dict[str, Any]] | None = None, used_parts: list[dict[str, Any]] | None = None,
auto: bool = False, auto: bool = False,
timestamp: str | None = None,
) -> None: ) -> None:
"""Add an entry to the maintenance history.""" """Add an entry to the maintenance history.
``timestamp`` overrides the default "now" (backdated completions,
#133). Entries are APPENDED regardless of chronology — consumers that
need order sort defensively, same as after a history-edit."""
entry: dict[str, Any] = { entry: dict[str, Any] = {
"timestamp": dt_util.now().isoformat(), "timestamp": timestamp or dt_util.now().isoformat(),
"type": entry_type, "type": entry_type,
} }
if auto: if auto:
@@ -237,10 +237,14 @@ class MaintenanceSensor(MaintenanceEntity, SensorEntity):
ttype = trigger_config.get("type") ttype = trigger_config.get("type")
attrs["trigger_type"] = ttype attrs["trigger_type"] = ttype
attrs["trigger_active"] = task.get("_trigger_active", False) attrs["trigger_active"] = task.get("_trigger_active", False)
if trigger_config.get("trigger_combinator") == "all":
attrs["trigger_combinator"] = "all"
if ttype == "threshold": if ttype == "threshold":
attrs["trigger_above"] = trigger_config.get("trigger_above") attrs["trigger_above"] = trigger_config.get("trigger_above")
attrs["trigger_below"] = trigger_config.get("trigger_below") attrs["trigger_below"] = trigger_config.get("trigger_below")
attrs["trigger_equals"] = trigger_config.get("trigger_equals")
attrs["trigger_not_equals"] = trigger_config.get("trigger_not_equals")
attrs["trigger_for_minutes"] = trigger_config.get("trigger_for_minutes") attrs["trigger_for_minutes"] = trigger_config.get("trigger_for_minutes")
elif ttype == "counter": elif ttype == "counter":
attrs["trigger_target_value"] = trigger_config.get("trigger_target_value") attrs["trigger_target_value"] = trigger_config.get("trigger_target_value")
@@ -391,6 +395,7 @@ class MaintenanceSensor(MaintenanceEntity, SensorEntity):
tasks = self.coordinator.data.get(CONF_TASKS, {}) tasks = self.coordinator.data.get(CONF_TASKS, {})
task = tasks.get(self._task_id, {}) task = tasks.get(self._task_id, {})
prev_active = task.get("_trigger_active", False)
# Track per-entity state # Track per-entity state
if trigger_entity_id is not None: if trigger_entity_id is not None:
@@ -421,9 +426,12 @@ class MaintenanceSensor(MaintenanceEntity, SensorEntity):
new_status = self._compute_live_status(task) new_status = self._compute_live_status(task)
task["_status"] = new_status task["_status"] = new_status
# Only write HA state when status actually changes to avoid # Only write HA state when the status — or the latched trigger flag —
# unnecessary recorder writes on every trigger value update. # actually changes, to avoid recorder writes on every value update.
if new_status != old_status: # With trigger_combinator="all" a latched trigger no longer implies a
# status change (the interval leg may still be pending), but the
# trigger_active attribute must repaint regardless.
if new_status != old_status or task.get("_trigger_active", False) != prev_active:
self.async_write_ha_state() self.async_write_ha_state()
@staticmethod @staticmethod
@@ -45,6 +45,11 @@ complete:
selector: selector:
entity: entity:
domain: person domain: person
completed_at:
name: Completed at
description: When the maintenance was actually performed — use it to backfill a past completion. Must not be in the future; when omitted, now is recorded.
selector:
datetime:
reset: reset:
name: Reset Maintenance name: Reset Maintenance
@@ -228,9 +228,12 @@
"data": { "data": {
"trigger_above": "Trigger when above", "trigger_above": "Trigger when above",
"trigger_below": "Trigger when below", "trigger_below": "Trigger when below",
"trigger_equals": "Trigger when equal to",
"trigger_not_equals": "Trigger when different from",
"trigger_for_minutes": "For at least (minutes)", "trigger_for_minutes": "For at least (minutes)",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers" "auto_complete_on_recovery": "Auto-complete when the sensor recovers"
@@ -238,9 +241,12 @@
"data_description": { "data_description": {
"trigger_above": "Trigger maintenance when value exceeds this threshold.", "trigger_above": "Trigger maintenance when value exceeds this threshold.",
"trigger_below": "Trigger maintenance when value falls below this threshold.", "trigger_below": "Trigger maintenance when value falls below this threshold.",
"trigger_equals": "Trigger maintenance when the value equals this level.",
"trigger_not_equals": "Trigger maintenance when the value differs from this level.",
"trigger_for_minutes": "Value must exceed threshold for this many minutes before triggering.", "trigger_for_minutes": "Value must exceed threshold for this many minutes before triggering.",
"interval_days": "Optional time-based safety interval in addition to the sensor trigger.", "interval_days": "Optional time-based safety interval in addition to the sensor trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning.", "warning_days": "Number of days before due date to show a warning.",
"go_back": "Return to trigger type selection without saving." "go_back": "Return to trigger type selection without saving."
} }
@@ -253,6 +259,7 @@
"trigger_delta_mode": "Delta mode (count changes, not absolute)", "trigger_delta_mode": "Delta mode (count changes, not absolute)",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers", "auto_complete_on_recovery": "Auto-complete when the sensor recovers",
@@ -263,6 +270,7 @@
"trigger_delta_mode": "If enabled, counts the difference from a baseline instead of the absolute value.", "trigger_delta_mode": "If enabled, counts the difference from a baseline instead of the absolute value.",
"interval_days": "Optional time-based safety interval in addition to the counter trigger.", "interval_days": "Optional time-based safety interval in addition to the counter trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning.", "warning_days": "Number of days before due date to show a warning.",
"go_back": "Return to trigger type selection without saving." "go_back": "Return to trigger type selection without saving."
} }
@@ -276,6 +284,7 @@
"trigger_target_changes": "Number of changes to trigger", "trigger_target_changes": "Number of changes to trigger",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers" "auto_complete_on_recovery": "Auto-complete when the sensor recovers"
@@ -286,6 +295,7 @@
"trigger_target_changes": "Number of matching state changes before triggering maintenance.", "trigger_target_changes": "Number of matching state changes before triggering maintenance.",
"interval_days": "Optional time-based safety interval in addition to the state change trigger.", "interval_days": "Optional time-based safety interval in addition to the state change trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning.", "warning_days": "Number of days before due date to show a warning.",
"go_back": "Return to trigger type selection without saving." "go_back": "Return to trigger type selection without saving."
} }
@@ -298,6 +308,7 @@
"trigger_on_states": "Active states (comma-separated)", "trigger_on_states": "Active states (comma-separated)",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers" "auto_complete_on_recovery": "Auto-complete when the sensor recovers"
@@ -307,6 +318,7 @@
"trigger_on_states": "Which sensor states count as 'active'. Leave empty for default: on, 1, true. Example: in_use, running", "trigger_on_states": "Which sensor states count as 'active'. Leave empty for default: on, 1, true. Example: in_use, running",
"interval_days": "Optional time-based safety interval in addition to the runtime trigger.", "interval_days": "Optional time-based safety interval in addition to the runtime trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning.", "warning_days": "Number of days before due date to show a warning.",
"go_back": "Return to trigger type selection without saving." "go_back": "Return to trigger type selection without saving."
} }
@@ -353,12 +365,16 @@
"data": { "data": {
"trigger_above": "Trigger above", "trigger_above": "Trigger above",
"trigger_below": "Trigger below", "trigger_below": "Trigger below",
"trigger_equals": "Trigger when equal to",
"trigger_not_equals": "Trigger when different from",
"trigger_for_minutes": "Duration (minutes)", "trigger_for_minutes": "Duration (minutes)",
"go_back": "Go back" "go_back": "Go back"
}, },
"data_description": { "data_description": {
"trigger_above": "Trigger when value exceeds this threshold.", "trigger_above": "Trigger when value exceeds this threshold.",
"trigger_below": "Trigger when value drops below this threshold.", "trigger_below": "Trigger when value drops below this threshold.",
"trigger_equals": "Trigger maintenance when the value equals this level.",
"trigger_not_equals": "Trigger maintenance when the value differs from this level.",
"trigger_for_minutes": "Value must remain beyond threshold for this duration.", "trigger_for_minutes": "Value must remain beyond threshold for this duration.",
"go_back": "Return to condition type selection without saving." "go_back": "Return to condition type selection without saving."
} }
@@ -886,9 +902,12 @@
"data": { "data": {
"trigger_above": "Trigger when above", "trigger_above": "Trigger when above",
"trigger_below": "Trigger when below", "trigger_below": "Trigger when below",
"trigger_equals": "Trigger when equal to",
"trigger_not_equals": "Trigger when different from",
"trigger_for_minutes": "For at least (minutes)", "trigger_for_minutes": "For at least (minutes)",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers" "auto_complete_on_recovery": "Auto-complete when the sensor recovers"
@@ -896,9 +915,12 @@
"data_description": { "data_description": {
"trigger_above": "Trigger maintenance when value exceeds this threshold.", "trigger_above": "Trigger maintenance when value exceeds this threshold.",
"trigger_below": "Trigger maintenance when value falls below this threshold.", "trigger_below": "Trigger maintenance when value falls below this threshold.",
"trigger_equals": "Trigger maintenance when the value equals this level.",
"trigger_not_equals": "Trigger maintenance when the value differs from this level.",
"trigger_for_minutes": "Value must exceed threshold for this many minutes before triggering.", "trigger_for_minutes": "Value must exceed threshold for this many minutes before triggering.",
"interval_days": "Optional time-based safety interval in addition to the sensor trigger.", "interval_days": "Optional time-based safety interval in addition to the sensor trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning." "warning_days": "Number of days before due date to show a warning."
} }
}, },
@@ -910,6 +932,7 @@
"trigger_delta_mode": "Delta mode (count changes, not absolute)", "trigger_delta_mode": "Delta mode (count changes, not absolute)",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers", "auto_complete_on_recovery": "Auto-complete when the sensor recovers",
@@ -920,6 +943,7 @@
"trigger_delta_mode": "If enabled, counts the difference from a baseline instead of the absolute value.", "trigger_delta_mode": "If enabled, counts the difference from a baseline instead of the absolute value.",
"interval_days": "Optional time-based safety interval in addition to the counter trigger.", "interval_days": "Optional time-based safety interval in addition to the counter trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning." "warning_days": "Number of days before due date to show a warning."
} }
}, },
@@ -932,6 +956,7 @@
"trigger_target_changes": "Number of changes to trigger", "trigger_target_changes": "Number of changes to trigger",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers" "auto_complete_on_recovery": "Auto-complete when the sensor recovers"
@@ -942,6 +967,7 @@
"trigger_target_changes": "Number of matching state changes before triggering maintenance.", "trigger_target_changes": "Number of matching state changes before triggering maintenance.",
"interval_days": "Optional time-based safety interval in addition to the state change trigger.", "interval_days": "Optional time-based safety interval in addition to the state change trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning." "warning_days": "Number of days before due date to show a warning."
} }
}, },
@@ -953,6 +979,7 @@
"trigger_on_states": "Active states (comma-separated)", "trigger_on_states": "Active states (comma-separated)",
"interval_days": "Safety interval", "interval_days": "Safety interval",
"interval_unit": "Interval unit", "interval_unit": "Interval unit",
"trigger_combinator": "Combine trigger and interval",
"warning_days": "Warning days before due", "warning_days": "Warning days before due",
"go_back": "Go back", "go_back": "Go back",
"auto_complete_on_recovery": "Auto-complete when the sensor recovers" "auto_complete_on_recovery": "Auto-complete when the sensor recovers"
@@ -962,6 +989,7 @@
"trigger_on_states": "Which sensor states count as 'active'. Leave empty for default: on, 1, true. Example: in_use, running", "trigger_on_states": "Which sensor states count as 'active'. Leave empty for default: on, 1, true. Example: in_use, running",
"interval_days": "Optional time-based safety interval in addition to the runtime trigger.", "interval_days": "Optional time-based safety interval in addition to the runtime trigger.",
"interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.", "interval_unit": "Unit for the interval: days, weeks, months or years. Months and years follow the calendar.",
"trigger_combinator": "Whichever first (default): trigger or elapsed interval makes the task due. Both required: the task only becomes due once the trigger fired and the interval elapsed.",
"warning_days": "Number of days before due date to show a warning." "warning_days": "Number of days before due date to show a warning."
} }
}, },
@@ -1004,12 +1032,16 @@
"data": { "data": {
"trigger_above": "Trigger above", "trigger_above": "Trigger above",
"trigger_below": "Trigger below", "trigger_below": "Trigger below",
"trigger_equals": "Trigger when equal to",
"trigger_not_equals": "Trigger when different from",
"trigger_for_minutes": "Duration (minutes)", "trigger_for_minutes": "Duration (minutes)",
"go_back": "Go back" "go_back": "Go back"
}, },
"data_description": { "data_description": {
"trigger_above": "Trigger when value exceeds this threshold.", "trigger_above": "Trigger when value exceeds this threshold.",
"trigger_below": "Trigger when value drops below this threshold.", "trigger_below": "Trigger when value drops below this threshold.",
"trigger_equals": "Trigger maintenance when the value equals this level.",
"trigger_not_equals": "Trigger maintenance when the value differs from this level.",
"trigger_for_minutes": "Value must remain beyond threshold for this duration." "trigger_for_minutes": "Value must remain beyond threshold for this duration."
} }
}, },
@@ -1458,6 +1490,10 @@
"completed_by": { "completed_by": {
"name": "Completed by", "name": "Completed by",
"description": "Person who performed the maintenance (must be linked to a Home Assistant user). When omitted, the user triggering the call is recorded." "description": "Person who performed the maintenance (must be linked to a Home Assistant user). When omitted, the user triggering the call is recorded."
},
"completed_at": {
"name": "Completed at",
"description": "When the maintenance was actually performed — use it to backfill a past completion. Must not be in the future; when omitted, now is recorded."
} }
} }
}, },
@@ -1739,6 +1775,12 @@
"all": "All entities must trigger" "all": "All entities must trigger"
} }
}, },
"trigger_combinator": {
"options": {
"any": "Trigger or interval (whichever first)",
"all": "Trigger and interval (both required)"
}
},
"notification_title_style": { "notification_title_style": {
"options": { "options": {
"default": "Default (per-status title)", "default": "Default (per-status title)",
@@ -1925,6 +1967,9 @@
}, },
"completion_details_required": { "completion_details_required": {
"message": "“{task_name}” requires these details when you complete it: {fields}. Complete it from the Maintenance panel or card so you can fill them in." "message": "“{task_name}” requires these details when you complete it: {fields}. Complete it from the Maintenance panel or card so you can fill them in."
},
"completed_at_in_future": {
"message": "The completion date cannot be in the future."
} }
}, },
"triggers": { "triggers": {
@@ -228,9 +228,12 @@
"data": { "data": {
"trigger_above": "Spustit nad", "trigger_above": "Spustit nad",
"trigger_below": "Spustit pod", "trigger_below": "Spustit pod",
"trigger_equals": "Spustit při rovnosti",
"trigger_not_equals": "Spustit při odlišnosti od",
"trigger_for_minutes": "Po dobu alespoň (minut)", "trigger_for_minutes": "Po dobu alespoň (minut)",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru" "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru"
@@ -238,9 +241,12 @@
"data_description": { "data_description": {
"trigger_above": "Spustit údržbu, když hodnota překročí tento práh.", "trigger_above": "Spustit údržbu, když hodnota překročí tento práh.",
"trigger_below": "Spustit údržbu, když hodnota klesne pod tento práh.", "trigger_below": "Spustit údržbu, když hodnota klesne pod tento práh.",
"trigger_equals": "Spustit údržbu, když se hodnota rovná této úrovni.",
"trigger_not_equals": "Spustit údržbu, když se hodnota liší od této úrovně.",
"trigger_for_minutes": "Hodnota musí překračovat práh tolik minut před spuštěním.", "trigger_for_minutes": "Hodnota musí překračovat práh tolik minut před spuštěním.",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči senzoru.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči senzoru.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění.", "warning_days": "Počet dní před termínem pro zobrazení upozornění.",
"go_back": "Návrat k výběru typu spouště bez uložení." "go_back": "Návrat k výběru typu spouště bez uložení."
} }
@@ -253,6 +259,7 @@
"trigger_delta_mode": "Delta režim (počítej změny, ne absolutní hodnotu)", "trigger_delta_mode": "Delta režim (počítej změny, ne absolutní hodnotu)",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru", "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru",
@@ -263,6 +270,7 @@
"trigger_delta_mode": "Pokud je povoleno, počítá rozdíl od základní hodnoty místo absolutní hodnoty.", "trigger_delta_mode": "Pokud je povoleno, počítá rozdíl od základní hodnoty místo absolutní hodnoty.",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči čítače.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči čítače.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění.", "warning_days": "Počet dní před termínem pro zobrazení upozornění.",
"go_back": "Návrat k výběru typu spouště bez uložení." "go_back": "Návrat k výběru typu spouště bez uložení."
} }
@@ -276,6 +284,7 @@
"trigger_target_changes": "Počet změn pro spuštění", "trigger_target_changes": "Počet změn pro spuštění",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru" "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru"
@@ -286,6 +295,7 @@
"trigger_target_changes": "Počet odpovídajících změn stavu před spuštěním údržby.", "trigger_target_changes": "Počet odpovídajících změn stavu před spuštěním údržby.",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči změny stavu.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči změny stavu.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění.", "warning_days": "Počet dní před termínem pro zobrazení upozornění.",
"go_back": "Návrat k výběru typu spouště bez uložení." "go_back": "Návrat k výběru typu spouště bez uložení."
} }
@@ -298,6 +308,7 @@
"trigger_on_states": "Aktivní stavy (oddělené čárkou)", "trigger_on_states": "Aktivní stavy (oddělené čárkou)",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru" "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru"
@@ -307,6 +318,7 @@
"trigger_on_states": "Které stavy senzoru se počítají jako „aktivní“. Nechte prázdné pro výchozí: on, 1, true. Příklad: in_use, running", "trigger_on_states": "Které stavy senzoru se počítají jako „aktivní“. Nechte prázdné pro výchozí: on, 1, true. Příklad: in_use, running",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči doby běhu.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči doby běhu.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění.", "warning_days": "Počet dní před termínem pro zobrazení upozornění.",
"go_back": "Návrat k výběru typu spouště bez uložení." "go_back": "Návrat k výběru typu spouště bez uložení."
} }
@@ -353,12 +365,16 @@
"data": { "data": {
"trigger_above": "Spustit nad", "trigger_above": "Spustit nad",
"trigger_below": "Spustit pod", "trigger_below": "Spustit pod",
"trigger_equals": "Spustit při rovnosti",
"trigger_not_equals": "Spustit při odlišnosti od",
"trigger_for_minutes": "Doba trvání (minuty)", "trigger_for_minutes": "Doba trvání (minuty)",
"go_back": "Zpět" "go_back": "Zpět"
}, },
"data_description": { "data_description": {
"trigger_above": "Spustit, když hodnota překročí tento práh.", "trigger_above": "Spustit, když hodnota překročí tento práh.",
"trigger_below": "Spustit, když hodnota klesne pod tento práh.", "trigger_below": "Spustit, když hodnota klesne pod tento práh.",
"trigger_equals": "Spustit údržbu, když se hodnota rovná této úrovni.",
"trigger_not_equals": "Spustit údržbu, když se hodnota liší od této úrovně.",
"trigger_for_minutes": "Hodnota musí zůstat mimo práh po tuto dobu.", "trigger_for_minutes": "Hodnota musí zůstat mimo práh po tuto dobu.",
"go_back": "Návrat k výběru typu podmínky bez uložení." "go_back": "Návrat k výběru typu podmínky bez uložení."
} }
@@ -762,12 +778,16 @@
"data": { "data": {
"trigger_above": "Spustit nad", "trigger_above": "Spustit nad",
"trigger_below": "Spustit pod", "trigger_below": "Spustit pod",
"trigger_equals": "Spustit při rovnosti",
"trigger_not_equals": "Spustit při odlišnosti od",
"trigger_for_minutes": "Doba trvání (minuty)", "trigger_for_minutes": "Doba trvání (minuty)",
"go_back": "Zpět" "go_back": "Zpět"
}, },
"data_description": { "data_description": {
"trigger_above": "Spustit, když hodnota překročí tento práh.", "trigger_above": "Spustit, když hodnota překročí tento práh.",
"trigger_below": "Spustit, když hodnota klesne pod tento práh.", "trigger_below": "Spustit, když hodnota klesne pod tento práh.",
"trigger_equals": "Spustit údržbu, když se hodnota rovná této úrovni.",
"trigger_not_equals": "Spustit údržbu, když se hodnota liší od této úrovně.",
"trigger_for_minutes": "Hodnota musí zůstat mimo práh po tuto dobu." "trigger_for_minutes": "Hodnota musí zůstat mimo práh po tuto dobu."
} }
}, },
@@ -1004,9 +1024,12 @@
"data": { "data": {
"trigger_above": "Spustit nad", "trigger_above": "Spustit nad",
"trigger_below": "Spustit pod", "trigger_below": "Spustit pod",
"trigger_equals": "Spustit při rovnosti",
"trigger_not_equals": "Spustit při odlišnosti od",
"trigger_for_minutes": "Po dobu alespoň (minut)", "trigger_for_minutes": "Po dobu alespoň (minut)",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru" "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru"
@@ -1014,9 +1037,12 @@
"data_description": { "data_description": {
"trigger_above": "Spustit údržbu, když hodnota překročí tento práh.", "trigger_above": "Spustit údržbu, když hodnota překročí tento práh.",
"trigger_below": "Spustit údržbu, když hodnota klesne pod tento práh.", "trigger_below": "Spustit údržbu, když hodnota klesne pod tento práh.",
"trigger_equals": "Spustit údržbu, když se hodnota rovná této úrovni.",
"trigger_not_equals": "Spustit údržbu, když se hodnota liší od této úrovně.",
"trigger_for_minutes": "Hodnota musí překračovat práh tolik minut před spuštěním.", "trigger_for_minutes": "Hodnota musí překračovat práh tolik minut před spuštěním.",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči senzoru.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči senzoru.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění." "warning_days": "Počet dní před termínem pro zobrazení upozornění."
} }
}, },
@@ -1028,6 +1054,7 @@
"trigger_delta_mode": "Delta režim (počítej změny, ne absolutní hodnotu)", "trigger_delta_mode": "Delta režim (počítej změny, ne absolutní hodnotu)",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru", "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru",
@@ -1038,6 +1065,7 @@
"trigger_delta_mode": "Pokud je povoleno, počítá rozdíl od základní hodnoty místo absolutní hodnoty.", "trigger_delta_mode": "Pokud je povoleno, počítá rozdíl od základní hodnoty místo absolutní hodnoty.",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči čítače.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči čítače.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění." "warning_days": "Počet dní před termínem pro zobrazení upozornění."
} }
}, },
@@ -1050,6 +1078,7 @@
"trigger_target_changes": "Počet změn pro spuštění", "trigger_target_changes": "Počet změn pro spuštění",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru" "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru"
@@ -1060,6 +1089,7 @@
"trigger_target_changes": "Počet odpovídajících změn stavu před spuštěním údržby.", "trigger_target_changes": "Počet odpovídajících změn stavu před spuštěním údržby.",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči změny stavu.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči změny stavu.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění." "warning_days": "Počet dní před termínem pro zobrazení upozornění."
} }
}, },
@@ -1071,6 +1101,7 @@
"trigger_on_states": "Aktivní stavy (oddělené čárkou)", "trigger_on_states": "Aktivní stavy (oddělené čárkou)",
"interval_days": "Bezpečnostní interval", "interval_days": "Bezpečnostní interval",
"interval_unit": "Jednotka intervalu", "interval_unit": "Jednotka intervalu",
"trigger_combinator": "Kombinovat spouštěč a interval",
"warning_days": "Dny upozornění před termínem", "warning_days": "Dny upozornění před termínem",
"go_back": "Zpět", "go_back": "Zpět",
"auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru" "auto_complete_on_recovery": "Automaticky dokončit při zotavení senzoru"
@@ -1080,6 +1111,7 @@
"trigger_on_states": "Které stavy senzoru se počítají jako „aktivní“. Nechte prázdné pro výchozí: on, 1, true. Příklad: in_use, running", "trigger_on_states": "Které stavy senzoru se počítají jako „aktivní“. Nechte prázdné pro výchozí: on, 1, true. Příklad: in_use, running",
"interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči doby běhu.", "interval_days": "Volitelný bezpečnostní interval založený na čase, navíc ke spouštěči doby běhu.",
"interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.", "interval_unit": "Jednotka intervalu: dny, týdny, měsíce nebo roky. Měsíce a roky se řídí kalendářem.",
"trigger_combinator": "Co nastane dřív (výchozí): spouštěč nebo uplynulý interval učiní úkol splatným. Obojí vyžadováno: úkol je splatný, až když spouštěč vystřelil a interval uplynul.",
"warning_days": "Počet dní před termínem pro zobrazení upozornění." "warning_days": "Počet dní před termínem pro zobrazení upozornění."
} }
}, },
@@ -1482,6 +1514,12 @@
"all": "Všechny entity musí spouštět" "all": "Všechny entity musí spouštět"
} }
}, },
"trigger_combinator": {
"options": {
"any": "Spouštěč nebo interval (co dřív)",
"all": "Spouštěč a interval (obojí vyžadováno)"
}
},
"notification_title_style": { "notification_title_style": {
"options": { "options": {
"default": "Výchozí (titulek podle stavu)", "default": "Výchozí (titulek podle stavu)",
@@ -1518,6 +1556,10 @@
"completed_by": { "completed_by": {
"name": "Dokončil(a)", "name": "Dokončil(a)",
"description": "Osoba, která údržbu provedla (musí být propojena s uživatelem Home Assistant). Pokud není uvedeno, zaznamená se volající uživatel." "description": "Osoba, která údržbu provedla (musí být propojena s uživatelem Home Assistant). Pokud není uvedeno, zaznamená se volající uživatel."
},
"completed_at": {
"name": "Dokončeno dne",
"description": "Kdy byla údržba skutečně provedena — pro zpětné doplnění dřívějšího dokončení. Nesmí být v budoucnosti; při vynechání se zaznamená nyní."
} }
} }
}, },
@@ -1925,6 +1967,9 @@
}, },
"completion_details_required": { "completion_details_required": {
"message": "„{task_name}“ při dokončení vyžaduje tyto údaje: {fields}. Dokončete úlohu v panelu údržby nebo na kartě, abyste je mohli vyplnit." "message": "„{task_name}“ při dokončení vyžaduje tyto údaje: {fields}. Dokončete úlohu v panelu údržby nebo na kartě, abyste je mohli vyplnit."
},
"completed_at_in_future": {
"message": "Datum dokončení nesmí být v budoucnosti."
} }
}, },
"triggers": { "triggers": {

Some files were not shown because too many files have changed in this diff Show More