429 files
This commit is contained in:
1 parent
472c19bceb
commit
963cce81a7
566 files changed
+50311
-47616
No files matched your search
@@ -8,6 +8,7 @@ from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
from homeassistant.auth import EVENT_USER_REMOVED
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_ENTITY_ID, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import (
|
||||
@@ -51,6 +52,7 @@ from .const import (
|
||||
CONF_INSTALL_ASSIST_SENTENCES,
|
||||
CONF_OBJECT,
|
||||
CONF_PANEL_ENABLED,
|
||||
CONF_SHOPPING_LIST_ENTITY,
|
||||
CONF_TASKS,
|
||||
CONF_WEEKLY_DIGEST_ENABLED,
|
||||
DEFAULT_PANEL_ENABLED,
|
||||
@@ -141,6 +143,7 @@ SERVICE_COMPLETE_SCHEMA = vol.Schema(
|
||||
# #133: when the maintenance was actually performed (backfill a past
|
||||
# completion). Future values are rejected by the coordinator.
|
||||
vol.Optional("completed_at"): cv.datetime,
|
||||
vol.Optional("via_tag_scan"): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -422,6 +425,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
await doc_store.async_load()
|
||||
hass.data[DOMAIN][DOCUMENT_STORE_KEY] = doc_store
|
||||
|
||||
|
||||
# Authenticated upload + serve endpoints for document blobs (the blobs live
|
||||
# under /config, so they must never be exposed via an unauthenticated path).
|
||||
from .views import async_register_document_views
|
||||
@@ -478,6 +482,9 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
reading_value=call.data.get("reading_value"),
|
||||
completed_by=completed_by,
|
||||
completed_at=call.data.get("completed_at"),
|
||||
# Proof of presence: an automation that reacted to the physical
|
||||
# scan itself passes via_tag_scan to satisfy require_tag_scan.
|
||||
tag_verified=bool(call.data.get("via_tag_scan")),
|
||||
)
|
||||
|
||||
async def _handle_reset(call: ServiceCall) -> None:
|
||||
@@ -765,7 +772,14 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
try:
|
||||
if action_type == "complete":
|
||||
_LOGGER.info("Completing task %s via notification action", task_id)
|
||||
await runtime_data.coordinator.complete_maintenance(task_id=task_id, unattended=True)
|
||||
await runtime_data.coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
unattended=True,
|
||||
# mobile_app fires the action with the registration's
|
||||
# user in the context - credit them (#128 attribution,
|
||||
# feeds least_completed rotation); bug audit 2026-08-29.
|
||||
completed_by=event.context.user_id if event.context else None,
|
||||
)
|
||||
elif action_type == "skip":
|
||||
_LOGGER.info("Skipping task %s via notification action", task_id)
|
||||
await runtime_data.coordinator.skip_maintenance(task_id=task_id, reason="Skipped from notification")
|
||||
@@ -835,6 +849,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
completed_by=user_id,
|
||||
notes="Completed via NFC tag",
|
||||
unattended=True,
|
||||
tag_verified=True,
|
||||
)
|
||||
except ServiceValidationError as err:
|
||||
# A tag tap cannot capture a cost or a photo. Log it
|
||||
@@ -862,7 +877,10 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
# (DeviceInfo.suggested_area only fires once, on first device creation).
|
||||
@callback
|
||||
def _object_entries_for_device(
|
||||
hass: HomeAssistant, device: dr.DeviceEntry
|
||||
# object: HA 2026.9 registry lookups return DeviceEntry |
|
||||
# ChildDeviceEntry (a union 2026.7 cannot import); the body reads
|
||||
# attributes via getattr already.
|
||||
hass: HomeAssistant, device: object
|
||||
) -> list[ConfigEntry]:
|
||||
"""Our object entries that live on this device.
|
||||
|
||||
@@ -894,7 +912,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
for ce in hass.config_entries.async_entries(DOMAIN):
|
||||
if ce.entry_id in seen or ce.unique_id == GLOBAL_UNIQUE_ID:
|
||||
continue
|
||||
if (ce.data.get(CONF_OBJECT) or {}).get("ha_device_id") == device.id:
|
||||
if (ce.data.get(CONF_OBJECT) or {}).get("ha_device_id") == getattr(device, "id", None):
|
||||
found.append(ce)
|
||||
return found
|
||||
|
||||
@@ -940,6 +958,21 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
|
||||
from .helpers.entity_rename import rewrite_store, rewrite_tasks
|
||||
|
||||
# v2.67: the global shopping-list target is an entity reference too.
|
||||
gentry = next(
|
||||
(e for e in hass.config_entries.async_entries(DOMAIN) if e.unique_id == GLOBAL_UNIQUE_ID),
|
||||
None,
|
||||
)
|
||||
if gentry is not None and gentry.options.get(CONF_SHOPPING_LIST_ENTITY) == old_eid:
|
||||
hass.config_entries.async_update_entry(
|
||||
gentry, options={**gentry.options, CONF_SHOPPING_LIST_ENTITY: new_eid}
|
||||
)
|
||||
from .shopping_sync import SHOPPING_SYNC_KEY
|
||||
|
||||
sync = hass.data.get(DOMAIN, {}).get(SHOPPING_SYNC_KEY)
|
||||
if sync is not None:
|
||||
await sync.async_handle_rename(old_eid, new_eid)
|
||||
|
||||
for ce in hass.config_entries.async_entries(DOMAIN):
|
||||
if ce.unique_id == GLOBAL_UNIQUE_ID:
|
||||
continue
|
||||
@@ -984,6 +1017,20 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
|
||||
unsub_entity = hass.bus.async_listen(er.EVENT_ENTITY_REGISTRY_UPDATED, _on_entity_registry_update)
|
||||
|
||||
# A deleted HA user used to be reconciled only at the next boot: the
|
||||
# least_completed rotation kept assigning the ghost meanwhile and the
|
||||
# admin-panel allowlist kept the stale id (bug audit 2026-08-29).
|
||||
async def _on_user_removed(_event: Event) -> None:
|
||||
await _check_task_responsible_user_orphans(hass)
|
||||
gentry = next(
|
||||
(e for e in hass.config_entries.async_entries(DOMAIN) if e.unique_id == GLOBAL_UNIQUE_ID),
|
||||
None,
|
||||
)
|
||||
if gentry is not None:
|
||||
await _check_admin_panel_user_orphans(hass, gentry)
|
||||
|
||||
unsub_user_removed = hass.bus.async_listen(EVENT_USER_REMOVED, _on_user_removed)
|
||||
|
||||
# v2.10.0: daily archive / auto-delete retention sweep (completed one-offs).
|
||||
# The pure decision logic + the sweep live in helpers/retention; this just
|
||||
# paces it once a day. First fire is +24h, which is plenty for a day-granular
|
||||
@@ -1003,6 +1050,16 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
await async_maybe_send_lead_reminders(hass)
|
||||
|
||||
unsub_digest = async_track_time_change(hass, _weekly_digest_tick, hour=8, minute=0, second=0)
|
||||
|
||||
# The 08:00 tick drops lead reminders inside quiet hours; a quiet window
|
||||
# ending after 08:00 therefore silenced them for good (bug audit
|
||||
# 2026-08-29). A second, reminder-only pass at noon catches those - the
|
||||
# per-day dedup inside makes it a no-op when 08:00 already delivered.
|
||||
async def _lead_reminder_retry(now: Any) -> None:
|
||||
await async_maybe_send_lead_reminders(hass)
|
||||
|
||||
unsub_lead_retry = async_track_time_change(hass, _lead_reminder_retry, hour=12, minute=0, second=0)
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, lambda _e: unsub_lead_retry())
|
||||
# async_track_time_change has no cancel_on_shutdown, so cancel it explicitly
|
||||
# on HA stop too — otherwise the daily timer lingers past shutdown when the
|
||||
# entries aren't formally unloaded (e.g. test teardown).
|
||||
@@ -1010,6 +1067,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
|
||||
# Store unsub callbacks so they can be cleaned up when domain is unloaded
|
||||
hass.data[DOMAIN][EVENT_UNSUBS_KEY] = [
|
||||
unsub_user_removed,
|
||||
unsub_notification,
|
||||
unsub_tag,
|
||||
unsub_action,
|
||||
@@ -1017,6 +1075,7 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
unsub_entity,
|
||||
unsub_retention,
|
||||
unsub_digest,
|
||||
unsub_lead_retry,
|
||||
]
|
||||
|
||||
# Once HA has started (all entries loaded), flag object entries that were
|
||||
@@ -1195,6 +1254,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: MaintenanceSupporterConf
|
||||
entry.async_on_unload(summary_coordinator.async_teardown_listeners)
|
||||
await summary_coordinator.async_refresh()
|
||||
|
||||
# v2.67: mirror auto "buy" tasks into the user's to-do list (no-op
|
||||
# until the shopping_list_entity option is set). Owned by the GLOBAL
|
||||
# entry's lifecycle - created here, torn down in async_unload_entry -
|
||||
# so a reload of the hub entry gets a fresh instance (the shared-
|
||||
# runtime setup only runs once per boot; bug audit 2026-08-29).
|
||||
from .shopping_sync import SHOPPING_SYNC_KEY, ShoppingListSync
|
||||
|
||||
if SHOPPING_SYNC_KEY not in hass.data[DOMAIN]:
|
||||
shopping_sync = ShoppingListSync(hass)
|
||||
await shopping_sync.async_setup()
|
||||
hass.data[DOMAIN][SHOPPING_SYNC_KEY] = shopping_sync
|
||||
|
||||
# One-time migration: auto-enable advanced feature flags for existing users
|
||||
options = dict(entry.options or entry.data)
|
||||
if CONF_ADVANCED_ADAPTIVE not in options:
|
||||
@@ -1563,6 +1634,13 @@ async def _async_global_options_updated(hass: HomeAssistant, entry: ConfigEntry)
|
||||
await async_sync_assist_sentences(
|
||||
hass, entry.options.get(CONF_INSTALL_ASSIST_SENTENCES, False)
|
||||
)
|
||||
# v2.67: the shopping-list target may have changed (set / cleared / swapped).
|
||||
# This listener does NOT reload the entry, so the sync must be nudged
|
||||
# explicitly - it re-reads the option, re-arms its state listener and
|
||||
# pulls its rows out of a list that is no longer the target.
|
||||
from .shopping_sync import schedule_resync
|
||||
|
||||
schedule_resync(hass)
|
||||
|
||||
|
||||
_ORPHAN_ISSUE_PREFIX = "orphan_admin_panel_user_"
|
||||
@@ -1693,6 +1771,11 @@ async def async_unload_entry(hass: HomeAssistant, entry: MaintenanceSupporterCon
|
||||
if entry.unique_id == GLOBAL_UNIQUE_ID:
|
||||
# Unregister panel when global entry is unloaded
|
||||
await async_unregister_panel(hass)
|
||||
from .shopping_sync import SHOPPING_SYNC_KEY
|
||||
|
||||
shopping_sync = hass.data.get(DOMAIN, {}).pop(SHOPPING_SYNC_KEY, None)
|
||||
if shopping_sync is not None:
|
||||
shopping_sync.async_teardown()
|
||||
|
||||
# Flush a pending debounced store save BEFORE tearing down — belt and
|
||||
# suspenders next to the store cache: disk is current the moment the entry
|
||||
@@ -1773,6 +1856,10 @@ async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
|
||||
async_dispatcher_send(hass, SIGNAL_OBJECT_ENTRY_REMOVED, entry.entry_id)
|
||||
# Its buy-task rows in the shopping list are orphans now (bug audit 2026-08-29).
|
||||
from .shopping_sync import schedule_resync as _schedule_shopping_resync
|
||||
|
||||
_schedule_shopping_resync(hass)
|
||||
|
||||
|
||||
async def async_remove_config_entry_device(
|
||||
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (místo uložení: {loc})",
|
||||
"stock_low": " — na úrovni objednacího limitu nebo pod ním",
|
||||
"stock_untracked": "{part} — u tohoto dílu sklad nesleduju.",
|
||||
"part_not_found": "Nenašel jsem náhradní díl, který by odpovídal '{name}'."
|
||||
"part_not_found": "Nenašel jsem náhradní díl, který by odpovídal '{name}'.",
|
||||
"st_phase": " — další krok: {phase}",
|
||||
"st_priority": ", vysoká priorita",
|
||||
"completed_phase": "'{task}' na {object} dokončeno — krok: {phase}. Příště: {next}.",
|
||||
"guide_phase": "Aktuální krok: {phase} (krok {index} z {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (opbevaringssted {loc})",
|
||||
"stock_low": " — på eller under genbestillingsgrænsen",
|
||||
"stock_untracked": "Der føres ikke lager for {part}.",
|
||||
"part_not_found": "Jeg kunne ikke finde en reservedel, der passer på '{name}'."
|
||||
"part_not_found": "Jeg kunne ikke finde en reservedel, der passer på '{name}'.",
|
||||
"st_phase": " — næste trin: {phase}",
|
||||
"st_priority": ", høj prioritet",
|
||||
"completed_phase": "'{task}' på {object} udført — trin: {phase}. Næste gang: {next}.",
|
||||
"guide_phase": "Aktuelt trin: {phase} (trin {index} af {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (Lagerort: {loc})",
|
||||
"stock_low": " — an oder unter der Nachbestellgrenze",
|
||||
"stock_untracked": "Für {part} wird kein Bestand geführt.",
|
||||
"part_not_found": "Ich habe kein Ersatzteil zu '{name}' gefunden."
|
||||
"part_not_found": "Ich habe kein Ersatzteil zu '{name}' gefunden.",
|
||||
"st_phase": " — nächster Schritt: {phase}",
|
||||
"st_priority": ", hohe Priorität",
|
||||
"completed_phase": "'{task}' an {object} erledigt — Schritt: {phase}. Beim nächsten Mal: {next}.",
|
||||
"guide_phase": "Aktueller Schritt: {phase} (Schritt {index} von {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (stored at {loc})",
|
||||
"stock_low": " — at or below the reorder threshold",
|
||||
"stock_untracked": "Stock isn't tracked for {part}.",
|
||||
"part_not_found": "I couldn't find a spare part matching '{name}'."
|
||||
"part_not_found": "I couldn't find a spare part matching '{name}'.",
|
||||
"st_phase": " — next step: {phase}",
|
||||
"st_priority": ", high priority",
|
||||
"completed_phase": "Completed '{task}' on {object} — step done: {phase}. Next time: {next}.",
|
||||
"guide_phase": "Current step: {phase} (step {index} of {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (guardado en {loc})",
|
||||
"stock_low": " — en el umbral de reposición o por debajo",
|
||||
"stock_untracked": "De {part} no se lleva control de stock.",
|
||||
"part_not_found": "No he encontrado ningún recambio que coincida con '{name}'."
|
||||
"part_not_found": "No he encontrado ningún recambio que coincida con '{name}'.",
|
||||
"st_phase": " — siguiente paso: {phase}",
|
||||
"st_priority": ", prioridad alta",
|
||||
"completed_phase": "'{task}' en {object} completado — paso: {phase}. La próxima vez: {next}.",
|
||||
"guide_phase": "Paso actual: {phase} (paso {index} de {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (säilytyspaikka {loc})",
|
||||
"stock_low": " — tilausrajalla tai sen alapuolella",
|
||||
"stock_untracked": "Varastosaldoa ei seurata varaosalle {part}.",
|
||||
"part_not_found": "En löytänyt varaosaa, joka vastaisi hakua '{name}'."
|
||||
"part_not_found": "En löytänyt varaosaa, joka vastaisi hakua '{name}'.",
|
||||
"st_phase": " — seuraava vaihe: {phase}",
|
||||
"st_priority": ", korkea prioriteetti",
|
||||
"completed_phase": "'{task}' ({object}) valmis — vaihe: {phase}. Ensi kerralla: {next}.",
|
||||
"guide_phase": "Nykyinen vaihe: {phase} (vaihe {index}/{count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (rangé à {loc})",
|
||||
"stock_low": " — au niveau ou en dessous du seuil de réapprovisionnement",
|
||||
"stock_untracked": "Le stock n'est pas suivi pour {part}.",
|
||||
"part_not_found": "Je n'ai pas trouvé de pièce détachée qui corresponde à '{name}'."
|
||||
"part_not_found": "Je n'ai pas trouvé de pièce détachée qui corresponde à '{name}'.",
|
||||
"st_phase": " — prochaine étape : {phase}",
|
||||
"st_priority": ", priorité haute",
|
||||
"completed_phase": "« {task} » sur {object} terminé — étape : {phase}. La prochaine fois : {next}.",
|
||||
"guide_phase": "Étape actuelle : {phase} (étape {index} sur {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (रखने की जगह: {loc})",
|
||||
"stock_low": " — दोबारा ऑर्डर करने की सीमा पर या उससे नीचे",
|
||||
"stock_untracked": "{part} के लिए स्टॉक ट्रैक नहीं किया जाता।",
|
||||
"part_not_found": "'{name}' से मिलता-जुलता कोई स्पेयर पार्ट नहीं मिला।"
|
||||
"part_not_found": "'{name}' से मिलता-जुलता कोई स्पेयर पार्ट नहीं मिला।",
|
||||
"st_phase": " — अगला चरण: {phase}",
|
||||
"st_priority": ", उच्च प्राथमिकता",
|
||||
"completed_phase": "{object} पर '{task}' पूर्ण — चरण: {phase}। अगली बार: {next}।",
|
||||
"guide_phase": "वर्तमान चरण: {phase} ({count} में से चरण {index})।"
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (tárolási hely: {loc})",
|
||||
"stock_low": " — az újrarendelési küszöbön vagy alatta",
|
||||
"stock_untracked": "Ehhez nincs készletnyilvántartás: {part}.",
|
||||
"part_not_found": "Nem találtam ehhez illő alkatrészt: '{name}'."
|
||||
"part_not_found": "Nem találtam ehhez illő alkatrészt: '{name}'.",
|
||||
"st_phase": " — következő lépés: {phase}",
|
||||
"st_priority": ", magas prioritás",
|
||||
"completed_phase": "'{task}' ({object}) elvégezve — lépés: {phase}. Legközelebb: {next}.",
|
||||
"guide_phase": "Aktuális lépés: {phase} ({index}. lépés a(z) {count} közül)."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (conservato in {loc})",
|
||||
"stock_low": " — pari o sotto la soglia di riordino",
|
||||
"stock_untracked": "Per {part} non viene tenuta la giacenza.",
|
||||
"part_not_found": "Non ho trovato nessun ricambio che corrisponda a '{name}'."
|
||||
"part_not_found": "Non ho trovato nessun ricambio che corrisponda a '{name}'.",
|
||||
"st_phase": " — prossimo passo: {phase}",
|
||||
"st_priority": ", priorità alta",
|
||||
"completed_phase": "'{task}' su {object} completato — passo: {phase}. La prossima volta: {next}.",
|
||||
"guide_phase": "Passo attuale: {phase} (passo {index} di {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": "(保管場所は{loc})",
|
||||
"stock_low": "。在庫は発注の目安以下です",
|
||||
"stock_untracked": "{part}の在庫は管理していません。",
|
||||
"part_not_found": "「{name}」に一致する交換部品は見つかりませんでした。"
|
||||
"part_not_found": "「{name}」に一致する交換部品は見つかりませんでした。",
|
||||
"st_phase": "(次のステップ: {phase})",
|
||||
"st_priority": "、優先度高",
|
||||
"completed_phase": "{object}の「{task}」を完了しました(ステップ: {phase})。次回は: {next}。",
|
||||
"guide_phase": "現在のステップ: {phase}({count}ステップ中{index}番目)。"
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (보관 위치: {loc})",
|
||||
"stock_low": ". 재주문 기준 이하예요",
|
||||
"stock_untracked": "{part} 재고는 관리하지 않아요.",
|
||||
"part_not_found": "'{name}'에 맞는 예비 부품을 찾지 못했어요."
|
||||
"part_not_found": "'{name}'에 맞는 예비 부품을 찾지 못했어요.",
|
||||
"st_phase": " — 다음 단계: {phase}",
|
||||
"st_priority": ", 높은 우선순위",
|
||||
"completed_phase": "{object}의 '{task}' 완료 — 단계: {phase}. 다음번: {next}.",
|
||||
"guide_phase": "현재 단계: {phase} ({count}단계 중 {index}단계)."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (oppbevaringssted {loc})",
|
||||
"stock_low": " — på eller under bestillingsgrensen",
|
||||
"stock_untracked": "Det føres ikke lager for {part}.",
|
||||
"part_not_found": "Jeg fant ingen reservedel som passer med '{name}'."
|
||||
"part_not_found": "Jeg fant ingen reservedel som passer med '{name}'.",
|
||||
"st_phase": " — neste trinn: {phase}",
|
||||
"st_priority": ", høy prioritet",
|
||||
"completed_phase": "'{task}' på {object} fullført — trinn: {phase}. Neste gang: {next}.",
|
||||
"guide_phase": "Gjeldende trinn: {phase} (trinn {index} av {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (ligt in {loc})",
|
||||
"stock_low": " — op of onder de bestelgrens",
|
||||
"stock_untracked": "Van {part} wordt de voorraad niet bijgehouden.",
|
||||
"part_not_found": "Ik kon geen onderdeel vinden dat past bij '{name}'."
|
||||
"part_not_found": "Ik kon geen onderdeel vinden dat past bij '{name}'.",
|
||||
"st_phase": " — volgende stap: {phase}",
|
||||
"st_priority": ", hoge prioriteit",
|
||||
"completed_phase": "'{task}' op {object} afgerond — stap: {phase}. Volgende keer: {next}.",
|
||||
"guide_phase": "Huidige stap: {phase} (stap {index} van {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (miejsce przechowywania: {loc})",
|
||||
"stock_low": " — na poziomie progu zamówienia lub poniżej",
|
||||
"stock_untracked": "{part} — nie prowadzę stanu magazynowego dla tej części.",
|
||||
"part_not_found": "Nie znalazłem części zamiennej pasującej do '{name}'."
|
||||
"part_not_found": "Nie znalazłem części zamiennej pasującej do '{name}'.",
|
||||
"st_phase": " — następny krok: {phase}",
|
||||
"st_priority": ", wysoki priorytet",
|
||||
"completed_phase": "Ukończono '{task}' ({object}) — krok: {phase}. Następnym razem: {next}.",
|
||||
"guide_phase": "Bieżący krok: {phase} (krok {index} z {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (local: {loc})",
|
||||
"stock_low": " — no limite de reposição ou abaixo dele",
|
||||
"stock_untracked": "Não há controle de estoque para {part}.",
|
||||
"part_not_found": "Não encontrei nenhuma peça de reposição que corresponda a '{name}'."
|
||||
"part_not_found": "Não encontrei nenhuma peça de reposição que corresponda a '{name}'.",
|
||||
"st_phase": " — próximo passo: {phase}",
|
||||
"st_priority": ", prioridade alta",
|
||||
"completed_phase": "'{task}' em {object} concluído — passo: {phase}. Na próxima vez: {next}.",
|
||||
"guide_phase": "Passo atual: {phase} (passo {index} de {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (guardado em {loc})",
|
||||
"stock_low": " — no limite de reposição ou abaixo",
|
||||
"stock_untracked": "O stock de {part} não é acompanhado.",
|
||||
"part_not_found": "Não encontrei nenhuma peça que corresponda a '{name}'."
|
||||
"part_not_found": "Não encontrei nenhuma peça que corresponda a '{name}'.",
|
||||
"st_phase": " — próximo passo: {phase}",
|
||||
"st_priority": ", prioridade alta",
|
||||
"completed_phase": "'{task}' em {object} concluído — passo: {phase}. Da próxima vez: {next}.",
|
||||
"guide_phase": "Passo atual: {phase} (passo {index} de {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (место хранения: {loc})",
|
||||
"stock_low": " — на уровне порога дозаказа или ниже",
|
||||
"stock_untracked": "{part} — запас по этой детали не отслеживается.",
|
||||
"part_not_found": "Я не нашёл запчасть, подходящую под '{name}'."
|
||||
"part_not_found": "Я не нашёл запчасть, подходящую под '{name}'.",
|
||||
"st_phase": " — следующий шаг: {phase}",
|
||||
"st_priority": ", высокий приоритет",
|
||||
"completed_phase": "«{task}» ({object}) выполнено — шаг: {phase}. В следующий раз: {next}.",
|
||||
"guide_phase": "Текущий шаг: {phase} (шаг {index} из {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (förvaringsplats {loc})",
|
||||
"stock_low": " — vid eller under beställningsgränsen",
|
||||
"stock_untracked": "Lagersaldot följs inte för {part}.",
|
||||
"part_not_found": "Jag hittade ingen reservdel som matchar '{name}'."
|
||||
"part_not_found": "Jag hittade ingen reservdel som matchar '{name}'.",
|
||||
"st_phase": " — nästa steg: {phase}",
|
||||
"st_priority": ", hög prioritet",
|
||||
"completed_phase": "'{task}' på {object} slutfört — steg: {phase}. Nästa gång: {next}.",
|
||||
"guide_phase": "Aktuellt steg: {phase} (steg {index} av {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (saklama yeri: {loc})",
|
||||
"stock_low": " — yeniden sipariş sınırında ya da altında",
|
||||
"stock_untracked": "{part} için stok takibi yapılmıyor.",
|
||||
"part_not_found": "'{name}' ile eşleşen bir yedek parça bulamadım."
|
||||
"part_not_found": "'{name}' ile eşleşen bir yedek parça bulamadım.",
|
||||
"st_phase": " — sonraki adım: {phase}",
|
||||
"st_priority": ", yüksek öncelik",
|
||||
"completed_phase": "{object} üzerindeki '{task}' tamamlandı — adım: {phase}. Bir dahaki sefere: {next}.",
|
||||
"guide_phase": "Geçerli adım: {phase} ({count} adımdan {index}.)."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": " (місце зберігання: {loc})",
|
||||
"stock_low": " — на рівні порога дозамовлення або нижче",
|
||||
"stock_untracked": "{part} — запас цієї деталі не відстежується.",
|
||||
"part_not_found": "Я не знайшов запчастину, яка відповідає '{name}'."
|
||||
"part_not_found": "Я не знайшов запчастину, яка відповідає '{name}'.",
|
||||
"st_phase": " — наступний крок: {phase}",
|
||||
"st_priority": ", високий пріоритет",
|
||||
"completed_phase": "«{task}» ({object}) виконано — крок: {phase}. Наступного разу: {next}.",
|
||||
"guide_phase": "Поточний крок: {phase} (крок {index} з {count})."
|
||||
}
|
||||
@@ -38,5 +38,9 @@
|
||||
"stock_loc": "(存放在{loc})",
|
||||
"stock_low": ",已达到或低于补货下限",
|
||||
"stock_untracked": "{part}的库存没有记录。",
|
||||
"part_not_found": "我没有找到和“{name}”对得上的备件。"
|
||||
"part_not_found": "我没有找到和“{name}”对得上的备件。",
|
||||
"st_phase": "——下一步:{phase}",
|
||||
"st_priority": ",高优先级",
|
||||
"completed_phase": "已完成{object}的“{task}”——步骤:{phase}。下次:{next}。",
|
||||
"guide_phase": "当前步骤:{phase}(第 {index} 步,共 {count} 步)。"
|
||||
}
|
||||
@@ -117,7 +117,10 @@ class MaintenanceActionButton(MaintenanceEntity, ButtonEntity):
|
||||
raise HomeAssistantError(f"Task {self._task_id} no longer exists")
|
||||
if self._action == "complete":
|
||||
await self.coordinator.complete_maintenance(
|
||||
self._task_id, notes="Completed from dashboard button", unattended=True
|
||||
self._task_id,
|
||||
notes="Completed from dashboard button",
|
||||
unattended=True,
|
||||
completed_by=self._context.user_id if self._context else None,
|
||||
)
|
||||
elif self._action == "skip":
|
||||
await self.coordinator.skip_maintenance(self._task_id, reason="Skipped from dashboard button")
|
||||
|
||||
@@ -129,7 +129,10 @@ class MaintenanceSupporterConfigFlow(ScheduleStepsMixin, TriggerConfigMixin, Con
|
||||
{
|
||||
vol.Optional(CONF_DEFAULT_WARNING_DAYS, default=DEFAULT_WARNING_DAYS): selector.NumberSelector(
|
||||
selector.NumberSelectorConfig(
|
||||
min=1, max=WARNING_DAYS_RANGE[1], step=1, mode=selector.NumberSelectorMode.BOX
|
||||
min=WARNING_DAYS_RANGE[0],
|
||||
max=WARNING_DAYS_RANGE[1],
|
||||
step=1,
|
||||
mode=selector.NumberSelectorMode.BOX,
|
||||
)
|
||||
),
|
||||
vol.Optional(CONF_NOTIFICATIONS_ENABLED, default=False): selector.BooleanSelector(),
|
||||
|
||||
@@ -54,6 +54,7 @@ from .const import (
|
||||
CONF_QUIET_HOURS_ENABLED,
|
||||
CONF_QUIET_HOURS_END,
|
||||
CONF_QUIET_HOURS_START,
|
||||
CONF_SHOPPING_LIST_ENTITY,
|
||||
CONF_SNOOZE_DURATION_HOURS,
|
||||
DEFAULT_BUDGET_CURRENCY,
|
||||
DEFAULT_MAX_NOTIFICATIONS_PER_DAY,
|
||||
@@ -576,6 +577,14 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
else:
|
||||
user_input[CONF_NOTIFY_SERVICE] = normalized
|
||||
|
||||
# Shopping-list target: a cleared EntitySelector omits the key —
|
||||
# normalise to "" so clearing actually turns the sync off. Format
|
||||
# only (todo.*): the list's integration may load later.
|
||||
raw_list = (user_input.get(CONF_SHOPPING_LIST_ENTITY) or "").strip()
|
||||
if raw_list and not raw_list.startswith("todo."):
|
||||
errors[CONF_SHOPPING_LIST_ENTITY] = "invalid_shopping_list_entity"
|
||||
user_input[CONF_SHOPPING_LIST_ENTITY] = raw_list
|
||||
|
||||
# Trim + cap the optional sidebar title; blank clears the override
|
||||
# (panel falls back to the default "Maintenance").
|
||||
raw_title = user_input.get(CONF_PANEL_TITLE)
|
||||
@@ -631,6 +640,12 @@ class GlobalOptionsFlow(OptionsFlow):
|
||||
custom_value=True,
|
||||
)
|
||||
),
|
||||
# v2.67: mirror auto "buy" tasks into this to-do list
|
||||
# (blank = off). suggested_value keeps the field clearable.
|
||||
vol.Optional(
|
||||
CONF_SHOPPING_LIST_ENTITY,
|
||||
description={"suggested_value": current.get(CONF_SHOPPING_LIST_ENTITY, "")},
|
||||
): selector.EntitySelector(selector.EntitySelectorConfig(domain="todo")),
|
||||
vol.Optional(
|
||||
CONF_PANEL_ENABLED,
|
||||
default=current.get(CONF_PANEL_ENABLED, DEFAULT_PANEL_ENABLED),
|
||||
|
||||
@@ -264,6 +264,8 @@ class TaskCrudMixin:
|
||||
updated_task[CONF_TASK_NFC_TAG] = nfc_val
|
||||
else:
|
||||
updated_task.pop(CONF_TASK_NFC_TAG, None)
|
||||
# Proof of presence: completion only via NFC/QR scan.
|
||||
updated_task["require_tag_scan"] = bool(user_input.get("require_tag_scan"))
|
||||
# v2.20 (#83): reading unit — clear by submitting "".
|
||||
if CONF_TASK_READING_UNIT in user_input:
|
||||
ru = (user_input.get(CONF_TASK_READING_UNIT) or "").strip()
|
||||
@@ -483,6 +485,7 @@ class TaskCrudMixin:
|
||||
default=", ".join(task.get("labels", [])),
|
||||
): selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)),
|
||||
nfc_tag_key: selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)),
|
||||
vol.Optional("require_tag_scan", default=bool(task.get("require_tag_scan"))): selector.BooleanSelector(),
|
||||
reading_unit_key: selector.TextSelector(selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT)),
|
||||
vol.Optional("go_back", default=False): selector.BooleanSelector(),
|
||||
}
|
||||
|
||||
@@ -111,6 +111,9 @@ SOUTHERN_SEASONS: dict[str, list[int]] = {
|
||||
CONF_DEFAULT_WARNING_DAYS = "default_warning_days"
|
||||
CONF_NOTIFICATIONS_ENABLED = "notifications_enabled"
|
||||
CONF_NOTIFY_SERVICE = "notify_service"
|
||||
# v2.67: mirror auto "buy" tasks into a user-picked HA to-do list (todo.*).
|
||||
# Empty = feature off. Instance-bound → excluded from settings export.
|
||||
CONF_SHOPPING_LIST_ENTITY = "shopping_list_entity"
|
||||
CONF_PANEL_ENABLED = "panel_enabled"
|
||||
# v2.10.4 (#69 follow-up): the sidebar panel is the integration's hub — the
|
||||
# auto-dashboard's "Open Maintenance panel" button, QR codes, and notifications
|
||||
@@ -450,6 +453,20 @@ DEFAULT_DEGRADATION_LOOKBACK_DAYS = 30 # Days of recorder data for slope comput
|
||||
DEFAULT_DEGRADATION_MIN_POINTS = 10 # Min hourly data points to compute regression
|
||||
DEFAULT_DEGRADATION_SIGNIFICANCE = 0.05 # Min |slope|/mean ratio for rising/falling
|
||||
|
||||
# --- Cycle-aware degradation (ET-4800 ink finding, 2026-08) ---
|
||||
# Consumption sensors are sawtooths: the value drifts toward the threshold and
|
||||
# JUMPS back on a refill/service. A regression across that jump reports the
|
||||
# refill as a rising trend. The analysis therefore splits the series into
|
||||
# service cycles at significant against-the-drift jumps, regresses the CURRENT
|
||||
# cycle, and LEARNS the typical rate from prior cycles (median, blended in
|
||||
# while the current cycle is still thin).
|
||||
DEGRADATION_CYCLE_LOOKBACK_DAYS = 180 # window for learning prior cycles
|
||||
DEGRADATION_CYCLE_JUMP_FRACTION = 0.2 # jump ≥ this share of the observed range = boundary
|
||||
DEGRADATION_BLEND_HALF_POINTS = 48 # current-cycle points at which current/history blend 50/50
|
||||
DEGRADATION_MIN_CYCLE_HOURS = 24 # prior cycles shorter than this teach nothing
|
||||
DEGRADATION_CYCLE_NOISE_SIGMAS = 8 # a boundary must also exceed this many robust sigmas of hourly wobble
|
||||
DEGRADATION_CYCLE_SETTLE_POINTS = 24 # a jump that snaps back within this many points is modulation
|
||||
|
||||
# --- Environmental Adjustment Defaults (Phase 3) ---
|
||||
DEFAULT_ENVIRONMENTAL_LOOKBACK_DAYS = 90 # Days of env data for correlation
|
||||
DEFAULT_ENVIRONMENTAL_CORRELATION_MIN = 0.3 # Min |r| to apply adjustment
|
||||
|
||||
@@ -193,6 +193,9 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._recent_manual_completions = {
|
||||
tid: ts for tid, ts in self._recent_manual_completions.items() if now_mono - ts < MANUAL_COMPLETION_DEDUP_SECONDS
|
||||
}
|
||||
self._recent_backfills = {
|
||||
key: ts for key, ts in self._recent_backfills.items() if now_mono - ts < MANUAL_COMPLETION_DEDUP_SECONDS
|
||||
}
|
||||
|
||||
result: dict[str, Any] = {
|
||||
CONF_OBJECT: obj.to_dict(),
|
||||
@@ -357,6 +360,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
task_result["_degradation_trend"] = deg.trend
|
||||
task_result["_degradation_r_squared"] = deg.r_squared
|
||||
task_result["_degradation_data_points"] = deg.data_points
|
||||
task_result["_prediction_cycles"] = deg.cycles_learned
|
||||
|
||||
# Threshold prediction
|
||||
if prediction.threshold_prediction:
|
||||
@@ -716,6 +720,15 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
|
||||
_vac = get_vacation_state(self.hass)
|
||||
notifiable = [row for row in notifiable if not _vac.is_silent_for(row[0])]
|
||||
# A status the user switched off, or a task they snoozed, must neither
|
||||
# count toward the bundle threshold nor ride inside the bundle - the
|
||||
# per-task path checks both, the bundle path did not (bug audit
|
||||
# 2026-08-29).
|
||||
notifiable = [
|
||||
row
|
||||
for row in notifiable
|
||||
if nm._is_status_enabled(row[2]) and not nm._is_snoozed(f"{self.entry.entry_id}_{row[0]}_{row[2]}")
|
||||
]
|
||||
|
||||
# v2.26 notification routing: a saved-view scope ("only notify about
|
||||
# view X") drops tasks the view's label/user filters don't match —
|
||||
@@ -940,6 +953,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
auto: bool = False,
|
||||
unattended: bool = False,
|
||||
completed_at: datetime | None = None,
|
||||
tag_verified: bool = False,
|
||||
) -> None:
|
||||
"""Mark a task as completed and persist.
|
||||
|
||||
@@ -1012,6 +1026,55 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
},
|
||||
)
|
||||
|
||||
# Proof of presence: a task flagged require_tag_scan only completes
|
||||
# from a surface that proves someone stood at the thing — an NFC tap
|
||||
# or the QR quick-complete. Checked at THIS choke point so every
|
||||
# other surface (panel, card, to-do, voice, notification button)
|
||||
# gets the same refusal. Automatic completions are exempt (a
|
||||
# recovered trigger has nobody to send to the machine), and
|
||||
# automations may assert the scan via the service's `via_tag_scan`.
|
||||
if not auto and not tag_verified and merged[task_id].get("require_tag_scan"):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="tag_scan_required",
|
||||
translation_placeholders={
|
||||
"task_name": str(merged[task_id].get("name", task_id)),
|
||||
},
|
||||
)
|
||||
|
||||
# Inert tasks (bug audit 2026-08-29): the auto path refuses archived /
|
||||
# disabled tasks and paused objects (see async_auto_complete_on_recovery),
|
||||
# but the manual path let an NFC sticker on a retired machine, a stale
|
||||
# panel or a voice command record a completion on a task nobody can
|
||||
# see any more - rotation advanced, parts consumed. Same gate here.
|
||||
if not auto:
|
||||
_td = merged[task_id]
|
||||
if (
|
||||
_td.get("archived_at") is not None
|
||||
or _td.get("enabled") is False
|
||||
or self.entry.data.get(CONF_OBJECT, {}).get("paused_at") is not None
|
||||
):
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="task_inactive",
|
||||
translation_placeholders={"task_name": str(_td.get("name", task_id))},
|
||||
)
|
||||
|
||||
# Completion window (earliest_completion_days) - at the choke point like
|
||||
# every other rule. The WS / to-do / voice surfaces pre-check it for a
|
||||
# friendly code, but the service, the dashboard button, an NFC tap and a
|
||||
# notification button reached this method with no gate at all (bug audit
|
||||
# 2026-08-29). A completion dated on a PAST day is a history correction,
|
||||
# not early work, and bypasses the window exactly like the WS pre-check.
|
||||
if not auto:
|
||||
_past_dated = completed_at is not None and completed_at.date() < dt_util.now().date()
|
||||
if not _past_dated and not MaintenanceTask.from_dict(merged[task_id]).can_complete_now:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="too_early",
|
||||
translation_placeholders={"task_name": str(merged[task_id].get("name", task_id))},
|
||||
)
|
||||
|
||||
# Household double-complete guard (journey M1): two people seeing the
|
||||
# same overdue task and both tapping Complete within seconds would
|
||||
# record two completions — duplicated history/cost and a DOUBLE
|
||||
@@ -1077,8 +1140,20 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# stay correctable through the history editor like dialog ones.
|
||||
from .helpers.phases import effective_field as _phase_effective
|
||||
|
||||
# A pure backfill (dated before the last completion) performed no work
|
||||
# in the CURRENT cycle: the automatic consumes_parts / buy-task restock
|
||||
# must not run for it (bug audit 2026-08-29) - an explicit used_parts
|
||||
# selection is still honoured. Mirrors MaintenanceTask.complete's
|
||||
# is_latest split (ISO-date string compare).
|
||||
_is_backfill = (
|
||||
completed_at is not None
|
||||
and bool(task.last_performed)
|
||||
and effective_ts.date().isoformat() < str(task.last_performed)
|
||||
)
|
||||
record_links = (
|
||||
used_parts if used_parts is not None else (_phase_effective(merged[task_id], "consumes_parts") or [])
|
||||
used_parts
|
||||
if used_parts is not None
|
||||
else ([] if _is_backfill else (_phase_effective(merged[task_id], "consumes_parts") or []))
|
||||
)
|
||||
enriched_used: list[dict[str, Any]] | None = None
|
||||
if used_parts is not None or record_links:
|
||||
@@ -1144,10 +1219,24 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
task.adaptive_config["hemisphere"] = "south" if (self.hass.config.latitude or 0) < 0 else "north"
|
||||
task.adaptive_config["_current_month"] = effective_ts.month
|
||||
task.adaptive_config["_current_date"] = effective_ts.date().isoformat()
|
||||
updated_config = analyzer.update_on_completion(task.adaptive_config, actual_interval, feedback)
|
||||
task.adaptive_config = updated_config
|
||||
try:
|
||||
updated_config = analyzer.update_on_completion(task.adaptive_config, actual_interval, feedback)
|
||||
task.adaptive_config = updated_config
|
||||
except (TypeError, ValueError, ZeroDivisionError): # hand-edited / imported adaptive_config
|
||||
_LOGGER.warning("Adaptive learning skipped for %s: malformed adaptive_config", task_id)
|
||||
|
||||
await self._persist_and_signal_task_change(task_id, task)
|
||||
try:
|
||||
await self._persist_and_signal_task_change(task_id, task)
|
||||
except Exception:
|
||||
# The completion did not land on disk - release the double-tap
|
||||
# guard stamped above, or the user's retry within 30 s would be
|
||||
# swallowed as a duplicate of a completion that never happened
|
||||
# (bug audit 2026-08-29).
|
||||
if completed_at is None and not auto:
|
||||
self._recent_manual_completions.pop(task_id, None)
|
||||
elif completed_at is not None:
|
||||
self._recent_backfills.pop((task_id, completed_at.isoformat()), None)
|
||||
raise
|
||||
|
||||
# Shared-task rotation: advance_rotation() (inside task.complete)
|
||||
# mutates responsible_user_id, which is a STATIC config field — the
|
||||
@@ -1172,16 +1261,24 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
from .parts_runtime import async_handle_completion_parts
|
||||
|
||||
try:
|
||||
await async_handle_completion_parts(
|
||||
self.hass,
|
||||
self.entry,
|
||||
merged[task_id],
|
||||
restock_quantity=restock_quantity,
|
||||
used_parts=used_parts,
|
||||
)
|
||||
if is_latest or used_parts is not None:
|
||||
await async_handle_completion_parts(
|
||||
self.hass,
|
||||
self.entry,
|
||||
merged[task_id],
|
||||
restock_quantity=restock_quantity,
|
||||
used_parts=used_parts,
|
||||
)
|
||||
except Exception:
|
||||
_LOGGER.exception("Part consumption failed for task %s", task_id)
|
||||
|
||||
# A status configured to notify ONCE (interval 0) stays silenced by the
|
||||
# _SENT_ONCE sentinel until something clears it; nothing did after a
|
||||
# completion, so a sensor task that re-triggered weeks later was never
|
||||
# announced again until HA restarted (bug audit 2026-08-29).
|
||||
if is_latest:
|
||||
self._clear_notification_state(task_id)
|
||||
|
||||
_LOGGER.debug("Maintenance completed: %s on %s", task.name, self.maintenance_object.name)
|
||||
|
||||
# Fire event after persistence — power users wire HA automations on
|
||||
@@ -1276,6 +1373,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# A reset starts a new cycle — a following completion is a NEW
|
||||
# real-world action, never a double-tap of the previous one.
|
||||
self._recent_manual_completions.pop(task_id, None)
|
||||
self._clear_notification_state(task_id)
|
||||
merged = self._get_merged_tasks_data()
|
||||
if task_id not in merged:
|
||||
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
|
||||
@@ -1310,6 +1408,18 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
await self._persist_and_signal_task_change(task_id, task)
|
||||
_LOGGER.debug("Occurrence postponed to %s: %s on %s", until, task.name, self.maintenance_object.name)
|
||||
|
||||
def _clear_notification_state(self, task_id: str) -> None:
|
||||
"""Forget the per-status notification bookkeeping for a task.
|
||||
|
||||
The notification manager lives on the shared runtime; it may be absent
|
||||
in tests or during teardown - then there is nothing to clear.
|
||||
"""
|
||||
from .const import NOTIFICATION_MANAGER_KEY
|
||||
|
||||
nm = self.hass.data.get(DOMAIN, {}).get(NOTIFICATION_MANAGER_KEY)
|
||||
if nm is not None:
|
||||
nm.clear_task_state(self.entry.entry_id, task_id)
|
||||
|
||||
async def skip_maintenance(
|
||||
self,
|
||||
task_id: str,
|
||||
@@ -1320,6 +1430,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
# Same as reset: skipping restarts the cycle — clear the double-tap
|
||||
# window so a follow-up completion counts.
|
||||
self._recent_manual_completions.pop(task_id, None)
|
||||
self._clear_notification_state(task_id)
|
||||
merged = self._get_merged_tasks_data()
|
||||
if task_id not in merged:
|
||||
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
|
||||
|
||||
@@ -58,7 +58,12 @@ class MaintenanceEntity(CoordinatorEntity[MaintenanceCoordinator]):
|
||||
coordinator.hass, device_id, own_entry_id=coordinator.entry.entry_id
|
||||
)
|
||||
if resolved is not None:
|
||||
self._linked_device = dr.async_get(coordinator.hass).async_get(resolved)
|
||||
# Any hop: HA 2026.9 lookups return DeviceEntry |
|
||||
# ChildDeviceEntry (both attach to entities the same way);
|
||||
# 2026.7 returns the plain entry — a cast would be redundant
|
||||
# on one version and required on the other.
|
||||
resolved_device: Any = dr.async_get(coordinator.hass).async_get(resolved)
|
||||
self._linked_device = resolved_device
|
||||
if self._linked_device is not None:
|
||||
self.device_entry = self._linked_device
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ class MaintenanceSummaryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
update_interval=None,
|
||||
)
|
||||
self._unsubs: list[CALLBACK_TYPE] = []
|
||||
self._attached: dict[str, CALLBACK_TYPE] = {}
|
||||
self._attached: dict[str, tuple[Any, CALLBACK_TYPE]] = {}
|
||||
|
||||
async def _async_update_data(self) -> dict[str, Any]:
|
||||
"""Recompute counts from the shared aggregator (single source)."""
|
||||
@@ -62,12 +62,22 @@ class MaintenanceSummaryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
self._unsubs.append(self.hass.bus.async_listen(EVENT_TRIGGER_DEACTIVATED, self._on_event))
|
||||
|
||||
def _attach_entry(self, entry_id: str) -> None:
|
||||
"""Register a listener on a single object coordinator (once)."""
|
||||
if entry_id in self._attached:
|
||||
return
|
||||
"""Register a listener on a single object coordinator.
|
||||
|
||||
A reload swaps the coordinator and re-announces the entry; the old
|
||||
"once" guard left the summary sensors listening to the dead one (bug
|
||||
audit 2026-08-29), so re-attach whenever the instance changed.
|
||||
"""
|
||||
rd = get_runtime_data(self.hass, entry_id)
|
||||
if rd and rd.coordinator:
|
||||
self._attached[entry_id] = rd.coordinator.async_add_listener(self._schedule)
|
||||
coordinator = rd.coordinator if rd else None
|
||||
if coordinator is None:
|
||||
return
|
||||
previous = self._attached.get(entry_id)
|
||||
if previous is not None:
|
||||
if previous[0] is coordinator:
|
||||
return
|
||||
previous[1]()
|
||||
self._attached[entry_id] = (coordinator, coordinator.async_add_listener(self._schedule))
|
||||
|
||||
@callback
|
||||
def _on_new_entry(self, entry_id: str) -> None:
|
||||
@@ -78,9 +88,9 @@ class MaintenanceSummaryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
@callback
|
||||
def _on_removed(self, entry_id: str) -> None:
|
||||
"""An object entry was deleted — detach its listener and recompute."""
|
||||
unsub = self._attached.pop(entry_id, None)
|
||||
if unsub is not None:
|
||||
unsub()
|
||||
attached = self._attached.pop(entry_id, None)
|
||||
if attached is not None:
|
||||
attached[1]()
|
||||
self._schedule()
|
||||
|
||||
@callback
|
||||
@@ -97,7 +107,7 @@ class MaintenanceSummaryCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
"""Detach all listeners (called on global entry unload)."""
|
||||
for unsub in self._unsubs:
|
||||
unsub()
|
||||
for unsub in self._attached.values():
|
||||
for _coordinator, unsub in self._attached.values():
|
||||
unsub()
|
||||
self._unsubs.clear()
|
||||
self._attached.clear()
|
||||
@@ -11,6 +11,11 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import (
|
||||
BATTERY_FLEET_EXCLUDED,
|
||||
BATTERY_FLEET_INCLUDED,
|
||||
BATTERY_FLEET_OBJECT_FLAG,
|
||||
BATTERY_FLEET_TASK_FLAG,
|
||||
BATTERY_FLEET_TRACK_SELF_CHARGING,
|
||||
CONF_OBJECT,
|
||||
CONF_TASKS,
|
||||
DEFAULT_WARNING_DAYS,
|
||||
@@ -111,6 +116,7 @@ def _build_export_object(
|
||||
"documentation_url": tdata.get("documentation_url"),
|
||||
"custom_icon": tdata.get("custom_icon"),
|
||||
"nfc_tag_id": tdata.get("nfc_tag_id"),
|
||||
"require_tag_scan": tdata.get("require_tag_scan"),
|
||||
"responsible_user_id": tdata.get("responsible_user_id"),
|
||||
"entity_slug": tdata.get("entity_slug"),
|
||||
"adaptive_config": tdata.get("adaptive_config"),
|
||||
@@ -155,6 +161,12 @@ def _build_export_object(
|
||||
if trigger_config:
|
||||
task["trigger_config"] = trigger_config
|
||||
|
||||
# Battery fleet identity: the single aggregate task is flagged so the
|
||||
# detail view renders the battery section — without the marker a
|
||||
# restored fleet task is a plain inspection task.
|
||||
if tdata.get(BATTERY_FLEET_TASK_FLAG):
|
||||
task[BATTERY_FLEET_TASK_FLAG] = True
|
||||
|
||||
if include_history:
|
||||
task["history"] = tdata.get("history") or []
|
||||
|
||||
@@ -167,33 +179,48 @@ def _build_export_object(
|
||||
object_id = obj_data.get("id", "")
|
||||
documents = _export_documents(doc_store, object_id) if doc_store is not None and object_id else []
|
||||
|
||||
export_obj: dict[str, Any] = {
|
||||
"name": obj_data.get("name", ""),
|
||||
"area_id": obj_data.get("area_id"),
|
||||
"manufacturer": obj_data.get("manufacturer"),
|
||||
"model": obj_data.get("model"),
|
||||
"serial_number": obj_data.get("serial_number"),
|
||||
"installation_date": obj_data.get("installation_date"),
|
||||
"warranty_expiry": obj_data.get("warranty_expiry"),
|
||||
# Round-tripped so a JSON backup restores the full asset record
|
||||
# (these were added in v1.4.0/v1.4.10 but missed here until #67).
|
||||
"documentation_url": obj_data.get("documentation_url"),
|
||||
"notes": obj_data.get("notes"),
|
||||
# 2.19: device link / parent hierarchy. Instance-specific ids —
|
||||
# meaningful when restoring on the SAME instance; dangling values
|
||||
# on a foreign instance are harmless (device_info falls back).
|
||||
"ha_device_id": obj_data.get("ha_device_id"),
|
||||
"parent_entry_id": obj_data.get("parent_entry_id"),
|
||||
# 2.20: seasonal pause (a paused pool restored in winter stays
|
||||
# paused) + replace-flow lineage (instance-specific entry ids,
|
||||
# same caveat as parent_entry_id above).
|
||||
"paused_at": obj_data.get("paused_at"),
|
||||
"paused_until": obj_data.get("paused_until"),
|
||||
"predecessor_entry_id": obj_data.get("predecessor_entry_id"),
|
||||
"replaced_by_entry_id": obj_data.get("replaced_by_entry_id"),
|
||||
# Object-level archive marker (the tasks carry their own pair
|
||||
# above). Without it an archived object came back ACTIVE after a
|
||||
# restore — with every task still archived (reason "object") and
|
||||
# object/unarchive refusing because the object "isn't archived".
|
||||
"archived_at": obj_data.get("archived_at"),
|
||||
}
|
||||
# Battery fleet identity — only emitted for the fleet object so a plain
|
||||
# object's export stays byte-identical to earlier versions. The importer
|
||||
# mirrors these (and keeps the deterministic ``batt_<type>`` part ids).
|
||||
if obj_data.get(BATTERY_FLEET_OBJECT_FLAG):
|
||||
export_obj[BATTERY_FLEET_OBJECT_FLAG] = True
|
||||
export_obj[BATTERY_FLEET_EXCLUDED] = list(obj_data.get(BATTERY_FLEET_EXCLUDED) or [])
|
||||
export_obj[BATTERY_FLEET_INCLUDED] = list(obj_data.get(BATTERY_FLEET_INCLUDED) or [])
|
||||
export_obj[BATTERY_FLEET_TRACK_SELF_CHARGING] = bool(obj_data.get(BATTERY_FLEET_TRACK_SELF_CHARGING))
|
||||
|
||||
return {
|
||||
"entry_id": entry.entry_id,
|
||||
"object": {
|
||||
"name": obj_data.get("name", ""),
|
||||
"area_id": obj_data.get("area_id"),
|
||||
"manufacturer": obj_data.get("manufacturer"),
|
||||
"model": obj_data.get("model"),
|
||||
"serial_number": obj_data.get("serial_number"),
|
||||
"installation_date": obj_data.get("installation_date"),
|
||||
"warranty_expiry": obj_data.get("warranty_expiry"),
|
||||
# Round-tripped so a JSON backup restores the full asset record
|
||||
# (these were added in v1.4.0/v1.4.10 but missed here until #67).
|
||||
"documentation_url": obj_data.get("documentation_url"),
|
||||
"notes": obj_data.get("notes"),
|
||||
# 2.19: device link / parent hierarchy. Instance-specific ids —
|
||||
# meaningful when restoring on the SAME instance; dangling values
|
||||
# on a foreign instance are harmless (device_info falls back).
|
||||
"ha_device_id": obj_data.get("ha_device_id"),
|
||||
"parent_entry_id": obj_data.get("parent_entry_id"),
|
||||
# 2.20: seasonal pause (a paused pool restored in winter stays
|
||||
# paused) + replace-flow lineage (instance-specific entry ids,
|
||||
# same caveat as parent_entry_id above).
|
||||
"paused_at": obj_data.get("paused_at"),
|
||||
"paused_until": obj_data.get("paused_until"),
|
||||
"predecessor_entry_id": obj_data.get("predecessor_entry_id"),
|
||||
"replaced_by_entry_id": obj_data.get("replaced_by_entry_id"),
|
||||
},
|
||||
"object": export_obj,
|
||||
"tasks": tasks,
|
||||
"documents": documents,
|
||||
# Spare parts: full static definition + the tracked stock (dynamic,
|
||||
@@ -293,7 +320,7 @@ def export_maintenance_data(
|
||||
# Global settings the export deliberately leaves behind: HA user ids are
|
||||
# instance-bound (and the panel-access allowlist is security-relevant), and
|
||||
# the adopted-task stash is transient re-adopt state.
|
||||
_NON_PORTABLE_SETTINGS = ("admin_panel_user_ids", "adopted_task_notes")
|
||||
_NON_PORTABLE_SETTINGS = ("admin_panel_user_ids", "adopted_task_notes", "shopping_list_entity")
|
||||
|
||||
|
||||
def build_settings_export(hass: HomeAssistant) -> dict[str, Any]:
|
||||
|
||||
+24
@@ -273,3 +273,27 @@ describe("complete-dialog cost suggestion from parts (#104 follow-up)", () => {
|
||||
expect(chip(el)).to.equal(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("complete-dialog require_tag_scan note", () => {
|
||||
it("shows the scan hint when the task demands proof of presence", async () => {
|
||||
const { hass } = createMockHass({});
|
||||
const el = await fixture<MaintenanceCompleteDialog>(html`
|
||||
<maintenance-complete-dialog
|
||||
.hass=${hass}
|
||||
.entryId=${"entry1"}
|
||||
.taskId=${"task1"}
|
||||
.taskName=${"Filter Wechsel"}
|
||||
.lang=${"en"}
|
||||
.requireTagScan=${true}
|
||||
></maintenance-complete-dialog>
|
||||
`);
|
||||
el.open();
|
||||
await el.updateComplete;
|
||||
expect(el.shadowRoot!.querySelector(".scan-required-note")).to.exist;
|
||||
});
|
||||
|
||||
it("stays silent for ordinary tasks", async () => {
|
||||
const { el } = await mount();
|
||||
expect(el.shadowRoot!.querySelector(".scan-required-note")).to.equal(null);
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,7 @@
|
||||
import { expect } from "@open-wc/testing";
|
||||
import { formatDate, formatDateTime, setDateTimePrefs } from "../styles";
|
||||
|
||||
const reset = () => setDateTimePrefs({ date_format: undefined, time_format: undefined });
|
||||
const reset = () => setDateTimePrefs({ date_format: undefined, time_format: undefined }, null);
|
||||
|
||||
describe("formatDate with HA profile date_format (#97)", () => {
|
||||
afterEach(reset);
|
||||
@@ -66,3 +66,39 @@ describe("formatDateTime with HA profile time_format (#97)", () => {
|
||||
expect(out.toLowerCase()).to.match(/pm|nachm/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("server country regionalizes the language default (#140)", () => {
|
||||
afterEach(reset);
|
||||
|
||||
it('"en" + AU renders dd/mm/yyyy without any profile setting', () => {
|
||||
setDateTimePrefs({ date_format: "language" }, "AU");
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("10/08/2026");
|
||||
});
|
||||
|
||||
it('"en" + US keeps mm/dd/yyyy; "de" + AT stays dd.mm.yyyy', () => {
|
||||
setDateTimePrefs({ date_format: "language" }, "US");
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("08/10/2026");
|
||||
setDateTimePrefs({ date_format: "language" }, "AT");
|
||||
expect(formatDate("2026-08-10", "de")).to.equal("10.08.2026");
|
||||
});
|
||||
|
||||
it("an explicit profile format always beats the country", () => {
|
||||
setDateTimePrefs({ date_format: "MDY" }, "AU");
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("08/10/2026");
|
||||
setDateTimePrefs({ date_format: "YMD" }, "AU");
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("2026-08-10");
|
||||
});
|
||||
|
||||
it("garbage/unset country falls back to the language mapping", () => {
|
||||
setDateTimePrefs({ date_format: "language" }, "!!");
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("08/10/2026");
|
||||
setDateTimePrefs({ date_format: "language" }, null);
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("08/10/2026");
|
||||
});
|
||||
|
||||
it("country survives a later prefs update that omits it (undefined ≠ clear)", () => {
|
||||
setDateTimePrefs({ date_format: "language" }, "AU");
|
||||
setDateTimePrefs({ date_format: "language" });
|
||||
expect(formatDate("2026-08-10", "en")).to.equal("10/08/2026");
|
||||
});
|
||||
});
|
||||
@@ -114,6 +114,83 @@ describe("panel deep links (QR scan routing)", () => {
|
||||
expect(dlg.taskName).to.equal("No Defaults");
|
||||
});
|
||||
|
||||
it("quick_complete refused for missing details falls back to the dialog WITH the scan flag", async () => {
|
||||
setDeepLink("entry_id=e1&task_id=t1&action=quick_complete");
|
||||
const { el, sent } = await mountPanel(
|
||||
[obj("e1", [task({ name: "Needs Notes", require_tag_scan: true })])],
|
||||
{
|
||||
"maintenance_supporter/task/quick_complete": () => {
|
||||
throw { code: "completion_details_required", message: "notes required" };
|
||||
},
|
||||
},
|
||||
);
|
||||
await settleRaf(el);
|
||||
await settleRaf(el);
|
||||
|
||||
await waitForOpenCompleteDialog(el);
|
||||
const dlg = completeDialog(el)!;
|
||||
expect(dlg.shadowRoot!.querySelector("ha-dialog"), "fallback dialog open").to.exist;
|
||||
expect(dlg.taskName).to.equal("Needs Notes");
|
||||
// The scan already happened: the gate is met, the note is not shown …
|
||||
expect(dlg.viaTagScan).to.equal(true);
|
||||
expect(dlg.requireTagScan).to.equal(true);
|
||||
expect(dlg.shadowRoot!.querySelector(".scan-required-note")).to.be.null;
|
||||
// … and the completion carries via_tag_scan for the backend's gate.
|
||||
const buttons = [...dlg.shadowRoot!.querySelectorAll(".dialog-actions ha-button")];
|
||||
(buttons[buttons.length - 1] as HTMLElement).click();
|
||||
await new Promise((r) => setTimeout(r, 20));
|
||||
const complete = sent.find((m) => m.type === "maintenance_supporter/task/complete");
|
||||
expect(complete, "task/complete sent").to.exist;
|
||||
expect(complete!.via_tag_scan).to.equal(true);
|
||||
});
|
||||
|
||||
it("a plain complete (no scan) opens the dialog WITHOUT the scan flag", async () => {
|
||||
setDeepLink("entry_id=e1&task_id=t1&action=complete");
|
||||
const { el } = await mountPanel([obj("e1", [task({ name: "Gated", require_tag_scan: true })])]);
|
||||
await settleRaf(el);
|
||||
await waitForOpenCompleteDialog(el);
|
||||
const dlg = completeDialog(el)!;
|
||||
expect(dlg.viaTagScan).to.equal(false);
|
||||
expect(dlg.shadowRoot!.querySelector(".scan-required-note"), "note shown").to.exist;
|
||||
});
|
||||
|
||||
it("any other quick_complete refusal relays the server's message", async () => {
|
||||
setDeepLink("entry_id=e1&task_id=t1&action=quick_complete");
|
||||
const { el } = await mountPanel(
|
||||
[obj("e1", [task({ name: "Early" })])],
|
||||
{
|
||||
"maintenance_supporter/task/quick_complete": () => {
|
||||
throw { code: "too_early", message: "Earliest completion is 2026-09-01" };
|
||||
},
|
||||
},
|
||||
);
|
||||
await settleRaf(el);
|
||||
await settleRaf(el);
|
||||
expect((el as unknown as { _toastMessage: string })._toastMessage).to.equal(
|
||||
"Earliest completion is 2026-09-01",
|
||||
);
|
||||
const dlg = completeDialog(el);
|
||||
expect(dlg?.shadowRoot?.querySelector("ha-dialog") ?? null, "no dialog").to.be.null;
|
||||
});
|
||||
|
||||
it("a silent quick_complete success reloads the data", async () => {
|
||||
setDeepLink("entry_id=e1&task_id=t1&action=quick_complete");
|
||||
const { el, sent } = await mountPanel(
|
||||
[obj("e1", [task({ name: "Quick" })])],
|
||||
{ "maintenance_supporter/task/quick_complete": () => ({ success: true, via: "quick" }) },
|
||||
);
|
||||
await settleRaf(el);
|
||||
await settleRaf(el);
|
||||
// The deep link is only routed once the objects are known, so every
|
||||
// objects fetch AFTER the quick_complete is the reload it triggered.
|
||||
const atQuick = sent.findIndex((m) => m.type === "maintenance_supporter/task/quick_complete");
|
||||
expect(atQuick, "quick_complete fired").to.be.greaterThan(-1);
|
||||
const reloaded = sent
|
||||
.slice(atQuick + 1)
|
||||
.some((m) => m.type === "maintenance_supporter/objects");
|
||||
expect(reloaded, "objects refetched after the silent complete").to.equal(true);
|
||||
});
|
||||
|
||||
it("an unknown entry_id lands safely on the overview", async () => {
|
||||
setDeepLink("entry_id=does-not-exist&task_id=t1&action=complete");
|
||||
const { el } = await mountPanel([obj("e1", [task({ name: "Real" })])]);
|
||||
|
||||
+13
@@ -215,6 +215,19 @@ describe("task-detail renderer", () => {
|
||||
expect(host2.querySelector(".kpi-bar")).to.be.null;
|
||||
});
|
||||
|
||||
it("history tab offers 'add past completion' routing to openComplete (#142)", () => {
|
||||
let completed: MaintenanceTask | null = null;
|
||||
const t2 = task();
|
||||
const host = mount(t2, ctx({
|
||||
activeTab: "history",
|
||||
openComplete: (tk) => { completed = tk; },
|
||||
}));
|
||||
const btn = host.querySelector(".history-add-past-btn") as HTMLElement;
|
||||
expect(btn, "add-past button").to.exist;
|
||||
btn.click();
|
||||
expect(completed).to.equal(t2);
|
||||
});
|
||||
|
||||
it("object-manual row falls back to an attached manual when the URL is empty", () => {
|
||||
let opened: unknown = null;
|
||||
const host = mount(task(), ctx({
|
||||
|
||||
@@ -43,6 +43,15 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
/** #139: "2/4 · Flip blades" — which phase of the cycle this completion
|
||||
* records. Purely informative; the backend advances the cursor itself. */
|
||||
@property() public phaseLabel = "";
|
||||
/** Proof of presence: the task completes only via NFC/QR scan — the
|
||||
* dialog says so up front instead of relaying the server's refusal. */
|
||||
@property({ type: Boolean }) public requireTagScan = false;
|
||||
/** This open is the fallback of a QR/NFC scan (quick-complete refused for
|
||||
* missing defaults/details): the completion carries `via_tag_scan` so the
|
||||
* tag-scan gate counts the scan that got the user here, and the "scan
|
||||
* required" note stays hidden — they just did. Set by open(); never
|
||||
* survives into the next open. */
|
||||
@property({ type: Boolean }) public viaTagScan = false;
|
||||
@state() private _open = false;
|
||||
@state() private _notes = "";
|
||||
@state() private _cost = "";
|
||||
@@ -66,9 +75,12 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
* prefill the dialog so nobody re-ticks what is already done. */
|
||||
@property({ attribute: false }) public checklistPrefill: Record<string, boolean> = {};
|
||||
|
||||
public open(): void {
|
||||
public open(opts: { viaTagScan?: boolean } = {}): void {
|
||||
if (this._open) return;
|
||||
this._open = true;
|
||||
// Reset on every open — a plain open after a scan fallback must not
|
||||
// keep claiming proof of presence.
|
||||
this.viaTagScan = !!opts.viaTagScan;
|
||||
this._notes = "";
|
||||
this._cost = "";
|
||||
this._duration = "";
|
||||
@@ -173,6 +185,12 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
if (this._photoDocId) {
|
||||
data.photo_doc_id = this._photoDocId;
|
||||
}
|
||||
// Scan fallback: the backend accepts via_tag_scan on task/complete so a
|
||||
// require_tag_scan task can still be finished from the dialog the scan
|
||||
// opened. Only ever sent when a scan actually happened.
|
||||
if (this.viaTagScan) {
|
||||
data.via_tag_scan = true;
|
||||
}
|
||||
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.
|
||||
@@ -290,6 +308,7 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
<ha-dialog open @closed=${this._close}>
|
||||
<div class="dialog-title">${t("complete_title", L)}${this.taskName}</div>
|
||||
${this.phaseLabel ? html`<div class="phase-line">${t("phase_current", L)}: ${this.phaseLabel}</div>` : nothing}
|
||||
${this.requireTagScan && !this.viaTagScan ? html`<div class="scan-required-note">${t("require_tag_scan_hint", L)}</div>` : nothing}
|
||||
<div class="content">
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
${this.checklist.length > 0 ? html`
|
||||
@@ -477,6 +496,14 @@ export class MaintenanceCompleteDialog extends LitElement {
|
||||
font-weight: 500;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.scan-required-note {
|
||||
margin: -4px 0 12px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 152, 0, 0.12);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
.phase-line {
|
||||
margin-top: -8px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
@@ -39,6 +39,10 @@ export class MsTextfield extends LitElement {
|
||||
@property() public max?: string;
|
||||
@property() public pattern?: string;
|
||||
@property() public helper?: string;
|
||||
/** Render a <textarea> instead of an <input> (free-text notes). The
|
||||
* `input` event contract is identical — target.value works for both. */
|
||||
@property({ type: Boolean }) public multiline = false;
|
||||
@property({ type: Number }) public rows = 3;
|
||||
|
||||
/** Forwards the native input's value into our `value` property and
|
||||
* re-fires as a bubbling `input` event so consumers reading
|
||||
@@ -56,6 +60,16 @@ export class MsTextfield extends LitElement {
|
||||
return html`
|
||||
<label class="field">
|
||||
${this.label ? html`<span class="label">${this.label}${this.required ? html`<span class="req">*</span>` : nothing}</span>` : nothing}
|
||||
${this.multiline ? html`
|
||||
<textarea
|
||||
.value=${this.value ?? ""}
|
||||
rows=${this.rows}
|
||||
?required=${this.required}
|
||||
?disabled=${this.disabled}
|
||||
placeholder=${this.placeholder}
|
||||
@input=${this._onInput}
|
||||
@change=${this._onInput}
|
||||
></textarea>` : html`
|
||||
<input
|
||||
.value=${this.value ?? ""}
|
||||
.type=${this.type}
|
||||
@@ -68,7 +82,7 @@ export class MsTextfield extends LitElement {
|
||||
pattern=${this.pattern ?? nothing}
|
||||
@input=${this._onInput}
|
||||
@change=${this._onInput}
|
||||
/>
|
||||
/>`}
|
||||
${this.helper ? html`<span class="helper">${this.helper}</span>` : nothing}
|
||||
</label>
|
||||
`;
|
||||
@@ -103,6 +117,21 @@ export class MsTextfield extends LitElement {
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
input:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
textarea {
|
||||
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;
|
||||
resize: vertical;
|
||||
}
|
||||
textarea:focus { border-color: var(--primary-color); }
|
||||
textarea:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.helper {
|
||||
font-size: 11px;
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
@@ -217,6 +217,7 @@ export class MaintenanceObjectDialog extends LitElement {
|
||||
.value=${this._notes}
|
||||
@input=${(e: Event) => (this._notes = (e.target as HTMLTextAreaElement).value)}
|
||||
></textarea>
|
||||
<span class="md-hint">${t("notes_markdown_hint", L)}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
@@ -271,6 +272,9 @@ export class MaintenanceObjectDialog extends LitElement {
|
||||
.textarea-field textarea:focus {
|
||||
outline: none; border-color: var(--primary-color);
|
||||
}
|
||||
.md-hint {
|
||||
font-size: 11px; color: var(--secondary-text-color); font-style: italic;
|
||||
}
|
||||
.parent-select {
|
||||
padding: 8px 10px; font-size: 14px; font-family: inherit;
|
||||
background: var(--secondary-background-color, rgba(0,0,0,0.06));
|
||||
|
||||
+3
-1
@@ -10,6 +10,7 @@
|
||||
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { renderNotesMarkdown } from "../helpers/notes-markdown";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { t, STATUS_COLORS, langOf } from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
@@ -186,7 +187,7 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
|
||||
? html`
|
||||
<div class="notes-section">
|
||||
<strong>${t("object_notes_label", L)}</strong>
|
||||
<div class="notes-body">${obj.notes}</div>
|
||||
<div class="notes-body">${renderNotesMarkdown(obj.notes)}</div>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
@@ -294,6 +295,7 @@ export class MaintenanceObjectQuickActionsDialog extends LitElement {
|
||||
.task-name { flex: 1; font-size: 14px; }
|
||||
.task-status { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; }
|
||||
.notes-body { white-space: pre-wrap; font-size: 13px; padding: 8px; background: var(--secondary-background-color); border-radius: 6px; }
|
||||
.notes-body ha-markdown { white-space: normal; }
|
||||
.actions { display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid var(--divider-color); }
|
||||
.actions .btn { flex: 1; }
|
||||
.btn {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { LitElement, html, css, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
import { unsafeHTML } from "lit/directives/unsafe-html.js";
|
||||
import { live } from "lit/directives/live.js";
|
||||
import type { HomeAssistant, AdvancedFeatures, BudgetStatus, HAUser } from "../types";
|
||||
import { t, langOf } from "../styles";
|
||||
import { signApiPath } from "../helpers/document-url";
|
||||
@@ -36,6 +37,8 @@ interface SettingsResponse {
|
||||
// so the picker matches the options-flow dropdown exactly. Optional for
|
||||
// backward-compat with an older backend that didn't send it.
|
||||
notify_targets?: string[];
|
||||
/** v2.67: todo.* entity the buy-task shopping sync mirrors into ("" = off). */
|
||||
shopping_list_entity?: string;
|
||||
panel_enabled: boolean;
|
||||
panel_title: string;
|
||||
/** Opt-in copy of the shipped Assist sentences into the config dir. */
|
||||
@@ -249,6 +252,11 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
this.dispatchEvent(new CustomEvent("settings-changed"));
|
||||
} catch {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
// The control already shows the rejected value (the browser changed it
|
||||
// before we asked the server). `_settings` is unchanged, so a plain
|
||||
// re-render would not touch the DOM — the selects bind their value
|
||||
// through `live()` precisely so this re-render snaps them back.
|
||||
this.requestUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,15 +587,16 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<h3>${t("settings_general", L)}</h3>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_default_warning", L)}</span>
|
||||
<input type="number" min="1" max="365" .value=${String(g.default_warning_days)}
|
||||
<input type="number" min="0" max="365" .value=${String(g.default_warning_days)}
|
||||
@change=${(e: Event) => {
|
||||
const v = parseInt((e.target as HTMLInputElement).value, 10);
|
||||
if (v >= 1 && v <= 365) this._updateSetting("default_warning_days", v);
|
||||
// 0 = no warning window (due soon only on the due date) — #145.
|
||||
if (v >= 0 && v <= 365) this._updateSetting("default_warning_days", v);
|
||||
}} />
|
||||
</label>
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_currency", L)}</span>
|
||||
<select @change=${(e: Event) => this._updateSetting("budget_currency", (e.target as HTMLSelectElement).value)}>
|
||||
<select .value=${live(b.currency)} @change=${(e: Event) => this._updateSetting("budget_currency", (e.target as HTMLSelectElement).value)}>
|
||||
${CURRENCIES.map((c) => html`<option value=${c} ?selected=${b.currency === c}>${c}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
@@ -652,10 +661,29 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
</div>
|
||||
` : nothing}
|
||||
` : nothing}
|
||||
|
||||
<label class="setting-row">
|
||||
<span class="setting-label" title=${t("settings_shopping_list_help", L)}>${t("settings_shopping_list", L)}</span>
|
||||
<select .value=${live(g.shopping_list_entity || "")}
|
||||
@change=${(e: Event) => this._updateSetting("shopping_list_entity", (e.target as HTMLSelectElement).value)}>
|
||||
<option value="" ?selected=${!g.shopping_list_entity}>${t("shopping_list_none", L)}</option>
|
||||
${this._todoEntities(g.shopping_list_entity || "").map((id) => html`
|
||||
<option value=${id} ?selected=${g.shopping_list_entity === id}>${id}</option>
|
||||
`)}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/** All todo.* entities, plus the saved value even when its integration
|
||||
* hasn't loaded (so an offline pick isn't silently dropped from the UI). */
|
||||
private _todoEntities(current: string): string[] {
|
||||
const ids = Object.keys(this.hass?.states || {}).filter((id) => id.startsWith("todo.")).sort();
|
||||
if (current && !ids.includes(current)) ids.unshift(current);
|
||||
return ids;
|
||||
}
|
||||
|
||||
// --- Section: Notifications ---
|
||||
|
||||
private _renderNotifications(L: string) {
|
||||
@@ -762,7 +790,7 @@ export class MaintenanceSettingsView extends LitElement {
|
||||
<label class="setting-row">
|
||||
<span class="setting-label">${t("settings_notify_scope", L)}</span>
|
||||
<select
|
||||
.value=${n.scope_view_id || ""}
|
||||
.value=${live(n.scope_view_id || "")}
|
||||
@change=${(e: Event) => this._updateSetting("notify_scope_view_id", (e.target as HTMLSelectElement).value)}
|
||||
>
|
||||
<option value="" ?selected=${!n.scope_view_id}>${t("settings_notify_scope_all", L)}</option>
|
||||
|
||||
@@ -244,6 +244,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
// NFC
|
||||
@state() private _lastPerformed = "";
|
||||
@state() private _nfcTagId = "";
|
||||
@state() private _requireTagScan = false;
|
||||
// v2.20 (#83): unit for `reading`-type tasks ("kWh", "m³", ...)
|
||||
@state() private _readingUnit = "";
|
||||
/** The picked links, keyed by `partLinkKey` — the (entry_id, part_id) pair,
|
||||
@@ -263,12 +264,16 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
// Task phases (#139): editable defs + the cycle order. The v1 editor keeps
|
||||
// each phase to name + checklist + at most ONE own-object part (the mower
|
||||
// "14 new blades" case); richer per-phase config exists at the WS level.
|
||||
// `carry` holds def fields the editor has no form for (notes,
|
||||
// required_completion_fields, future keys) and `extraParts` any links
|
||||
// beyond the first — both re-merged on save so a no-op edit never wipes
|
||||
// them (#106 pattern; same passthrough idea as trigger_config's carry).
|
||||
// `carry` holds def fields the editor has no form for (notes, future
|
||||
// keys) and `extraParts` any links beyond the first — both re-merged on
|
||||
// save so a no-op edit never wipes them (#106 pattern; same passthrough
|
||||
// idea as trigger_config's carry). Required completion fields are a real
|
||||
// form now (#139 follow-up): `reqOverride` distinguishes "inherit the
|
||||
// task's list" (off) from an explicit per-phase list — including the
|
||||
// meaningful EMPTY override "this phase demands nothing".
|
||||
@state() private _phaseDefs: Array<{
|
||||
id: string; name: string; checklistText: string; partId: string; partQty: string;
|
||||
reqOverride: boolean; reqFields: string[];
|
||||
extraParts: TaskPartLink[]; carry: Record<string, unknown>;
|
||||
}> = [];
|
||||
@state() private _phaseSeq: string[] = [];
|
||||
@@ -348,6 +353,11 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._entryId = entryId;
|
||||
this._taskId = task.id;
|
||||
this._error = "";
|
||||
// The object picker belongs to CREATE only. openCreate("", objects) left
|
||||
// the choices behind, so Create → Cancel → Edit showed the dropdown in
|
||||
// edit mode — and picking another object re-pointed _entryId while
|
||||
// _taskId kept the old task (audit 2026-08-29).
|
||||
this._objectChoices = [];
|
||||
this._name = task.name;
|
||||
this._type = task.type;
|
||||
this._scheduleType = task.schedule_type;
|
||||
@@ -389,6 +399,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._enabled = task.enabled !== false;
|
||||
this._lastPerformed = task.last_performed || "";
|
||||
this._nfcTagId = task.nfc_tag_id || "";
|
||||
this._requireTagScan = !!task.require_tag_scan;
|
||||
this._readingUnit = task.reading_unit || "";
|
||||
// Whole link, entry_id included — hydrating only part_id would turn every
|
||||
// shared-pool link into an own-part link on the next save (#111).
|
||||
@@ -401,14 +412,28 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
|
||||
this._checklistText = (task.checklist || []).join("\n");
|
||||
this._phaseDefs = Object.entries(task.phases || {}).map(([id, def]) => {
|
||||
const { name: _n, checklist: _c, consumes_parts: _p, ...carry } = def as unknown as Record<string, unknown>;
|
||||
const {
|
||||
name: _n, checklist: _c, consumes_parts: _p, required_completion_fields: _r,
|
||||
...carry
|
||||
} = def as unknown as Record<string, unknown>;
|
||||
// The editor's single part field is the first OWN link (no entry_id —
|
||||
// the picker only lists the object's own parts). Every other link,
|
||||
// shared pools (#111) first among them, rides along whole in
|
||||
// `extraParts` and is re-emitted on save: hydrating index 0 blindly
|
||||
// turned a phase whose first link is foreign into an own-part link
|
||||
// (or lost it) on the next save (audit 2026-08-29).
|
||||
const links = def.consumes_parts || [];
|
||||
const ownIdx = links.findIndex((l) => !l.entry_id);
|
||||
const own = ownIdx >= 0 ? links[ownIdx] : undefined;
|
||||
return {
|
||||
id,
|
||||
name: def.name || id,
|
||||
checklistText: (def.checklist || []).join("\n"),
|
||||
partId: def.consumes_parts?.[0]?.part_id || "",
|
||||
partQty: def.consumes_parts?.[0]?.quantity != null ? String(def.consumes_parts[0].quantity) : "",
|
||||
extraParts: (def.consumes_parts || []).slice(1),
|
||||
partId: own?.part_id || "",
|
||||
partQty: own?.quantity != null ? String(own.quantity) : "",
|
||||
reqOverride: def.required_completion_fields !== undefined,
|
||||
reqFields: [...(def.required_completion_fields || [])],
|
||||
extraParts: links.filter((_, i) => i !== ownIdx).map((l) => ({ ...l })),
|
||||
carry,
|
||||
};
|
||||
});
|
||||
@@ -525,6 +550,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._enabled = true;
|
||||
this._lastPerformed = "";
|
||||
this._nfcTagId = "";
|
||||
this._requireTagScan = false;
|
||||
this._readingUnit = "";
|
||||
this._consumesParts = {};
|
||||
this._responsibleUserId = null;
|
||||
@@ -1032,7 +1058,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
|
||||
private _addPhaseDef(): void {
|
||||
const id = this._phaseSlug(`phase-${this._phaseDefs.length + 1}`);
|
||||
this._phaseDefs = [...this._phaseDefs, { id, name: "", checklistText: "", partId: "", partQty: "", extraParts: [], carry: {} }];
|
||||
this._phaseDefs = [...this._phaseDefs, { id, name: "", checklistText: "", partId: "", partQty: "", reqOverride: false, reqFields: [], extraParts: [], carry: {} }];
|
||||
}
|
||||
|
||||
private _removePhaseDef(id: string): void {
|
||||
@@ -1040,7 +1066,10 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._phaseSeq = this._phaseSeq.filter((s) => s !== id);
|
||||
}
|
||||
|
||||
private _patchPhaseDef(id: string, patch: Partial<{ name: string; checklistText: string; partId: string; partQty: string }>): void {
|
||||
private _patchPhaseDef(
|
||||
id: string,
|
||||
patch: Partial<{ name: string; checklistText: string; partId: string; partQty: string; reqOverride: boolean; reqFields: string[] }>,
|
||||
): void {
|
||||
this._phaseDefs = this._phaseDefs.map((d) => (d.id === id ? { ...d, ...patch } : d));
|
||||
}
|
||||
|
||||
@@ -1084,6 +1113,33 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@input=${(e: Event) => this._patchPhaseDef(d.id, { checklistText: (e.target as HTMLTextAreaElement).value })}
|
||||
></textarea>
|
||||
` : nothing}
|
||||
<label class="req-option phase-req-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${d.reqOverride}
|
||||
@change=${(e: Event) => this._patchPhaseDef(d.id, { reqOverride: (e.target as HTMLInputElement).checked })}
|
||||
/>
|
||||
<span>${t("phase_require_override", L)}</span>
|
||||
</label>
|
||||
${d.reqOverride ? html`
|
||||
<div class="required-completion phase-req-fields">
|
||||
${REQUIRED_COMPLETION_KEYS.map((field) => html`
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${d.reqFields.includes(field)}
|
||||
@change=${(e: Event) => {
|
||||
const on = (e.target as HTMLInputElement).checked;
|
||||
const next = new Set(d.reqFields);
|
||||
if (on) next.add(field); else next.delete(field);
|
||||
this._patchPhaseDef(d.id, { reqFields: [...next] });
|
||||
}}
|
||||
/>
|
||||
<span>${t(REQUIRED_COMPLETION_LABELS[field], L)}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
` : nothing}
|
||||
</div>
|
||||
`)}
|
||||
<ha-button appearance="plain" @click=${this._addPhaseDef}>
|
||||
@@ -1196,6 +1252,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
data.enabled = this._enabled;
|
||||
data.last_performed = this._lastPerformed || null;
|
||||
data.nfc_tag_id = this._nfcTagId || null;
|
||||
data.require_tag_scan = this._requireTagScan;
|
||||
data.reading_unit = this._readingUnit.trim() || null;
|
||||
// Task phases (#139): always sent — null clears a removed cycle.
|
||||
{
|
||||
@@ -1210,8 +1267,19 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
const qty = parseFloat(d.partQty);
|
||||
links.push({ part_id: d.partId, quantity: Number.isFinite(qty) && qty > 0 ? qty : 1 });
|
||||
}
|
||||
links.push(...d.extraParts);
|
||||
// entry_id travels only when present (a shared pool, #111) — an
|
||||
// own-part link stays byte-identical to what shipped before.
|
||||
for (const l of d.extraParts) {
|
||||
links.push(
|
||||
l.entry_id
|
||||
? { part_id: l.part_id, quantity: l.quantity, entry_id: l.entry_id }
|
||||
: { part_id: l.part_id, quantity: l.quantity },
|
||||
);
|
||||
}
|
||||
if (links.length) def.consumes_parts = links;
|
||||
// Override on → send the list, EMPTY included ("demands nothing");
|
||||
// off → omit the key so the task-level list falls through.
|
||||
if (d.reqOverride) def.required_completion_fields = [...d.reqFields];
|
||||
defs[d.id] = def;
|
||||
}
|
||||
const seq = this._phaseSeq.filter((id) => id in defs);
|
||||
@@ -2432,7 +2500,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<div class="dialog-title">${title}</div>
|
||||
<div class="content">
|
||||
${this._error ? html`<div class="error">${this._error}</div>` : nothing}
|
||||
${this._objectChoices.length > 0 ? html`
|
||||
${this._taskId === null && this._objectChoices.length > 0 ? html`
|
||||
<div class="select-row">
|
||||
<label>${t("object", L)}</label>
|
||||
<select
|
||||
@@ -2703,6 +2771,9 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
${this._renderAdaptiveSection(L)}
|
||||
<ms-textfield
|
||||
label="${t("notes_optional", L)}"
|
||||
multiline
|
||||
.rows=${3}
|
||||
.helper=${t("notes_markdown_hint", L)}
|
||||
.value=${this._notes}
|
||||
@input=${(e: Event) => (this._notes = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
@@ -2751,6 +2822,15 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
</div>
|
||||
`
|
||||
}
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this._requireTagScan}
|
||||
@change=${(e: Event) => (this._requireTagScan = (e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span>${t("require_tag_scan", L)}</span>
|
||||
</label>
|
||||
${this._requireTagScan ? html`<div class="field-help">${t("require_tag_scan_help", L)}</div>` : nothing}
|
||||
<label class="toggle-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -2923,6 +3003,15 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
min-height: 56px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.phase-req-toggle {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.phase-req-fields {
|
||||
margin: 2px 0 0 22px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.phase-remove {
|
||||
--mdc-icon-button-size: 36px;
|
||||
color: var(--secondary-text-color);
|
||||
|
||||
+34
-30
@@ -17,8 +17,8 @@ import { property, state } from "lit/decorators.js";
|
||||
import { sharedStyles, t, STATUS_COLORS, formatDate, formatDateTime, formatInterval, formatRecurrence, langOf } from "../styles";
|
||||
import { describeWsError } from "../ws-errors";
|
||||
import { isoDateLocal } from "../helpers/calendar-bucket";
|
||||
import { partsForCompletion } from "../helpers/shared-parts";
|
||||
import { effectivePhase, phaseLabel } from "../helpers/phases";
|
||||
import { buildCompleteDialogArgs } from "../helpers/complete-dialog-args";
|
||||
import { phaseLabel } from "../helpers/phases";
|
||||
import { renderWeibullSection } from "../renderers/weibull";
|
||||
import { renderPredictionSection } from "../renderers/prediction";
|
||||
import { renderRecommendationBars } from "../renderers/recommendation";
|
||||
@@ -63,6 +63,9 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
};
|
||||
@state() private _toast = "";
|
||||
private _featuresLoaded = false;
|
||||
/** Currency symbol for the complete dialog's cost suggestion — read off
|
||||
* the same settings response as the feature flags. */
|
||||
private _currencySymbol = "";
|
||||
|
||||
private get _lang(): string {
|
||||
return langOf(this.hass);
|
||||
@@ -93,10 +96,12 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
try {
|
||||
const r = await this.hass.connection.sendMessagePromise<{
|
||||
features?: Partial<AdvancedFeatures>;
|
||||
budget?: { currency_symbol?: string };
|
||||
}>({ type: "maintenance_supporter/settings" });
|
||||
if (r?.features) {
|
||||
this._features = { ...this._features, ...r.features };
|
||||
}
|
||||
this._currencySymbol = r?.budget?.currency_symbol || "";
|
||||
this._featuresLoaded = true;
|
||||
} catch {
|
||||
// Settings endpoint unavailable — leave defaults (all false), the
|
||||
@@ -158,35 +163,34 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
// (bug audit 2026-08-22).
|
||||
import("../dialog-mount").then(async ({ openCompleteDialog }) => {
|
||||
const task = this._task!;
|
||||
const isBuy = !!(task as { part_ref?: string }).part_ref;
|
||||
// #139: a phased task completes the phase currently due, so the phase's
|
||||
// checklist/parts/required fields replace the task-level ones here.
|
||||
const phase = effectivePhase(task);
|
||||
const links = phase ? phase.consumesParts : (task.consumes_parts || []);
|
||||
let parts: Parameters<typeof openCompleteDialog>[0]["parts"] = [];
|
||||
if (!isBuy) {
|
||||
try {
|
||||
const r = await this.hass.connection.sendMessagePromise<{
|
||||
objects: MaintenanceObjectResponse[];
|
||||
}>({ type: "maintenance_supporter/objects", compact: true });
|
||||
parts = partsForCompletion({ consumes_parts: links }, this._entryId!, r.objects || [], this._lang);
|
||||
} catch {
|
||||
// Parts stay empty — the dialog still completes without them.
|
||||
}
|
||||
// The object list resolves the parts picker (own inventory + shared
|
||||
// pools, #99/#111), the "consumes" hint lines and a buy task's restock
|
||||
// default — fetched for every task kind, since a buy task needs its
|
||||
// part's restock quantity + unit cost too.
|
||||
let objects: MaintenanceObjectResponse[] = [];
|
||||
try {
|
||||
const r = await this.hass.connection.sendMessagePromise<{
|
||||
objects: MaintenanceObjectResponse[];
|
||||
}>({ type: "maintenance_supporter/objects", compact: true });
|
||||
objects = r.objects || [];
|
||||
} catch {
|
||||
// Parts stay empty — the dialog still completes without them.
|
||||
}
|
||||
const ok = openCompleteDialog({
|
||||
entry_id: this._entryId!,
|
||||
task_id: this._taskId!,
|
||||
task_name: task.name,
|
||||
checklist: phase ? phase.checklist : (task.checklist || []),
|
||||
adaptive_enabled: !!task.adaptive_config?.enabled,
|
||||
required_completion_fields: phase ? phase.requiredFields : (task.required_completion_fields || []),
|
||||
task_type: task.type || "",
|
||||
reading_unit: (task as { reading_unit?: string }).reading_unit || "",
|
||||
parts,
|
||||
consumes_parts: isBuy ? [] : links,
|
||||
phase_label: phaseLabel(task),
|
||||
});
|
||||
// Same derivation as the panel and the card (phase override, tag-scan
|
||||
// gate, checklist ticks …) — see helpers/complete-dialog-args.
|
||||
const ok = openCompleteDialog(
|
||||
buildCompleteDialogArgs({
|
||||
entryId: this._entryId!,
|
||||
taskId: this._taskId!,
|
||||
taskName: task.name,
|
||||
task,
|
||||
objects,
|
||||
lang: this._lang,
|
||||
checklist: task.checklist || [],
|
||||
adaptiveEnabled: !!task.adaptive_config?.enabled,
|
||||
currencySymbol: this._currencySymbol,
|
||||
}),
|
||||
);
|
||||
if (ok) {
|
||||
this._notifyChanged("complete");
|
||||
this.close();
|
||||
|
||||
@@ -45,6 +45,7 @@ import type { MaintenanceTaskQuickActionsDialog } from "./components/task-quick-
|
||||
import type { MaintenanceObjectQuickActionsDialog } from "./components/object-quick-actions-dialog";
|
||||
import type { HomeAssistant, MaintenanceObject } from "./types";
|
||||
import { ensureLocale, isLocaleLoaded, langOf } from "./styles";
|
||||
import { fillAndOpenCompleteDialog, type CompleteDialogArgs } from "./helpers/complete-dialog-args";
|
||||
|
||||
const OBJECT_DIALOG_TAG = "maintenance-object-dialog";
|
||||
const TASK_DIALOG_TAG = "maintenance-task-dialog";
|
||||
@@ -274,41 +275,16 @@ export function openHistoryEditDialog(draft: HistoryEntryDraft): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** v2.3.0: open the rich complete-dialog from any Lovelace context. */
|
||||
export function openCompleteDialog(args: {
|
||||
entry_id: string;
|
||||
task_id: string;
|
||||
task_name: string;
|
||||
checklist?: string[];
|
||||
adaptive_enabled?: boolean;
|
||||
/** Details the task demands before it counts as done (v2.44). */
|
||||
required_completion_fields?: string[];
|
||||
/** Reading tasks need type+unit or the value field never renders. */
|
||||
task_type?: string;
|
||||
reading_unit?: string;
|
||||
/** #99/#111: per-completion parts selection incl. shared pools. */
|
||||
parts?: MaintenanceCompleteDialog["parts"];
|
||||
consumes_parts?: MaintenanceCompleteDialog["consumesParts"];
|
||||
/** #139: "2/4 · Flip blades" — the phase this completion records. */
|
||||
phase_label?: string;
|
||||
}): boolean {
|
||||
/** v2.3.0: open the rich complete-dialog from any Lovelace context.
|
||||
*
|
||||
* Takes the full argument bag (`buildCompleteDialogArgs` derives it from a
|
||||
* task + object list). Every dialog field is assigned — the dialog is a
|
||||
* singleton, so an omitted field must reset, never leak the previous
|
||||
* task's value (tag-scan gate, restock default, checklist ticks included). */
|
||||
export function openCompleteDialog(args: CompleteDialogArgs): boolean {
|
||||
const dlg = getOrCreate<MaintenanceCompleteDialog>(COMPLETE_DIALOG_TAG);
|
||||
if (!syncHass(dlg)) return false;
|
||||
dlg.entryId = args.entry_id;
|
||||
dlg.taskId = args.task_id;
|
||||
dlg.taskName = args.task_name;
|
||||
dlg.checklist = args.checklist ?? [];
|
||||
dlg.adaptiveEnabled = !!args.adaptive_enabled;
|
||||
dlg.requiredFields = args.required_completion_fields ?? [];
|
||||
// Always assign — the dialog is a singleton, so an omitted field must not
|
||||
// leak the previous task's value.
|
||||
dlg.taskType = args.task_type ?? "";
|
||||
dlg.readingUnit = args.reading_unit ?? "";
|
||||
dlg.parts = args.parts ?? [];
|
||||
dlg.consumesParts = args.consumes_parts ?? [];
|
||||
dlg.phaseLabel = args.phase_label ?? "";
|
||||
dlg.lang = (getHass()?.language) || "en";
|
||||
dlg.open();
|
||||
fillAndOpenCompleteDialog(dlg, args, (getHass()?.language) || "en");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Prázdné = půlnoc (výchozí). Časové pásmo HA.",
|
||||
"at_time": "v",
|
||||
"notes_optional": "Poznámky (volitelné)",
|
||||
"notes_markdown_hint": "Markdown je podporován — **tučně**, seznamy, [odkazy](…)",
|
||||
"cost_optional": "Náklady (volitelné)",
|
||||
"duration_minutes": "Doba trvání v minutách (volitelné)",
|
||||
"completed_at_optional": "Dokončeno dne (volitelné, prázdné = nyní)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Název panelu",
|
||||
"settings_notifications": "Oznámení",
|
||||
"settings_notify_service": "Služba oznámení",
|
||||
"settings_shopping_list": "Nákupní seznam (nákupní úkoly)",
|
||||
"settings_shopping_list_help": "Upozornění na nákup docházejících dílů se objeví v tomto seznamu; odškrtnutí doplní zásobu.",
|
||||
"shopping_list_none": "Vypnuto — žádný nákupní seznam",
|
||||
"settings_install_assist_sentences": "Nainstalovat věty pro Assist",
|
||||
"settings_install_assist_sentences_hint": "Zkopíruje hlasové věty do vaší konfigurace, aby je klasický agent Assist rozpoznal. Soubor, který jste upravili, nebude nikdy přepsán.",
|
||||
"test_notification": "Testovací oznámení",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Přidat krok",
|
||||
"phase_current": "Aktuální fáze",
|
||||
"phase_set": "Nastavit jako aktuální",
|
||||
"chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)"
|
||||
"chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)",
|
||||
"chart_history_alarm": "Pohled spouštěče z historie stavů: 1 = stav výstrahy držen po dobu přidržení, 0 = v pořádku (obvykle posledních ~10 dní)",
|
||||
"chart_history_count": "Počet změn sestaven z historie stavů od posledního servisu (obvykle posledních ~10 dní)",
|
||||
"prediction_cycles": "Naučeno z cyklů",
|
||||
"phase_require_override": "Přepsat „Vyžadovat při dokončení“ pro tuto fázi",
|
||||
"history_add_past": "Přidat dřívější dokončení",
|
||||
"require_tag_scan": "Dokončit pouze naskenováním štítku",
|
||||
"require_tag_scan_help": "Důkaz přítomnosti: „Hotovo“ je všude blokováno, dokud nenaskenujete NFC štítek nebo QR kód na zařízení. Automatizace mohou službě complete předat 'via_tag_scan'.",
|
||||
"require_tag_scan_hint": "Tento úkol lze dokončit pouze naskenováním jeho NFC štítku nebo QR kódu na zařízení — uložení zde bude odmítnuto."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Tom = midnat (standard). HA-tidszone.",
|
||||
"at_time": "kl.",
|
||||
"notes_optional": "Noter (valgfrit)",
|
||||
"notes_markdown_hint": "Markdown understøttes — **fed**, lister, [links](…)",
|
||||
"cost_optional": "Omkostning (valgfrit)",
|
||||
"duration_minutes": "Varighed i minutter (valgfrit)",
|
||||
"completed_at_optional": "Udført den (valgfrit, tomt = nu)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Sidepanelets titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjeneste",
|
||||
"settings_shopping_list": "Indkøbsliste (købsopgaver)",
|
||||
"settings_shopping_list_help": "Købspåmindelser for dele med lav beholdning vises på denne liste; afkrydsning genopfylder delen.",
|
||||
"shopping_list_none": "Fra — ingen indkøbsliste",
|
||||
"settings_install_assist_sentences": "Installer Assist-sætninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer stemmesætningerne til din konfiguration, så den klassiske Assist-agent genkender dem. En fil, du selv har redigeret, overskrives aldrig.",
|
||||
"test_notification": "Testnotifikation",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Tilføj trin",
|
||||
"phase_current": "Aktuel fase",
|
||||
"phase_set": "Sæt som aktuel",
|
||||
"chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)"
|
||||
"chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)",
|
||||
"chart_history_alarm": "Udløservisning fra tilstandshistorikken: 1 = alarmtilstand fastholdt i holdetiden, 0 = ok (typisk de seneste ~10 dage)",
|
||||
"chart_history_count": "Antal skift genopbygget fra tilstandshistorikken siden sidste service (typisk de seneste ~10 dage)",
|
||||
"prediction_cycles": "Lært af cyklusser",
|
||||
"phase_require_override": "Tilsidesæt „Kræv ved udførelse“ for denne fase",
|
||||
"history_add_past": "Tilføj tidligere udførelse",
|
||||
"require_tag_scan": "Kan kun udføres ved tag-scanning",
|
||||
"require_tag_scan_help": "Tilstedeværelsesbevis: „Udført“ er blokeret overalt, indtil NFC-tagget eller QR-koden på selve tingen er scannet. Automatiseringer kan give 'via_tag_scan' til complete-tjenesten.",
|
||||
"require_tag_scan_hint": "Denne opgave udføres kun ved at scanne dens NFC-tag eller QR-kode på selve tingen — at gemme her vil blive afvist."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Leer = Mitternacht (Default). HA-Zeitzone.",
|
||||
"at_time": "um",
|
||||
"notes_optional": "Notizen (optional)",
|
||||
"notes_markdown_hint": "Markdown wird unterstützt — **fett**, Listen, [Links](…)",
|
||||
"cost_optional": "Kosten (optional)",
|
||||
"duration_minutes": "Dauer in Minuten (optional)",
|
||||
"completed_at_optional": "Erledigt am (optional, leer = jetzt)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Panel-Titel",
|
||||
"settings_notifications": "Benachrichtigungen",
|
||||
"settings_notify_service": "Benachrichtigungsdienst",
|
||||
"settings_shopping_list": "Einkaufsliste (Kauf-Aufgaben)",
|
||||
"settings_shopping_list_help": "Kauf-Erinnerungen für knappe Teile erscheinen in dieser To-do-Liste; Abhaken füllt den Bestand auf.",
|
||||
"shopping_list_none": "Aus — keine Einkaufsliste",
|
||||
"settings_install_assist_sentences": "Assist-Sätze installieren",
|
||||
"settings_install_assist_sentences_hint": "Kopiert die Sprachbefehle in deine Konfiguration, damit der klassische Assist-Agent sie erkennt. Eine selbst bearbeitete Datei wird nie überschrieben.",
|
||||
"test_notification": "Test-Benachrichtigung",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Schritt anhängen",
|
||||
"phase_current": "Aktuelle Phase",
|
||||
"phase_set": "Als aktuell setzen",
|
||||
"chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)"
|
||||
"chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)",
|
||||
"chart_history_alarm": "Trigger-Sicht aus dem Recorder-Zustandsverlauf: 1 = Alarmzustand über die Haltezeit gehalten, 0 = in Ordnung (typisch die letzten ~10 Tage)",
|
||||
"chart_history_count": "Wechselzähler aus dem Recorder-Zustandsverlauf seit dem letzten Service (typisch die letzten ~10 Tage)",
|
||||
"prediction_cycles": "Gelernt aus Zyklen",
|
||||
"phase_require_override": "„Bei Erledigung verlangen“ für diese Phase überschreiben",
|
||||
"history_add_past": "Vergangene Erledigung nachtragen",
|
||||
"require_tag_scan": "Nur per Tag-Scan abschließbar",
|
||||
"require_tag_scan_help": "Anwesenheitsnachweis: „Erledigt“ ist überall blockiert, bis das NFC-Tag oder der QR-Code am Gerät gescannt wurde. Automationen können 'via_tag_scan' an den complete-Dienst übergeben.",
|
||||
"require_tag_scan_hint": "Diese Aufgabe kann nur per Scan des NFC-Tags oder QR-Codes am Gerät erledigt werden — Speichern hier wird abgelehnt."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Empty = midnight (default). HA timezone.",
|
||||
"at_time": "at",
|
||||
"notes_optional": "Notes (optional)",
|
||||
"notes_markdown_hint": "Markdown is supported — **bold**, lists, [links](…)",
|
||||
"cost_optional": "Cost (optional)",
|
||||
"duration_minutes": "Duration in minutes (optional)",
|
||||
"completed_at_optional": "Completed at (optional, empty = now)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Sidebar panel title",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Notification service",
|
||||
"settings_shopping_list": "Shopping list (buy tasks)",
|
||||
"settings_shopping_list_help": "Low-part buy reminders appear in this to-do list; checking one off restocks the part.",
|
||||
"shopping_list_none": "Off — no shopping list",
|
||||
"settings_install_assist_sentences": "Install Assist sentences",
|
||||
"settings_install_assist_sentences_hint": "Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",
|
||||
"test_notification": "Test notification",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Add step",
|
||||
"phase_current": "Current phase",
|
||||
"phase_set": "Set as current",
|
||||
"chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)"
|
||||
"chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)",
|
||||
"chart_history_alarm": "Trigger view from recorder state history: 1 = alert state held for the hold time, 0 = fine (typically the last ~10 days)",
|
||||
"chart_history_count": "Change count rebuilt from recorder state history since the last service (typically the last ~10 days)",
|
||||
"prediction_cycles": "Learned from cycles",
|
||||
"phase_require_override": "Override “Require on completion” for this phase",
|
||||
"history_add_past": "Add past completion",
|
||||
"require_tag_scan": "Only complete by scanning the tag",
|
||||
"require_tag_scan_help": "Proof of presence: Done is blocked on every surface until the NFC tag or QR code on the thing itself is scanned. Automations can pass 'via_tag_scan' to the complete service.",
|
||||
"require_tag_scan_hint": "This task completes only by scanning its NFC tag or QR code on the thing itself — saving here will be refused."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vacío = medianoche (predeterminado). Zona horaria HA.",
|
||||
"at_time": "a las",
|
||||
"notes_optional": "Notas (opcional)",
|
||||
"notes_markdown_hint": "Se admite Markdown — **negrita**, listas, [enlaces](…)",
|
||||
"cost_optional": "Coste (opcional)",
|
||||
"duration_minutes": "Duración en minutos (opcional)",
|
||||
"completed_at_optional": "Completado el (opcional, vacío = ahora)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Título del panel lateral",
|
||||
"settings_notifications": "Notificaciones",
|
||||
"settings_notify_service": "Servicio de notificación",
|
||||
"settings_shopping_list": "Lista de la compra (tareas de compra)",
|
||||
"settings_shopping_list_help": "Los recordatorios de compra de piezas escasas aparecen en esta lista; marcar uno repone la pieza.",
|
||||
"shopping_list_none": "Desactivado — sin lista de la compra",
|
||||
"settings_install_assist_sentences": "Instalar frases de Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia las frases de voz en tu configuración para que el agente clásico de Assist las reconozca. Un archivo que hayas editado nunca se sobrescribe.",
|
||||
"test_notification": "Notificación de prueba",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Añadir paso",
|
||||
"phase_current": "Fase actual",
|
||||
"phase_set": "Establecer como actual",
|
||||
"chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)"
|
||||
"chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)",
|
||||
"chart_history_alarm": "Vista del disparador desde el historial de estados: 1 = estado de alerta mantenido durante el tiempo de retención, 0 = correcto (normalmente los últimos ~10 días)",
|
||||
"chart_history_count": "Recuento de cambios reconstruido desde el historial de estados desde el último servicio (normalmente los últimos ~10 días)",
|
||||
"prediction_cycles": "Aprendido de ciclos",
|
||||
"phase_require_override": "Sobrescribir «Requerir al completar» para esta fase",
|
||||
"history_add_past": "Añadir una finalización pasada",
|
||||
"require_tag_scan": "Completar solo escaneando la etiqueta",
|
||||
"require_tag_scan_help": "Prueba de presencia: «Hecho» queda bloqueado en todas partes hasta escanear la etiqueta NFC o el código QR del propio objeto. Las automatizaciones pueden pasar 'via_tag_scan' al servicio complete.",
|
||||
"require_tag_scan_hint": "Esta tarea solo se completa escaneando su etiqueta NFC o código QR en el propio objeto; guardar aquí será rechazado."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Tyhjä = keskiyö (oletus). HA:n aikavyöhyke.",
|
||||
"at_time": "klo",
|
||||
"notes_optional": "Muistiinpanot (valinnainen)",
|
||||
"notes_markdown_hint": "Markdown on tuettu — **lihavointi**, listat, [linkit](…)",
|
||||
"cost_optional": "Kustannus (valinnainen)",
|
||||
"duration_minutes": "Kesto minuutteina (valinnainen)",
|
||||
"completed_at_optional": "Suoritettu (valinnainen, tyhjä = nyt)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Sivupalkin paneelin otsikko",
|
||||
"settings_notifications": "Ilmoitukset",
|
||||
"settings_notify_service": "Ilmoituspalvelu",
|
||||
"settings_shopping_list": "Ostoslista (ostotehtävät)",
|
||||
"settings_shopping_list_help": "Vähissä olevien osien ostomuistutukset näkyvät tässä luettelossa; kuittaus täydentää varaston.",
|
||||
"shopping_list_none": "Pois — ei ostoslistaa",
|
||||
"settings_install_assist_sentences": "Asenna Assist-lauseet",
|
||||
"settings_install_assist_sentences_hint": "Kopioi äänikomennot asetuksiisi, jotta perinteinen Assist-agentti tunnistaa ne. Itse muokkaamaasi tiedostoa ei koskaan korvata.",
|
||||
"test_notification": "Testi-ilmoitus",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Lisää askel",
|
||||
"phase_current": "Nykyinen vaihe",
|
||||
"phase_set": "Aseta nykyiseksi",
|
||||
"chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)"
|
||||
"chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)",
|
||||
"chart_history_alarm": "Laukaisimen näkymä tilahistoriasta: 1 = hälytystila pysyi pitoajan, 0 = kunnossa (yleensä viimeiset ~10 päivää)",
|
||||
"chart_history_count": "Muutoslaskuri koottu tilahistoriasta viime huollon jälkeen (yleensä viimeiset ~10 päivää)",
|
||||
"prediction_cycles": "Opittu sykleistä",
|
||||
"phase_require_override": "Ohita ”Vaadi valmistuessa” tälle vaiheelle",
|
||||
"history_add_past": "Lisää aiempi suoritus",
|
||||
"require_tag_scan": "Suoritus vain tunnisteen skannauksella",
|
||||
"require_tag_scan_help": "Läsnäolotodistus: ”Valmis” on estetty kaikkialla, kunnes laitteen NFC-tunniste tai QR-koodi on skannattu. Automaatiot voivat välittää 'via_tag_scan' complete-palvelulle.",
|
||||
"require_tag_scan_hint": "Tämä tehtävä valmistuu vain skannaamalla sen NFC-tunniste tai QR-koodi itse laitteesta — tallennus tässä estetään."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vide = minuit (défaut). Fuseau horaire HA.",
|
||||
"at_time": "à",
|
||||
"notes_optional": "Notes (optionnel)",
|
||||
"notes_markdown_hint": "Markdown est pris en charge — **gras**, listes, [liens](…)",
|
||||
"cost_optional": "Coût (optionnel)",
|
||||
"duration_minutes": "Durée en minutes (optionnel)",
|
||||
"completed_at_optional": "Effectué le (optionnel, vide = maintenant)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Titre du panneau latéral",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Service de notification",
|
||||
"settings_shopping_list": "Liste de courses (tâches d'achat)",
|
||||
"settings_shopping_list_help": "Les rappels d'achat pour pièces en rupture apparaissent dans cette liste ; cocher un élément réapprovisionne la pièce.",
|
||||
"shopping_list_none": "Désactivé — pas de liste de courses",
|
||||
"settings_install_assist_sentences": "Installer les phrases Assist",
|
||||
"settings_install_assist_sentences_hint": "Copie les phrases vocales dans votre configuration afin que l'agent Assist classique les reconnaisse. Un fichier que vous avez modifié n'est jamais écrasé.",
|
||||
"test_notification": "Notification de test",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Ajouter une étape",
|
||||
"phase_current": "Phase actuelle",
|
||||
"phase_set": "Définir comme actuelle",
|
||||
"chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)"
|
||||
"chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)",
|
||||
"chart_history_alarm": "Vue déclencheur issue de l'historique d'états : 1 = état d'alerte maintenu pendant le délai, 0 = normal (généralement les ~10 derniers jours)",
|
||||
"chart_history_count": "Nombre de changements reconstitué à partir de l'historique d'états depuis le dernier entretien (généralement les ~10 derniers jours)",
|
||||
"prediction_cycles": "Appris sur cycles",
|
||||
"phase_require_override": "Remplacer « Exiger à l’achèvement » pour cette phase",
|
||||
"history_add_past": "Ajouter une réalisation passée",
|
||||
"require_tag_scan": "Terminer uniquement en scannant le tag",
|
||||
"require_tag_scan_help": "Preuve de présence : « Terminé » est bloqué partout tant que le tag NFC ou le QR code sur l'objet n'a pas été scanné. Les automatisations peuvent passer 'via_tag_scan' au service complete.",
|
||||
"require_tag_scan_hint": "Cette tâche ne se termine qu'en scannant son tag NFC ou son QR code sur l'objet — l'enregistrement ici sera refusé."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "खाली = मध्यरात्रि (डिफ़ॉल्ट)। HA समय क्षेत्र।",
|
||||
"at_time": "बजे",
|
||||
"notes_optional": "टिप्पणियाँ (वैकल्पिक)",
|
||||
"notes_markdown_hint": "Markdown समर्थित है — **बोल्ड**, सूचियाँ, [लिंक](…)",
|
||||
"cost_optional": "लागत (वैकल्पिक)",
|
||||
"duration_minutes": "मिनटों में अवधि (वैकल्पिक)",
|
||||
"completed_at_optional": "पूर्ण होने का समय (वैकल्पिक, खाली = अभी)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "साइडबार पैनल शीर्षक",
|
||||
"settings_notifications": "सूचनाएँ",
|
||||
"settings_notify_service": "सूचना सेवा",
|
||||
"settings_shopping_list": "शॉपिंग सूची (खरीद कार्य)",
|
||||
"settings_shopping_list_help": "कम स्टॉक वाले पुर्जों के खरीद अनुस्मारक इस सूची में दिखते हैं; टिक करने पर पुर्जा री-स्टॉक होता है।",
|
||||
"shopping_list_none": "बंद — कोई शॉपिंग सूची नहीं",
|
||||
"settings_install_assist_sentences": "Assist वाक्य इंस्टॉल करें",
|
||||
"settings_install_assist_sentences_hint": "वॉइस वाक्यों को आपके कॉन्फ़िगरेशन में कॉपी करता है ताकि क्लासिक Assist एजेंट उन्हें पहचान सके। आपके द्वारा संपादित फ़ाइल कभी अधिलेखित नहीं होती।",
|
||||
"test_notification": "परीक्षण सूचना",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "कदम जोड़ें",
|
||||
"phase_current": "वर्तमान चरण",
|
||||
"phase_set": "वर्तमान बनाएँ",
|
||||
"chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)"
|
||||
"chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)",
|
||||
"chart_history_alarm": "स्थिति इतिहास से ट्रिगर दृश्य: 1 = चेतावनी स्थिति होल्ड समय तक बनी रही, 0 = सामान्य (आमतौर पर पिछले ~10 दिन)",
|
||||
"chart_history_count": "पिछली सेवा के बाद से स्थिति इतिहास से पुनर्निर्मित परिवर्तन गणना (आमतौर पर पिछले ~10 दिन)",
|
||||
"prediction_cycles": "चक्रों से सीखा",
|
||||
"phase_require_override": "इस चरण के लिए “पूर्ण करते समय आवश्यक” को ओवरराइड करें",
|
||||
"history_add_past": "पिछली पूर्णता जोड़ें",
|
||||
"require_tag_scan": "केवल टैग स्कैन से पूर्ण करें",
|
||||
"require_tag_scan_help": "उपस्थिति प्रमाण: जब तक वस्तु पर लगा NFC टैग या QR कोड स्कैन न हो, हर जगह “पूर्ण” अवरुद्ध रहता है। ऑटोमेशन complete सेवा को 'via_tag_scan' दे सकते हैं।",
|
||||
"require_tag_scan_hint": "यह कार्य केवल वस्तु पर लगे NFC टैग या QR कोड को स्कैन करके ही पूर्ण होता है — यहाँ सहेजना अस्वीकार होगा।"
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Üres = éjfél (alapértelmezés). HA időzóna.",
|
||||
"at_time": "ekkor:",
|
||||
"notes_optional": "Megjegyzések (opcionális)",
|
||||
"notes_markdown_hint": "A Markdown támogatott — **félkövér**, listák, [hivatkozások](…)",
|
||||
"cost_optional": "Költség (opcionális)",
|
||||
"duration_minutes": "Időtartam percben (opcionális)",
|
||||
"completed_at_optional": "Elvégezve ekkor (opcionális, üres = most)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Oldalsáv-panel címe",
|
||||
"settings_notifications": "Értesítések",
|
||||
"settings_notify_service": "Értesítési szolgáltatás",
|
||||
"settings_shopping_list": "Bevásárlólista (vásárlási feladatok)",
|
||||
"settings_shopping_list_help": "A fogyóban lévő alkatrészek vásárlási emlékeztetői ezen a listán jelennek meg; a kipipálás feltölti a készletet.",
|
||||
"shopping_list_none": "Ki — nincs bevásárlólista",
|
||||
"settings_install_assist_sentences": "Assist mondatok telepítése",
|
||||
"settings_install_assist_sentences_hint": "Átmásolja a hangmondatokat a konfigurációdba, hogy a klasszikus Assist ügynök felismerje őket. Az általad szerkesztett fájlt soha nem írja felül.",
|
||||
"test_notification": "Tesztértesítés",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Lépés hozzáadása",
|
||||
"phase_current": "Aktuális fázis",
|
||||
"phase_set": "Beállítás aktuálisként",
|
||||
"chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)"
|
||||
"chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)",
|
||||
"chart_history_alarm": "Trigger nézet az állapotelőzményekből: 1 = riasztási állapot a tartási idő alatt fennállt, 0 = rendben (általában az utolsó ~10 nap)",
|
||||
"chart_history_count": "Változásszámláló az állapotelőzményekből az utolsó szerviz óta (általában az utolsó ~10 nap)",
|
||||
"prediction_cycles": "Ciklusokból tanulva",
|
||||
"phase_require_override": "A „Megkövetelés elvégzéskor” felülbírálása ennél a fázisnál",
|
||||
"history_add_past": "Korábbi elvégzés rögzítése",
|
||||
"require_tag_scan": "Csak címke beolvasásával fejezhető be",
|
||||
"require_tag_scan_help": "Jelenlét-igazolás: a „Kész” mindenhol blokkolva van, amíg be nem olvasod az eszközön lévő NFC-címkét vagy QR-kódot. Az automatizálások 'via_tag_scan' értéket adhatnak át a complete szolgáltatásnak.",
|
||||
"require_tag_scan_hint": "Ez a feladat csak az eszközön lévő NFC-címke vagy QR-kód beolvasásával fejezhető be — az itteni mentés el lesz utasítva."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vuoto = mezzanotte (default). Fuso orario HA.",
|
||||
"at_time": "alle",
|
||||
"notes_optional": "Note (opzionale)",
|
||||
"notes_markdown_hint": "Markdown è supportato — **grassetto**, elenchi, [link](…)",
|
||||
"cost_optional": "Costo (opzionale)",
|
||||
"duration_minutes": "Durata in minuti (opzionale)",
|
||||
"completed_at_optional": "Completato il (opzionale, vuoto = adesso)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Titolo pannello laterale",
|
||||
"settings_notifications": "Notifiche",
|
||||
"settings_notify_service": "Servizio di notifica",
|
||||
"settings_shopping_list": "Lista della spesa (attività di acquisto)",
|
||||
"settings_shopping_list_help": "I promemoria di acquisto per ricambi scarsi appaiono in questo elenco; spuntarne uno rifornisce il ricambio.",
|
||||
"shopping_list_none": "Disattivato — nessuna lista della spesa",
|
||||
"settings_install_assist_sentences": "Installa le frasi di Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia le frasi vocali nella tua configurazione affinché l'agente Assist classico le riconosca. Un file modificato da te non viene mai sovrascritto.",
|
||||
"test_notification": "Notifica di test",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Aggiungi passo",
|
||||
"phase_current": "Fase attuale",
|
||||
"phase_set": "Imposta come attuale",
|
||||
"chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)"
|
||||
"chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)",
|
||||
"chart_history_alarm": "Vista del trigger dallo storico stati: 1 = stato di allarme mantenuto per il tempo di attesa, 0 = ok (in genere gli ultimi ~10 giorni)",
|
||||
"chart_history_count": "Conteggio dei cambi ricostruito dallo storico stati dall'ultimo intervento (in genere gli ultimi ~10 giorni)",
|
||||
"prediction_cycles": "Appreso da cicli",
|
||||
"phase_require_override": "Sostituisci «Richiedi al completamento» per questa fase",
|
||||
"history_add_past": "Aggiungi un completamento passato",
|
||||
"require_tag_scan": "Completa solo scansionando il tag",
|
||||
"require_tag_scan_help": "Prova di presenza: «Fatto» è bloccato ovunque finché non viene scansionato il tag NFC o il codice QR sull'oggetto. Le automazioni possono passare 'via_tag_scan' al servizio complete.",
|
||||
"require_tag_scan_hint": "Questa attività si completa solo scansionando il suo tag NFC o codice QR sull'oggetto — il salvataggio qui verrà rifiutato."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "空欄=深夜0時(既定)。HAのタイムゾーン。",
|
||||
"at_time": "時刻",
|
||||
"notes_optional": "メモ(任意)",
|
||||
"notes_markdown_hint": "Markdownに対応 — **太字**、リスト、[リンク](…)",
|
||||
"cost_optional": "費用(任意)",
|
||||
"duration_minutes": "所要時間(分、任意)",
|
||||
"completed_at_optional": "完了日時(任意・空欄 = 現在)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "サイドバーパネルのタイトル",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知サービス",
|
||||
"settings_shopping_list": "買い物リスト(購入タスク)",
|
||||
"settings_shopping_list_help": "在庫が少ない部品の購入リマインダーがこのリストに表示され、チェックすると部品が補充されます。",
|
||||
"shopping_list_none": "オフ — 買い物リストなし",
|
||||
"settings_install_assist_sentences": "Assist の文を導入する",
|
||||
"settings_install_assist_sentences_hint": "音声フレーズを設定に複製し、従来の Assist エージェントが認識できるようにします。自分で編集したファイルが上書きされることはありません。",
|
||||
"test_notification": "テスト通知",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "ステップを追加",
|
||||
"phase_current": "現在のフェーズ",
|
||||
"phase_set": "現在に設定",
|
||||
"chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)"
|
||||
"chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)",
|
||||
"chart_history_alarm": "状態履歴からのトリガー表示:1 = 保持時間の間アラート状態が続いた、0 = 正常(通常は直近約10日)",
|
||||
"chart_history_count": "前回のサービス以降の状態履歴から再構築した変化回数(通常は直近約10日)",
|
||||
"prediction_cycles": "学習したサイクル数",
|
||||
"phase_require_override": "このフェーズで「完了時に必須」を上書き",
|
||||
"history_add_past": "過去の完了を追加",
|
||||
"require_tag_scan": "タグのスキャンでのみ完了可能",
|
||||
"require_tag_scan_help": "在席証明:対象物のNFCタグまたはQRコードをスキャンするまで、どの画面でも「完了」はブロックされます。オートメーションはcompleteサービスに 'via_tag_scan' を渡せます。",
|
||||
"require_tag_scan_hint": "このタスクは対象物のNFCタグまたはQRコードをスキャンしないと完了できません。ここでの保存は拒否されます。"
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "비워두면 자정(기본값)입니다. HA 시간대 기준.",
|
||||
"at_time": "시각",
|
||||
"notes_optional": "메모 (선택)",
|
||||
"notes_markdown_hint": "Markdown 지원 — **굵게**, 목록, [링크](…)",
|
||||
"cost_optional": "비용 (선택)",
|
||||
"duration_minutes": "소요 시간(분, 선택)",
|
||||
"completed_at_optional": "완료 시각 (선택, 비우면 지금)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "사이드바 패널 제목",
|
||||
"settings_notifications": "알림",
|
||||
"settings_notify_service": "알림 서비스",
|
||||
"settings_shopping_list": "쇼핑 목록 (구매 작업)",
|
||||
"settings_shopping_list_help": "부족한 부품의 구매 알림이 이 목록에 표시되며, 체크하면 부품이 재입고됩니다.",
|
||||
"shopping_list_none": "끄기 — 쇼핑 목록 없음",
|
||||
"settings_install_assist_sentences": "Assist 문장 설치",
|
||||
"settings_install_assist_sentences_hint": "음성 문장을 설정에 복사하여 기존 Assist 에이전트가 인식하도록 합니다. 직접 수정한 파일은 절대 덮어쓰지 않습니다.",
|
||||
"test_notification": "테스트 알림",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "스텝 추가",
|
||||
"phase_current": "현재 단계",
|
||||
"phase_set": "현재로 설정",
|
||||
"chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)"
|
||||
"chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)",
|
||||
"chart_history_alarm": "상태 기록 기반 트리거 보기: 1 = 유지 시간 동안 경보 상태 유지, 0 = 정상 (보통 최근 약 10일)",
|
||||
"chart_history_count": "마지막 서비스 이후 상태 기록으로 재구성한 변경 횟수 (보통 최근 약 10일)",
|
||||
"prediction_cycles": "학습한 주기 수",
|
||||
"phase_require_override": "이 단계에서 ‘완료 시 필수’ 재정의",
|
||||
"history_add_past": "지난 완료 추가",
|
||||
"require_tag_scan": "태그 스캔으로만 완료 가능",
|
||||
"require_tag_scan_help": "현장 확인: 기기의 NFC 태그나 QR 코드를 스캔할 때까지 모든 화면에서 '완료'가 차단됩니다. 자동화는 complete 서비스에 'via_tag_scan'을 전달할 수 있습니다.",
|
||||
"require_tag_scan_hint": "이 작업은 기기의 NFC 태그나 QR 코드를 스캔해야만 완료됩니다. 여기서 저장하면 거부됩니다."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Tom = midnatt (standard). HA-tidssone.",
|
||||
"at_time": "kl.",
|
||||
"notes_optional": "Notater (valgfritt)",
|
||||
"notes_markdown_hint": "Markdown støttes — **fet**, lister, [lenker](…)",
|
||||
"cost_optional": "Kostnad (valgfritt)",
|
||||
"duration_minutes": "Varighet i minutter (valgfritt)",
|
||||
"completed_at_optional": "Utført den (valgfritt, tomt = nå)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Tittel på sidepanel",
|
||||
"settings_notifications": "Varsler",
|
||||
"settings_notify_service": "Varslingstjeneste",
|
||||
"settings_shopping_list": "Handleliste (kjøpsoppgaver)",
|
||||
"settings_shopping_list_help": "Kjøpspåminnelser for deler med lav beholdning vises i denne listen; avkryssing fyller på beholdningen.",
|
||||
"shopping_list_none": "Av — ingen handleliste",
|
||||
"settings_install_assist_sentences": "Installer Assist-setninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer talesetningene til konfigurasjonen din slik at den klassiske Assist-agenten gjenkjenner dem. En fil du selv har endret, blir aldri overskrevet.",
|
||||
"test_notification": "Testvarsel",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Legg til trinn",
|
||||
"phase_current": "Gjeldende fase",
|
||||
"phase_set": "Sett som gjeldende",
|
||||
"chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)"
|
||||
"chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)",
|
||||
"chart_history_alarm": "Utløservisning fra tilstandshistorikken: 1 = alarmtilstand holdt gjennom holdetiden, 0 = ok (vanligvis de siste ~10 dagene)",
|
||||
"chart_history_count": "Antall endringer gjenoppbygd fra tilstandshistorikken siden siste service (vanligvis de siste ~10 dagene)",
|
||||
"prediction_cycles": "Lært av sykluser",
|
||||
"phase_require_override": "Overstyr «Krev ved fullføring» for denne fasen",
|
||||
"history_add_past": "Legg til tidligere fullføring",
|
||||
"require_tag_scan": "Kan bare fullføres ved tag-skanning",
|
||||
"require_tag_scan_help": "Tilstedeværelsesbevis: «Fullført» er blokkert overalt til NFC-taggen eller QR-koden på selve tingen er skannet. Automatiseringer kan sende 'via_tag_scan' til complete-tjenesten.",
|
||||
"require_tag_scan_hint": "Denne oppgaven fullføres bare ved å skanne NFC-taggen eller QR-koden på selve tingen — lagring her vil bli avvist."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Leeg = middernacht (standaard). HA-tijdzone.",
|
||||
"at_time": "om",
|
||||
"notes_optional": "Notities (optioneel)",
|
||||
"notes_markdown_hint": "Markdown wordt ondersteund — **vet**, lijsten, [links](…)",
|
||||
"cost_optional": "Kosten (optioneel)",
|
||||
"duration_minutes": "Duur in minuten (optioneel)",
|
||||
"completed_at_optional": "Voltooid op (optioneel, leeg = nu)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Titel zijbalkpaneel",
|
||||
"settings_notifications": "Meldingen",
|
||||
"settings_notify_service": "Meldingsservice",
|
||||
"settings_shopping_list": "Boodschappenlijst (kooptaken)",
|
||||
"settings_shopping_list_help": "Koopherinneringen voor schaarse onderdelen verschijnen in deze lijst; afvinken vult de voorraad aan.",
|
||||
"shopping_list_none": "Uit — geen boodschappenlijst",
|
||||
"settings_install_assist_sentences": "Assist-zinnen installeren",
|
||||
"settings_install_assist_sentences_hint": "Kopieert de spraakzinnen naar je configuratie zodat de klassieke Assist-agent ze herkent. Een bestand dat je zelf hebt bewerkt wordt nooit overschreven.",
|
||||
"test_notification": "Testmelding",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Stap toevoegen",
|
||||
"phase_current": "Huidige fase",
|
||||
"phase_set": "Instellen als huidige",
|
||||
"chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)"
|
||||
"chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)",
|
||||
"chart_history_alarm": "Triggerweergave uit de statusgeschiedenis: 1 = alarmstatus gedurende de wachttijd aangehouden, 0 = in orde (meestal de laatste ~10 dagen)",
|
||||
"chart_history_count": "Aantal wisselingen opnieuw opgebouwd uit de statusgeschiedenis sinds de laatste service (meestal de laatste ~10 dagen)",
|
||||
"prediction_cycles": "Geleerd uit cycli",
|
||||
"phase_require_override": "„Vereisen bij afronden” voor deze fase overschrijven",
|
||||
"history_add_past": "Eerdere afronding toevoegen",
|
||||
"require_tag_scan": "Alleen af te ronden via tag-scan",
|
||||
"require_tag_scan_help": "Aanwezigheidsbewijs: „Klaar” is overal geblokkeerd totdat de NFC-tag of QR-code op het apparaat zelf is gescand. Automatiseringen kunnen 'via_tag_scan' aan de complete-service meegeven.",
|
||||
"require_tag_scan_hint": "Deze taak wordt alleen afgerond door de NFC-tag of QR-code op het apparaat te scannen — opslaan hier wordt geweigerd."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Puste = północ (domyślnie). Strefa czasowa HA.",
|
||||
"at_time": "o",
|
||||
"notes_optional": "Notatki (opcjonalne)",
|
||||
"notes_markdown_hint": "Obsługiwany jest Markdown — **pogrubienie**, listy, [linki](…)",
|
||||
"cost_optional": "Koszt (opcjonalne)",
|
||||
"duration_minutes": "Czas trwania w minutach (opcjonalne)",
|
||||
"completed_at_optional": "Wykonano dnia (opcjonalne, puste = teraz)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Tytuł panelu bocznego",
|
||||
"settings_notifications": "Powiadomienia",
|
||||
"settings_notify_service": "Usługa powiadomień",
|
||||
"settings_shopping_list": "Lista zakupów (zadania zakupowe)",
|
||||
"settings_shopping_list_help": "Przypomnienia o zakupie brakujących części pojawiają się na tej liście; odhaczenie uzupełnia zapas.",
|
||||
"shopping_list_none": "Wyłączone — brak listy zakupów",
|
||||
"settings_install_assist_sentences": "Zainstaluj zdania Assist",
|
||||
"settings_install_assist_sentences_hint": "Kopiuje zdania głosowe do Twojej konfiguracji, aby klasyczny agent Assist je rozpoznawał. Plik zmieniony przez Ciebie nigdy nie zostanie nadpisany.",
|
||||
"test_notification": "Powiadomienie testowe",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Dodaj krok",
|
||||
"phase_current": "Bieżąca faza",
|
||||
"phase_set": "Ustaw jako bieżącą",
|
||||
"chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)"
|
||||
"chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)",
|
||||
"chart_history_alarm": "Widok wyzwalacza z historii stanów: 1 = stan alarmu utrzymany przez czas podtrzymania, 0 = w porządku (zwykle ostatnie ~10 dni)",
|
||||
"chart_history_count": "Licznik zmian odtworzony z historii stanów od ostatniego serwisu (zwykle ostatnie ~10 dni)",
|
||||
"prediction_cycles": "Nauczone z cykli",
|
||||
"phase_require_override": "Nadpisz „Wymagaj przy ukończeniu” dla tej fazy",
|
||||
"history_add_past": "Dodaj wcześniejsze ukończenie",
|
||||
"require_tag_scan": "Ukończenie tylko przez skan tagu",
|
||||
"require_tag_scan_help": "Dowód obecności: „Gotowe” jest wszędzie zablokowane, dopóki nie zeskanujesz tagu NFC lub kodu QR na urządzeniu. Automatyzacje mogą przekazać 'via_tag_scan' do usługi complete.",
|
||||
"require_tag_scan_hint": "To zadanie można ukończyć tylko skanując jego tag NFC lub kod QR na urządzeniu — zapis tutaj zostanie odrzucony."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Vazio = meia-noite (padrão). Fuso horário do HA.",
|
||||
"at_time": "às",
|
||||
"notes_optional": "Observações (opcional)",
|
||||
"notes_markdown_hint": "Markdown é suportado — **negrito**, listas, [links](…)",
|
||||
"cost_optional": "Custo (opcional)",
|
||||
"duration_minutes": "Duração em minutos (opcional)",
|
||||
"completed_at_optional": "Concluído em (opcional, vazio = agora)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Título do painel na barra lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_shopping_list": "Lista de compras (tarefas de compra)",
|
||||
"settings_shopping_list_help": "Os lembretes de compra de peças em falta aparecem nesta lista; marcar um repõe a peça.",
|
||||
"shopping_list_none": "Desligado — sem lista de compras",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um arquivo que você editou nunca é sobrescrito.",
|
||||
"test_notification": "Notificação de teste",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Adicionar passo",
|
||||
"phase_current": "Fase atual",
|
||||
"phase_set": "Definir como atual",
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)"
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)",
|
||||
"chart_history_alarm": "Visão do gatilho a partir do histórico de estados: 1 = estado de alerta mantido pelo tempo de espera, 0 = normal (normalmente os últimos ~10 dias)",
|
||||
"chart_history_count": "Contagem de mudanças reconstruída a partir do histórico de estados desde o último serviço (normalmente os últimos ~10 dias)",
|
||||
"prediction_cycles": "Aprendido de ciclos",
|
||||
"phase_require_override": "Substituir “Exigir na conclusão” para esta fase",
|
||||
"history_add_past": "Adicionar conclusão passada",
|
||||
"require_tag_scan": "Concluir apenas escaneando a etiqueta",
|
||||
"require_tag_scan_help": "Prova de presença: “Concluído” fica bloqueado em toda parte até escanear a etiqueta NFC ou o código QR no próprio objeto. Automações podem passar 'via_tag_scan' ao serviço complete.",
|
||||
"require_tag_scan_hint": "Esta tarefa só é concluída escaneando sua etiqueta NFC ou código QR no próprio objeto — salvar aqui será recusado."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vazio = meia-noite (padrão). Fuso horário HA.",
|
||||
"at_time": "às",
|
||||
"notes_optional": "Notas (opcional)",
|
||||
"notes_markdown_hint": "Markdown é suportado — **negrito**, listas, [ligações](…)",
|
||||
"cost_optional": "Custo (opcional)",
|
||||
"duration_minutes": "Duração em minutos (opcional)",
|
||||
"completed_at_optional": "Concluído em (opcional, vazio = agora)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Título do painel lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_shopping_list": "Lista de compras (tarefas de compra)",
|
||||
"settings_shopping_list_help": "Os lembretes de compra de peças em falta aparecem nesta lista; marcar um repõe a peça.",
|
||||
"shopping_list_none": "Desligado — sem lista de compras",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um ficheiro que editou nunca é substituído.",
|
||||
"test_notification": "Notificação de teste",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Adicionar passo",
|
||||
"phase_current": "Fase atual",
|
||||
"phase_set": "Definir como atual",
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)"
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)",
|
||||
"chart_history_alarm": "Vista do disparador a partir do histórico de estados: 1 = estado de alerta mantido pelo tempo de espera, 0 = normal (normalmente os últimos ~10 dias)",
|
||||
"chart_history_count": "Contagem de mudanças reconstruída a partir do histórico de estados desde o último serviço (normalmente os últimos ~10 dias)",
|
||||
"prediction_cycles": "Aprendido de ciclos",
|
||||
"phase_require_override": "Substituir «Exigir na conclusão» para esta fase",
|
||||
"history_add_past": "Adicionar conclusão passada",
|
||||
"require_tag_scan": "Concluir apenas lendo a etiqueta",
|
||||
"require_tag_scan_help": "Prova de presença: «Concluído» fica bloqueado em todo o lado até ler a etiqueta NFC ou o código QR no próprio objeto. As automações podem passar 'via_tag_scan' ao serviço complete.",
|
||||
"require_tag_scan_hint": "Esta tarefa só é concluída lendo a sua etiqueta NFC ou código QR no próprio objeto — guardar aqui será recusado."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Пусто = полночь (по умолчанию). Часовой пояс HA.",
|
||||
"at_time": "в",
|
||||
"notes_optional": "Примечания (опционально)",
|
||||
"notes_markdown_hint": "Поддерживается Markdown — **жирный**, списки, [ссылки](…)",
|
||||
"cost_optional": "Стоимость (опционально)",
|
||||
"duration_minutes": "Длительность в минутах (опционально)",
|
||||
"completed_at_optional": "Выполнено (необязательно, пусто = сейчас)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Заголовок панели",
|
||||
"settings_notifications": "Уведомления",
|
||||
"settings_notify_service": "Сервис уведомлений",
|
||||
"settings_shopping_list": "Список покупок (задачи покупки)",
|
||||
"settings_shopping_list_help": "Напоминания о покупке заканчивающихся деталей появляются в этом списке; отметка пополняет запас.",
|
||||
"shopping_list_none": "Выкл. — без списка покупок",
|
||||
"settings_install_assist_sentences": "Установить фразы Assist",
|
||||
"settings_install_assist_sentences_hint": "Копирует голосовые фразы в вашу конфигурацию, чтобы классический агент Assist их распознавал. Файл, который вы изменили, никогда не перезаписывается.",
|
||||
"test_notification": "Тестовое уведомление",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Добавить шаг",
|
||||
"phase_current": "Текущая фаза",
|
||||
"phase_set": "Сделать текущей",
|
||||
"chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)"
|
||||
"chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)",
|
||||
"chart_history_alarm": "Вид триггера из истории состояний: 1 = состояние тревоги удерживалось в течение времени удержания, 0 = норма (обычно последние ~10 дней)",
|
||||
"chart_history_count": "Счётчик изменений восстановлен из истории состояний с момента последнего обслуживания (обычно последние ~10 дней)",
|
||||
"prediction_cycles": "Изучено по циклам",
|
||||
"phase_require_override": "Переопределить «Требовать при выполнении» для этой фазы",
|
||||
"history_add_past": "Добавить прошлое выполнение",
|
||||
"require_tag_scan": "Завершение только по скану метки",
|
||||
"require_tag_scan_help": "Подтверждение присутствия: «Готово» заблокировано везде, пока не отсканирована NFC-метка или QR-код на самом устройстве. Автоматизации могут передавать 'via_tag_scan' в службу complete.",
|
||||
"require_tag_scan_hint": "Эта задача завершается только сканированием её NFC-метки или QR-кода на самом устройстве — сохранение здесь будет отклонено."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Tomt = midnatt (standard). HA-tidszon.",
|
||||
"at_time": "kl.",
|
||||
"notes_optional": "Anteckningar (valfritt)",
|
||||
"notes_markdown_hint": "Markdown stöds — **fetstil**, listor, [länkar](…)",
|
||||
"cost_optional": "Kostnad (valfritt)",
|
||||
"duration_minutes": "Varaktighet i minuter (valfritt)",
|
||||
"completed_at_optional": "Utförd den (valfritt, tomt = nu)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Sidopanelens titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjänst",
|
||||
"settings_shopping_list": "Inköpslista (köpuppgifter)",
|
||||
"settings_shopping_list_help": "Köppåminnelser för delar som tar slut visas i denna lista; att bocka av en fyller på lagret.",
|
||||
"shopping_list_none": "Av — ingen inköpslista",
|
||||
"settings_install_assist_sentences": "Installera Assist-meningar",
|
||||
"settings_install_assist_sentences_hint": "Kopierar rösmeningarna till din konfiguration så att den klassiska Assist-agenten känner igen dem. En fil du själv har ändrat skrivs aldrig över.",
|
||||
"test_notification": "Testnotifikation",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Lägg till steg",
|
||||
"phase_current": "Aktuell fas",
|
||||
"phase_set": "Ange som aktuell",
|
||||
"chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)"
|
||||
"chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)",
|
||||
"chart_history_alarm": "Utlösarvy från tillståndshistoriken: 1 = larmläge hållet under hålltiden, 0 = ok (vanligtvis de senaste ~10 dagarna)",
|
||||
"chart_history_count": "Antal ändringar återskapat från tillståndshistoriken sedan senaste service (vanligtvis de senaste ~10 dagarna)",
|
||||
"prediction_cycles": "Lärt av cykler",
|
||||
"phase_require_override": "Åsidosätt ”Kräv vid slutförande” för denna fas",
|
||||
"history_add_past": "Lägg till tidigare slutförande",
|
||||
"require_tag_scan": "Kan endast slutföras via tagg-skanning",
|
||||
"require_tag_scan_help": "Närvarobevis: ”Klar” är blockerad överallt tills NFC-taggen eller QR-koden på själva saken har skannats. Automationer kan skicka 'via_tag_scan' till complete-tjänsten.",
|
||||
"require_tag_scan_hint": "Den här uppgiften slutförs endast genom att skanna dess NFC-tagg eller QR-kod på själva saken — att spara här kommer att nekas."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Boş = gece yarısı (varsayılan). HA saat dilimi.",
|
||||
"at_time": "saat",
|
||||
"notes_optional": "Notlar (isteğe bağlı)",
|
||||
"notes_markdown_hint": "Markdown desteklenir — **kalın**, listeler, [bağlantılar](…)",
|
||||
"cost_optional": "Maliyet (isteğe bağlı)",
|
||||
"duration_minutes": "Dakika cinsinden süre (isteğe bağlı)",
|
||||
"completed_at_optional": "Tamamlanma zamanı (isteğe bağlı, boş = şimdi)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Kenar çubuğu paneli başlığı",
|
||||
"settings_notifications": "Bildirimler",
|
||||
"settings_notify_service": "Bildirim servisi",
|
||||
"settings_shopping_list": "Alışveriş listesi (satın alma görevleri)",
|
||||
"settings_shopping_list_help": "Azalan parçalar için satın alma hatırlatıcıları bu listede görünür; işaretlemek parçayı yeniden stoklar.",
|
||||
"shopping_list_none": "Kapalı — alışveriş listesi yok",
|
||||
"settings_install_assist_sentences": "Assist cümlelerini yükle",
|
||||
"settings_install_assist_sentences_hint": "Sesli komut cümlelerini yapılandırmanıza kopyalar, böylece klasik Assist aracısı bunları tanır. Kendi düzenlediğiniz bir dosyanın üzerine asla yazılmaz.",
|
||||
"test_notification": "Test bildirimi",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Adım ekle",
|
||||
"phase_current": "Geçerli aşama",
|
||||
"phase_set": "Geçerli olarak ayarla",
|
||||
"chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)"
|
||||
"chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)",
|
||||
"chart_history_alarm": "Durum geçmişinden tetikleyici görünümü: 1 = uyarı durumu bekletme süresi boyunca sürdü, 0 = normal (genellikle son ~10 gün)",
|
||||
"chart_history_count": "Değişim sayısı son servisten bu yana durum geçmişinden yeniden oluşturuldu (genellikle son ~10 gün)",
|
||||
"prediction_cycles": "Döngülerden öğrenildi",
|
||||
"phase_require_override": "Bu aşama için “Tamamlarken zorunlu tut” ayarını geçersiz kıl",
|
||||
"history_add_past": "Geçmiş tamamlama ekle",
|
||||
"require_tag_scan": "Yalnızca etiket taranarak tamamlanır",
|
||||
"require_tag_scan_help": "Yerinde olma kanıtı: Cihazın üzerindeki NFC etiketi veya QR kodu taranana kadar 'Tamamlandı' her yerde engellenir. Otomasyonlar complete servisine 'via_tag_scan' iletebilir.",
|
||||
"require_tag_scan_hint": "Bu görev yalnızca cihazın üzerindeki NFC etiketi veya QR kodu taranarak tamamlanır — buradan kaydetme reddedilecektir."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Порожньо = опівночі (за замовчуванням). Часовий пояс HA.",
|
||||
"at_time": "о",
|
||||
"notes_optional": "Примітки (необов'язково)",
|
||||
"notes_markdown_hint": "Підтримується Markdown — **жирний**, списки, [посилання](…)",
|
||||
"cost_optional": "Вартість (необов'язково)",
|
||||
"duration_minutes": "Тривалість у хвилинах (необов'язково)",
|
||||
"completed_at_optional": "Виконано (необов'язково, порожньо = зараз)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Заголовок панелі",
|
||||
"settings_notifications": "Сповіщення",
|
||||
"settings_notify_service": "Служба сповіщень",
|
||||
"settings_shopping_list": "Список покупок (завдання покупки)",
|
||||
"settings_shopping_list_help": "Нагадування про купівлю деталей, що закінчуються, з'являються в цьому списку; відмітка поповнює запас.",
|
||||
"shopping_list_none": "Вимк. — без списку покупок",
|
||||
"settings_install_assist_sentences": "Встановити фрази Assist",
|
||||
"settings_install_assist_sentences_hint": "Копіює голосові фрази до вашої конфігурації, щоб класичний агент Assist їх розпізнавав. Файл, який ви редагували, ніколи не перезаписується.",
|
||||
"test_notification": "Тестове сповіщення",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Додати крок",
|
||||
"phase_current": "Поточна фаза",
|
||||
"phase_set": "Зробити поточною",
|
||||
"chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)"
|
||||
"chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)",
|
||||
"chart_history_alarm": "Вигляд тригера з історії станів: 1 = стан тривоги утримувався протягом часу утримання, 0 = норма (зазвичай останні ~10 днів)",
|
||||
"chart_history_count": "Лічильник змін відновлено з історії станів від останнього обслуговування (зазвичай останні ~10 днів)",
|
||||
"prediction_cycles": "Вивчено з циклів",
|
||||
"phase_require_override": "Перевизначити «Вимагати при виконанні» для цієї фази",
|
||||
"history_add_past": "Додати минуле виконання",
|
||||
"require_tag_scan": "Завершення лише скануванням мітки",
|
||||
"require_tag_scan_help": "Підтвердження присутності: «Готово» заблоковано всюди, доки не відскановано NFC-мітку або QR-код на самому пристрої. Автоматизації можуть передати 'via_tag_scan' службі complete.",
|
||||
"require_tag_scan_hint": "Це завдання завершується лише скануванням його NFC-мітки або QR-коду на самому пристрої — збереження тут буде відхилено."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "留空 = 午夜 (默认)。基于 HA 时区。",
|
||||
"at_time": "于",
|
||||
"notes_optional": "备注 (可选)",
|
||||
"notes_markdown_hint": "支持 Markdown — **加粗**、列表、[链接](…)",
|
||||
"cost_optional": "成本 (可选)",
|
||||
"duration_minutes": "耗时 (分钟, 可选)",
|
||||
"completed_at_optional": "完成时间(可选,留空 = 现在)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "侧边栏面板标题",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知服务",
|
||||
"settings_shopping_list": "购物清单(购买任务)",
|
||||
"settings_shopping_list_help": "库存不足部件的购买提醒会显示在此清单中;勾选即可补充库存。",
|
||||
"shopping_list_none": "关闭 — 无购物清单",
|
||||
"settings_install_assist_sentences": "安装 Assist 语句",
|
||||
"settings_install_assist_sentences_hint": "将语音语句复制到你的配置中,让传统 Assist 代理能够识别它们。你自己修改过的文件永远不会被覆盖。",
|
||||
"test_notification": "测试通知",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "添加步骤",
|
||||
"phase_current": "当前阶段",
|
||||
"phase_set": "设为当前",
|
||||
"chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)"
|
||||
"chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)",
|
||||
"chart_history_alarm": "来自状态历史的触发视图:1 = 警报状态保持了保持时间,0 = 正常(通常为最近约 10 天)",
|
||||
"chart_history_count": "根据上次维护以来的状态历史重建的变化次数(通常为最近约 10 天)",
|
||||
"prediction_cycles": "已学习的周期数",
|
||||
"phase_require_override": "为此阶段覆盖“完成时要求”",
|
||||
"history_add_past": "补录过去的完成记录",
|
||||
"require_tag_scan": "仅可通过扫描标签完成",
|
||||
"require_tag_scan_help": "在场证明:在扫描设备上的 NFC 标签或二维码之前,所有界面的“完成”均被阻止。自动化可向 complete 服务传递 'via_tag_scan'。",
|
||||
"require_tag_scan_hint": "此任务只能通过扫描设备上的 NFC 标签或二维码来完成——在此保存将被拒绝。"
|
||||
}
|
||||
@@ -18,10 +18,11 @@ import type {
|
||||
SavedViewFilters,
|
||||
} from "./types";
|
||||
import { UserService } from "./user-service";
|
||||
import { partsForCompletion } from "./helpers/shared-parts";
|
||||
import { effectivePhase, phaseLabel } from "./helpers/phases";
|
||||
import { phaseLabel } from "./helpers/phases";
|
||||
import { buildCompleteDialogArgs, fillAndOpenCompleteDialog } from "./helpers/complete-dialog-args";
|
||||
import "./maintenance-card-editor";
|
||||
import "./components/complete-dialog";
|
||||
import type { MaintenanceCompleteDialog } from "./components/complete-dialog";
|
||||
// The Battery Fleet card ships in this globally-loaded bundle so it is
|
||||
// available on every dashboard without another extra_module_url entry.
|
||||
import "./components/battery-fleet-card";
|
||||
@@ -514,32 +515,26 @@ export class MaintenanceSupporterCard extends LitElement {
|
||||
@click=${(e: Event) => {
|
||||
// Stop the row's open-task handler from also firing
|
||||
e.stopPropagation();
|
||||
const dlg = this.shadowRoot!.querySelector("maintenance-complete-dialog") as any;
|
||||
// #139: a phased task completes the phase
|
||||
// currently due — its fields override the
|
||||
// task-level ones (fallthrough included).
|
||||
const phase = effectivePhase(task);
|
||||
dlg.entryId = entry_id;
|
||||
dlg.taskId = task.id;
|
||||
dlg.taskName = task.name;
|
||||
dlg.checklist = phase ? phase.checklist : (task.checklist || []);
|
||||
dlg.adaptiveEnabled = !!task.adaptive_config?.enabled;
|
||||
dlg.taskType = task.type || "";
|
||||
dlg.readingUnit = (task as any).reading_unit || "";
|
||||
dlg.requiredFields = phase ? phase.requiredFields : (task.required_completion_fields || []);
|
||||
dlg.phaseLabel = phaseLabel(task);
|
||||
dlg.lang = L;
|
||||
// #99: editable per-completion parts selection
|
||||
// (skip on buy tasks — those restock instead).
|
||||
// #111: the list also carries the shared pools
|
||||
// this task draws on, each named after its
|
||||
// owner — resolving against the object's own
|
||||
// parts alone left a foreign link invisible.
|
||||
const isBuy = !!(task as any).part_ref;
|
||||
const links = phase ? phase.consumesParts : (task.consumes_parts || []);
|
||||
dlg.parts = isBuy ? [] : partsForCompletion({ consumes_parts: links }, entry_id, this._objects, L);
|
||||
dlg.consumesParts = isBuy ? [] : links;
|
||||
dlg.open();
|
||||
const dlg = this.shadowRoot!.querySelector<MaintenanceCompleteDialog>("maintenance-complete-dialog")!;
|
||||
// The SAME derivation the panel uses (phase
|
||||
// override, parts incl. shared pools, tag-scan
|
||||
// gate, restock default, checklist ticks) — the
|
||||
// card used to forward a subset.
|
||||
fillAndOpenCompleteDialog(
|
||||
dlg,
|
||||
buildCompleteDialogArgs({
|
||||
entryId: entry_id,
|
||||
taskId: task.id,
|
||||
taskName: task.name,
|
||||
task,
|
||||
objects: this._objects,
|
||||
lang: L,
|
||||
checklist: task.checklist || [],
|
||||
adaptiveEnabled: !!task.adaptive_config?.enabled,
|
||||
currencySymbol: this._stats?.budget?.currency_symbol || "",
|
||||
}),
|
||||
L,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
|
||||
@@ -15,9 +15,11 @@ import { buildObjectReportHtml, type ReportLabels } from "./helpers/report";
|
||||
import { warrantyStatus } from "./helpers/warranty";
|
||||
import { OBJECT_COLUMNS, DEFAULT_OBJECTS_TABLE_COLUMNS, sanitizeColumns } from "./helpers/object-columns";
|
||||
import { downloadTextFile } from "./helpers/download";
|
||||
import { renderNotesMarkdown } from "./helpers/notes-markdown";
|
||||
import { buildTaskWorksheetHtml, type WorksheetExcerpt, type WorksheetLabels } from "./helpers/worksheet";
|
||||
import { describePartLink, partsForCompletion } from "./helpers/shared-parts";
|
||||
import { effectivePhase, phaseLabel } from "./helpers/phases";
|
||||
import { describePartLink } from "./helpers/shared-parts";
|
||||
import { effectivePhase } from "./helpers/phases";
|
||||
import { buildCompleteDialogArgs, fillAndOpenCompleteDialog } from "./helpers/complete-dialog-args";
|
||||
import { describeWsError } from "./ws-errors";
|
||||
import { panelStyles } from "./panel-styles";
|
||||
import type {
|
||||
@@ -694,6 +696,20 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
if ((msg as SubscriptionEvent<MaintenanceObjectResponse>).objects) {
|
||||
writeObjectsCache(next, this._stats ?? null);
|
||||
}
|
||||
// The KPI row (_stats) and the budget card come from their own
|
||||
// endpoints — a pushed change (completion from the card, a timer
|
||||
// wave flipping a task overdue) left them frozen at the last
|
||||
// _loadData until the next user action.
|
||||
void this._refreshKpis();
|
||||
// The open task's full history is a separate fetch too; the
|
||||
// truncated list payload never wins the "longer list" comparison
|
||||
// in _historyCtx, so a pushed completion stayed invisible there.
|
||||
if (
|
||||
this._view === "task" && this._selectedEntryId && this._selectedTaskId
|
||||
&& (!!ev.objects || (ev.delta || []).some((d) => d.entry_id === this._selectedEntryId))
|
||||
) {
|
||||
void this._fetchFullHistory(this._selectedEntryId, this._selectedTaskId);
|
||||
}
|
||||
}
|
||||
},
|
||||
// deltas: only entries whose rebuilt response actually changed —
|
||||
@@ -713,6 +729,34 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
/** Refetch the KPI statistics + budget status after a pushed object change.
|
||||
* Coalesced: a burst of deltas (import, timer wave) runs at most one
|
||||
* in-flight fetch plus one trailing catch-up instead of one per event. */
|
||||
private _kpiRefreshInFlight = false;
|
||||
private _kpiRefreshPending = false;
|
||||
|
||||
private async _refreshKpis(): Promise<void> {
|
||||
if (this._kpiRefreshInFlight) {
|
||||
this._kpiRefreshPending = true;
|
||||
return;
|
||||
}
|
||||
this._kpiRefreshInFlight = true;
|
||||
try {
|
||||
do {
|
||||
this._kpiRefreshPending = false;
|
||||
const [statsResult, budgetResult] = await Promise.all([
|
||||
this.hass.connection.sendMessagePromise({ type: "maintenance_supporter/statistics" }).catch(() => null),
|
||||
this.hass.connection.sendMessagePromise({ type: "maintenance_supporter/budget_status" }).catch(() => null),
|
||||
]);
|
||||
if (!this.isConnected) return;
|
||||
if (statsResult) this._stats = statsResult as StatisticsResponse;
|
||||
if (budgetResult) this._budget = budgetResult as BudgetStatus;
|
||||
} while (this._kpiRefreshPending);
|
||||
} finally {
|
||||
this._kpiRefreshInFlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Data accessors ---
|
||||
|
||||
private get _taskRows(): TaskRow[] {
|
||||
@@ -1734,17 +1778,27 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._showToast(t("quick_complete_success", this._lang));
|
||||
} catch (e: unknown) {
|
||||
const code = (e as { code?: string })?.code || "";
|
||||
if (code === "no_defaults") {
|
||||
// No defaults set — open the dialog so the user can fill them in.
|
||||
// Both refusals mean "the dialog has to collect something": no
|
||||
// quick-complete defaults configured, or the task demands completion
|
||||
// details a silent complete cannot supply. The dialog opens as the
|
||||
// scan's fallback — via_tag_scan keeps the proof of presence.
|
||||
if (code === "no_defaults" || code === "completion_details_required") {
|
||||
this._openCompleteDialog(
|
||||
entryId, taskId, task.name,
|
||||
this._features.checklists ? task.checklist : undefined,
|
||||
this._features.adaptive && !!task.adaptive_config?.enabled,
|
||||
{ viaTagScan: true },
|
||||
);
|
||||
} else {
|
||||
this._showToast(t("action_error", this._lang));
|
||||
// Relay the server's reason (too_early carries the earliest date)
|
||||
// instead of a generic "action failed".
|
||||
this._showToast(describeWsError(e, this._lang, t("action_error", this._lang)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Silent success: the list must reflect the completion right away — the
|
||||
// subscription delta may lag or be coalesced.
|
||||
try { await this._loadData(); } catch { /* subscription will sync */ }
|
||||
}
|
||||
|
||||
// v2.21: printable one-pager for a task — details, checklist tick boxes,
|
||||
@@ -1874,59 +1928,41 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _openCompleteDialog(entryId: string, taskId: string, taskName: string, checklist?: string[], adaptiveEnabled?: boolean): void {
|
||||
private _openCompleteDialog(
|
||||
entryId: string, taskId: string, taskName: string, checklist?: string[], adaptiveEnabled?: boolean,
|
||||
opts?: { viaTagScan?: boolean },
|
||||
): void {
|
||||
void this._ui<MaintenanceCompleteDialog>("maintenance-complete-dialog")
|
||||
.then((dlg) => dlg && this._fillAndOpenCompleteDialog(dlg, entryId, taskId, taskName, checklist, adaptiveEnabled));
|
||||
.then((dlg) => dlg && this._fillAndOpenCompleteDialog(dlg, entryId, taskId, taskName, checklist, adaptiveEnabled, opts));
|
||||
}
|
||||
|
||||
private _fillAndOpenCompleteDialog(dlg: MaintenanceCompleteDialog, entryId: string, taskId: string, taskName: string, checklist?: string[], adaptiveEnabled?: boolean): void {
|
||||
dlg.entryId = entryId;
|
||||
dlg.taskId = taskId;
|
||||
dlg.taskName = taskName;
|
||||
dlg.lang = this._lang;
|
||||
dlg.checklist = checklist || [];
|
||||
dlg.adaptiveEnabled = !!adaptiveEnabled;
|
||||
// v2.20 (#83): reading tasks get a value field — resolve type + unit here
|
||||
// so none of the many call sites need to thread them through.
|
||||
const tk = this._objects
|
||||
.find((o) => o.entry_id === entryId)
|
||||
?.tasks.find((tsk) => tsk.id === taskId);
|
||||
// #139: a phased task completes the phase currently due — its checklist,
|
||||
// parts and required fields override the task-level values (fallthrough
|
||||
// is already baked into effectivePhase). Resolved HERE so every call
|
||||
// site of _openCompleteDialog rotates for free.
|
||||
const phase = tk ? effectivePhase(tk) : null;
|
||||
dlg.phaseLabel = phase ? phaseLabel(tk) : "";
|
||||
dlg.taskType = tk?.type || "";
|
||||
dlg.readingUnit = tk?.reading_unit || "";
|
||||
// #73: ticks recorded during the cycle prefill the dialog's checklist.
|
||||
dlg.checklistPrefill = tk?.checklist_progress || {};
|
||||
dlg.requiredFields = phase ? phase.requiredFields : (tk?.required_completion_fields || []);
|
||||
if (phase) {
|
||||
dlg.checklist = this._features.checklists ? phase.checklist : [];
|
||||
}
|
||||
// Spare parts: a buy task gets an editable restock-qty field; a consuming
|
||||
// task shows what it will decrement (incl. the storage location).
|
||||
const objParts = this._objects.find((o) => o.entry_id === entryId)?.parts || [];
|
||||
const refPart = tk?.part_ref ? objParts.find((pt) => pt.id === tk.part_ref!.part_id) : undefined;
|
||||
dlg.restockDefault = tk?.part_ref ? (refPart?.restock_quantity ?? 1) : null;
|
||||
// #104 follow-up: parts carry unit costs — the dialog offers their sum
|
||||
// as a one-click cost suggestion (buy task: restock qty × unit cost).
|
||||
dlg.restockUnitCost = tk?.part_ref ? (refPart?.cost ?? null) : null;
|
||||
dlg.currencySymbol = this._currencySymbol;
|
||||
// #111: a link may point at another object's pool — name that object, and
|
||||
// never drop a line that fails to resolve (the old .filter(Boolean) hid it).
|
||||
const links = phase ? phase.consumesParts : (tk?.consumes_parts || []);
|
||||
dlg.consumesInfo = links.map((link) =>
|
||||
describePartLink(link, entryId, this._objects, this._lang),
|
||||
/** Every dialog field is derived in helpers/complete-dialog-args — the
|
||||
* ONE derivation the panel, the Lovelace card and the quick-actions
|
||||
* dialog share (phase override #139, reading type+unit #83, restock
|
||||
* default + unit cost #104, shared pools #111, checklist ticks #73,
|
||||
* tag-scan gate). Resolved HERE so none of the call sites need to
|
||||
* thread anything but the name through. */
|
||||
private _fillAndOpenCompleteDialog(
|
||||
dlg: MaintenanceCompleteDialog, entryId: string, taskId: string, taskName: string,
|
||||
checklist?: string[], adaptiveEnabled?: boolean, opts?: { viaTagScan?: boolean },
|
||||
): void {
|
||||
fillAndOpenCompleteDialog(
|
||||
dlg,
|
||||
buildCompleteDialogArgs({
|
||||
entryId,
|
||||
taskId,
|
||||
taskName,
|
||||
task: this._getTask(entryId, taskId),
|
||||
objects: this._objects,
|
||||
lang: this._lang,
|
||||
checklist,
|
||||
checklistsEnabled: this._features.checklists,
|
||||
adaptiveEnabled,
|
||||
currencySymbol: this._currencySymbol,
|
||||
viaTagScan: opts?.viaTagScan,
|
||||
}),
|
||||
this._lang,
|
||||
);
|
||||
// #99: editable per-completion parts selection (not on buy tasks — those
|
||||
// RESTOCK via the qty field instead of consuming). The list carries the
|
||||
// object's own parts plus the shared pools this task draws on, so a foreign
|
||||
// link is visible and untickable rather than silently absent.
|
||||
dlg.parts = tk?.part_ref ? [] : partsForCompletion({ consumes_parts: links }, entryId, this._objects, this._lang);
|
||||
dlg.consumesParts = tk?.part_ref ? [] : links;
|
||||
dlg.open();
|
||||
}
|
||||
|
||||
private _openQrForObject(entryId: string, objectName: string): void {
|
||||
@@ -3287,7 +3323,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
${o.notes
|
||||
? html`<div class="object-notes">
|
||||
<div class="object-notes-label">${t("object_notes_label", L)}</div>
|
||||
<div class="object-notes-body">${o.notes}</div>
|
||||
<div class="object-notes-body">${renderNotesMarkdown(o.notes)}</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
|
||||
|
||||
@@ -445,6 +445,12 @@ export const panelStyles = css`
|
||||
word-break: break-word;
|
||||
line-height: 1.45;
|
||||
}
|
||||
/* Markdown notes: <ha-markdown> parses the source itself — the container's
|
||||
pre-wrap must not leak into its rendered paragraphs. */
|
||||
.object-notes-body ha-markdown,
|
||||
.task-meta-notes ha-markdown {
|
||||
white-space: normal;
|
||||
}
|
||||
.empty { color: var(--secondary-text-color); font-style: italic; }
|
||||
.analysis-empty-state { text-align: center; padding: 24px 16px; }
|
||||
.analysis-empty-state .empty { font-size: 15px; margin-bottom: 8px; }
|
||||
@@ -1053,6 +1059,19 @@ export const panelStyles = css`
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* #142: "add a past completion" entry point in the history tab — the
|
||||
backdate field lives in the complete dialog, but people LOOK for it
|
||||
here. */
|
||||
.history-add-past {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.history-add-past-btn ha-icon {
|
||||
--mdc-icon-size: 18px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* #139: cycle-phase strip in the task overview */
|
||||
.phases-card {
|
||||
background: var(--card-background-color, #fff);
|
||||
|
||||
@@ -44,6 +44,7 @@ export function renderPredictionSection(task: MaintenanceTask, lang: string, fea
|
||||
<span class="prediction-value prediction-days${task.days_until_threshold === 0 ? " exceeded" : task.sensor_prediction_urgency ? " urgent" : ""}">${task.days_until_threshold === 0 ? t("threshold_exceeded", lang) : "~" + Math.round(task.days_until_threshold!) + " " + t("days", lang)}</span>
|
||||
${task.threshold_prediction_date ? html`<span class="prediction-date">${formatDate(task.threshold_prediction_date, lang)}</span>` : nothing}
|
||||
${task.threshold_prediction_confidence ? html`<span class="confidence-dot ${task.threshold_prediction_confidence}"></span>` : nothing}
|
||||
${(task.prediction_cycles ?? 0) > 0 ? html`<span class="prediction-cycles">${t("prediction_cycles", lang)}: ${task.prediction_cycles}</span>` : nothing}
|
||||
</div>
|
||||
` : nothing}
|
||||
${hasEnv && features.environmental ? html`
|
||||
|
||||
@@ -39,6 +39,100 @@ export interface SparklineContext {
|
||||
* [Q1 − 1.5·IQR, Q3 + 1.5·IQR]. Robust to a few wild glitch readings (a
|
||||
* pressure sensor spiking to 100 while it normally sits at 1.5–3). No-ops on
|
||||
* short series (< 4 points) where quartiles aren't meaningful. */
|
||||
/** A binary HA state as the 0/1 level the history fallback plots, or null
|
||||
* for states the fallback cannot express (then no trigger view is built). */
|
||||
export function binaryLevel(state: string | null | undefined): 0 | 1 | null {
|
||||
const s = (state ?? "").trim().toLowerCase();
|
||||
if (s === "on" || s === "open" || s === "true") return 1;
|
||||
if (s === "off" || s === "closed" || s === "false") return 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface StateChangeView {
|
||||
points: ChartPoint[];
|
||||
/** "alarm": single-shot latch (target 1 + a to-state) — 1 while the alert
|
||||
* state is held, 0 otherwise. "count": change counter since the last
|
||||
* service, stepped, honouring the hold filter. */
|
||||
mode: "alarm" | "count";
|
||||
}
|
||||
|
||||
/** #141 (round 2): the recorder fallback plots the RAW entity state, but a
|
||||
* state_change trigger is about transitions. A water-valve switch that is
|
||||
* "on" all week with a few second-long "off" blips drew a flat 1 with the
|
||||
* "Target changes: 1" line on the same axis — while the header said 0 / 1,
|
||||
* because those blips never held for the #136 hold time. Re-express the
|
||||
* step series in the trigger's own terms:
|
||||
*
|
||||
* - latch (target_changes == 1 with a to-state): 1 = alert state HELD for
|
||||
* the hold time, 0 = fine. Reads as "is the trigger active" — for a
|
||||
* problem sensor (to-state on) this equals the raw line, for a switch
|
||||
* watched for "off" it is the inverse the user expects.
|
||||
* - counter (everything else): the cumulative number of matching, held
|
||||
* transitions since the last service, anchored so the line ends at the
|
||||
* trigger's live count (transitions before the recorder window are
|
||||
* unknown but already in that count).
|
||||
*
|
||||
* Returns null when the from/to states cannot be expressed as 0/1. */
|
||||
export function stateChangeView(
|
||||
points: ChartPoint[],
|
||||
tc: { trigger_from_state?: string | null; trigger_to_state?: string | null; trigger_target_changes?: number; trigger_for_minutes?: number },
|
||||
opts: { since: number | null; current: number | null; now?: number },
|
||||
): StateChangeView | null {
|
||||
if (points.length < 2) return null;
|
||||
const now = opts.now ?? Date.now();
|
||||
const holdMs = Math.max(0, tc.trigger_for_minutes ?? 0) * 60000;
|
||||
const fromLevel = tc.trigger_from_state ? binaryLevel(tc.trigger_from_state) : null;
|
||||
const toLevel = tc.trigger_to_state ? binaryLevel(tc.trigger_to_state) : null;
|
||||
if ((tc.trigger_from_state && fromLevel === null) || (tc.trigger_to_state && toLevel === null)) return null;
|
||||
|
||||
// Collapse the doubled step points into held segments (start, level).
|
||||
const sorted = [...points].sort((a, b) => a.ts - b.ts);
|
||||
const segments: { start: number; level: number }[] = [];
|
||||
for (const p of sorted) {
|
||||
const last = segments[segments.length - 1];
|
||||
if (!last || last.level !== p.val) segments.push({ start: p.ts, level: p.val });
|
||||
}
|
||||
const held = (i: number) => {
|
||||
const end = i + 1 < segments.length ? segments[i + 1].start : now;
|
||||
return end - segments[i].start >= holdMs;
|
||||
};
|
||||
const step = (out: ChartPoint[], ts: number, val: number) => {
|
||||
const last = out[out.length - 1];
|
||||
if (last && last.val !== val) out.push({ ts, val: last.val });
|
||||
out.push({ ts, val });
|
||||
};
|
||||
|
||||
const latch = (tc.trigger_target_changes ?? 1) === 1 && toLevel !== null;
|
||||
if (latch) {
|
||||
const out: ChartPoint[] = [];
|
||||
segments.forEach((seg, i) => step(out, seg.start, seg.level === toLevel && held(i) ? 1 : 0));
|
||||
const lastVal = out[out.length - 1]?.val ?? 0;
|
||||
out.push({ ts: now, val: lastVal });
|
||||
return { points: out, mode: "alarm" };
|
||||
}
|
||||
|
||||
const since = opts.since ?? segments[0].start;
|
||||
let counted = 0;
|
||||
const stepsAt: number[] = [];
|
||||
for (let i = 1; i < segments.length; i++) {
|
||||
const prev = segments[i - 1], seg = segments[i];
|
||||
if (fromLevel !== null && prev.level !== fromLevel) continue;
|
||||
if (toLevel !== null && seg.level !== toLevel) continue;
|
||||
if (!held(i) || seg.start < since) continue;
|
||||
counted += 1;
|
||||
stepsAt.push(seg.start);
|
||||
}
|
||||
const base = Math.max(0, (opts.current ?? counted) - counted);
|
||||
const out: ChartPoint[] = [{ ts: Math.max(since, segments[0].start), val: base }];
|
||||
let val = base;
|
||||
for (const ts of stepsAt) {
|
||||
val += 1;
|
||||
step(out, ts, val);
|
||||
}
|
||||
out.push({ ts: now, val });
|
||||
return { points: out, mode: "count" };
|
||||
}
|
||||
|
||||
export function filterOutliers(points: ChartPoint[]): ChartPoint[] {
|
||||
if (points.length < 4) return points;
|
||||
const vals = points.map((p) => p.val).sort((a, b) => a - b);
|
||||
@@ -276,6 +370,13 @@ function renderChart(task: MaintenanceTask, unit: string, ctx: SparklineContext)
|
||||
const entityId = tc.entity_id || "";
|
||||
|
||||
let points = rawStatsPoints(task, ctx);
|
||||
// #141 round 2: a state_change trigger on recorder history draws the
|
||||
// trigger's view (alert held / change count), not the raw entity state.
|
||||
let stateView: StateChangeView | null = null;
|
||||
if (triggerType === "state_change" && entityId && ctx.historyFallbackIds?.has(entityId)) {
|
||||
stateView = stateChangeView(points, tc, { since: lastServiceTs(task), current: task.trigger_current_value ?? null });
|
||||
if (stateView) points = stateView.points;
|
||||
}
|
||||
// Runtime accumulates hours with no stored intermediate snapshots: synthesize
|
||||
// the current cycle (0 at the last service → live value now) so the chart
|
||||
// resets each completion and always has a drawable 2-point line.
|
||||
@@ -326,8 +427,9 @@ function renderChart(task: MaintenanceTask, unit: string, ctx: SparklineContext)
|
||||
}
|
||||
targetValue = tc.trigger_target_value;
|
||||
forceZero = true;
|
||||
} else if (triggerType === "state_change" && tc.trigger_target_changes) {
|
||||
// Change counts / runtime hours already accumulate from zero.
|
||||
} else if (triggerType === "state_change" && tc.trigger_target_changes && stateView?.mode !== "alarm") {
|
||||
// Change counts / runtime hours already accumulate from zero. A latch
|
||||
// ("alarm" view) has no meaningful target line: its axis is alert/ok.
|
||||
targetValue = tc.trigger_target_changes;
|
||||
forceZero = true;
|
||||
} else if (triggerType === "runtime" && tc.trigger_runtime_hours) {
|
||||
@@ -341,15 +443,25 @@ function renderChart(task: MaintenanceTask, unit: string, ctx: SparklineContext)
|
||||
// exists — a slow 0.25 %/day decline classifies stable yet still crosses
|
||||
// the threshold in a foreseeable number of days.
|
||||
let projection: ChartPoint[] | null = null;
|
||||
// A projection pointing AWAY from the configured threshold is noise (the
|
||||
// ET-4800 finding: a refill-skewed rising rate on a below-threshold ink
|
||||
// sensor projected the ink level going UP) — draw it only when it heads
|
||||
// toward the bound, or when no bound is configured at all.
|
||||
const rate = task.degradation_rate;
|
||||
const pointsAway =
|
||||
rate != null &&
|
||||
((tc.trigger_below != null && tc.trigger_above == null && rate > 0) ||
|
||||
(tc.trigger_above != null && tc.trigger_below == null && rate < 0));
|
||||
if (
|
||||
targetValue == null &&
|
||||
task.degradation_rate != null &&
|
||||
rate != null &&
|
||||
!pointsAway &&
|
||||
(task.degradation_trend !== "stable" || task.days_until_threshold != null) &&
|
||||
task.degradation_trend !== "insufficient_data" &&
|
||||
points.length >= 2
|
||||
) {
|
||||
const lp = points[points.length - 1];
|
||||
projection = [lp, { ts: lp.ts + 30 * 86400000, val: lp.val + task.degradation_rate * 30 }];
|
||||
projection = [lp, { ts: lp.ts + 30 * 86400000, val: lp.val + rate * 30 }];
|
||||
}
|
||||
|
||||
const events: ChartEvent[] = task.history
|
||||
@@ -380,7 +492,7 @@ function renderChart(task: MaintenanceTask, unit: string, ctx: SparklineContext)
|
||||
if (entityId && ctx.historyFallbackIds?.has(entityId) && !statsFetchedEmpty) {
|
||||
return html`<div class="chart-note">
|
||||
<ha-icon icon="mdi:information-outline"></ha-icon>
|
||||
${t("chart_history_fallback", ctx.lang)}
|
||||
${t(stateView?.mode === "alarm" ? "chart_history_alarm" : stateView?.mode === "count" ? "chart_history_count" : "chart_history_fallback", ctx.lang)}
|
||||
</div>`;
|
||||
}
|
||||
if (statsFetchedEmpty) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
import { html, nothing } from "lit";
|
||||
import { isSafeHttpUrl } from "../helpers/url";
|
||||
import { renderNotesMarkdown } from "../helpers/notes-markdown";
|
||||
import { t, formatDate, formatDateTime, formatRecurrence } from "../styles";
|
||||
import type { AdvancedFeatures, HomeAssistant, MaintenanceTask, ManualDocRef } from "../types";
|
||||
import { renderTriggerSection, type SparklineContext } from "./sparkline";
|
||||
@@ -222,8 +223,8 @@ function renderPhasesCard(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
const last = lastByPhase.get(id);
|
||||
return html`
|
||||
<div class="phase-step ${i === cursor ? "current" : ""}"
|
||||
title=${i === cursor ? t("phase_current", L) : (ctx.isOperator ? t("phase_set", L) : "")}
|
||||
@click=${() => { if (ctx.isOperator && i !== cursor) ctx.setPhaseCursor(i); }}>
|
||||
title=${i === cursor ? t("phase_current", L) : t("phase_set", L)}
|
||||
@click=${() => { if (i !== cursor) ctx.setPhaseCursor(i); }}>
|
||||
<span class="phase-step-name">${i + 1}. ${name}</span>
|
||||
${last ? html`<span class="phase-step-last">${formatDate(last, L)}</span>` : nothing}
|
||||
</div>
|
||||
@@ -284,7 +285,7 @@ function renderTaskMeta(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
${task.notes ? html`
|
||||
<div class="task-meta-row">
|
||||
<ha-icon icon="mdi:note-text-outline"></ha-icon>
|
||||
<span class="task-meta-notes">${task.notes}</span>
|
||||
<span class="task-meta-notes">${renderNotesMarkdown(task.notes)}</span>
|
||||
</div>
|
||||
` : nothing}
|
||||
${safeTaskUrl ? html`
|
||||
@@ -494,6 +495,12 @@ export function renderOverviewTab(task: MaintenanceTask, ctx: TaskDetailContext)
|
||||
function renderHistoryTab(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
return html`
|
||||
<div class="tab-content history-tab">
|
||||
<div class="history-add-past">
|
||||
<ha-button appearance="plain" class="history-add-past-btn" @click=${() => ctx.openComplete(task)}>
|
||||
<ha-icon icon="mdi:calendar-plus"></ha-icon>
|
||||
${t("history_add_past", ctx.lang)}
|
||||
</ha-button>
|
||||
</div>
|
||||
${renderHistoryFilters(task, ctx.history)}
|
||||
${renderHistoryList(task, ctx.history)}
|
||||
</div>
|
||||
|
||||
@@ -99,10 +99,15 @@ export function t(key: string, lang?: string): string {
|
||||
* in English until then rather than as raw keys). Was copy-pasted across the
|
||||
* panel and both cards (drift audit 2026-08). */
|
||||
export function syncLocaleFromHass(
|
||||
host: { hass?: { language?: string; locale?: unknown }; requestUpdate: () => void },
|
||||
host: {
|
||||
hass?: { language?: string; locale?: unknown; config?: { country?: string | null } };
|
||||
requestUpdate: () => void;
|
||||
},
|
||||
changedProps: Map<string, unknown>,
|
||||
): void {
|
||||
if (changedProps.has("hass")) setDateTimePrefs(host.hass?.locale as Parameters<typeof setDateTimePrefs>[0]);
|
||||
if (changedProps.has("hass")) {
|
||||
setDateTimePrefs(host.hass?.locale as Parameters<typeof setDateTimePrefs>[0], host.hass?.config?.country);
|
||||
}
|
||||
const lang = host.hass?.language;
|
||||
if (lang && !isLocaleLoaded(lang)) {
|
||||
ensureLocale(lang).then(() => host.requestUpdate());
|
||||
@@ -196,15 +201,41 @@ function langToLocale(lang?: string): string {
|
||||
interface DateTimePrefs {
|
||||
date?: string;
|
||||
time?: string;
|
||||
country?: string;
|
||||
}
|
||||
const _w = window as unknown as { __msDateTimePrefs?: DateTimePrefs };
|
||||
const DT_PREFS: DateTimePrefs = (_w.__msDateTimePrefs ??= {});
|
||||
|
||||
/** Feed HA's per-user date/time format into formatDate/formatDateTime. */
|
||||
export function setDateTimePrefs(locale?: { date_format?: string; time_format?: string }): void {
|
||||
/** Feed HA's per-user date/time format into formatDate/formatDateTime.
|
||||
* `country` is the SERVER's configured country (#140): with the profile
|
||||
* format left on "language" it regionalizes the language default —
|
||||
* "en" + AU renders DD/MM/YYYY instead of the hard en-US mapping. */
|
||||
export function setDateTimePrefs(
|
||||
locale?: { date_format?: string; time_format?: string },
|
||||
country?: string | null,
|
||||
): void {
|
||||
if (!locale) return;
|
||||
DT_PREFS.date = locale.date_format;
|
||||
DT_PREFS.time = locale.time_format;
|
||||
if (country !== undefined) DT_PREFS.country = country || undefined;
|
||||
}
|
||||
|
||||
/** BCP-47 locale for the "language" default: the UI language regionalized
|
||||
* by the server country when Intl knows the combination (#140). Explicit
|
||||
* profile formats (DMY/MDY/YMD/system) never reach this path. */
|
||||
function resolveLocale(lang?: string): string {
|
||||
const base = langToLocale(lang);
|
||||
const country = DT_PREFS.country;
|
||||
if (country && /^[A-Za-z]{2}$/.test(country)) {
|
||||
const candidate = `${normLang(lang).split("-")[0]}-${country.toUpperCase()}`;
|
||||
try {
|
||||
new Intl.DateTimeFormat(candidate);
|
||||
return candidate;
|
||||
} catch {
|
||||
// syntactically invalid tag — keep the language mapping
|
||||
}
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
function _formatDateObj(d: Date, lang?: string): string {
|
||||
@@ -217,17 +248,17 @@ function _formatDateObj(d: Date, lang?: string): string {
|
||||
case "YMD": return `${yyyy}-${mm}-${dd}`;
|
||||
case "system":
|
||||
return d.toLocaleDateString(undefined, { day: "2-digit", month: "2-digit", year: "numeric" });
|
||||
default: // "language" or unset — derive from the UI language (pre-#97 behaviour)
|
||||
return d.toLocaleDateString(langToLocale(lang), { day: "2-digit", month: "2-digit", year: "numeric" });
|
||||
default: // "language" or unset — UI language, regionalized by the server country (#140)
|
||||
return d.toLocaleDateString(resolveLocale(lang), { day: "2-digit", month: "2-digit", year: "numeric" });
|
||||
}
|
||||
}
|
||||
|
||||
function _formatTimeObj(d: Date, lang?: string): string {
|
||||
switch (DT_PREFS.time) {
|
||||
case "12": return d.toLocaleTimeString(langToLocale(lang), { hour: "2-digit", minute: "2-digit", hour12: true });
|
||||
case "24": return d.toLocaleTimeString(langToLocale(lang), { hour: "2-digit", minute: "2-digit", hour12: false });
|
||||
case "12": return d.toLocaleTimeString(resolveLocale(lang), { hour: "2-digit", minute: "2-digit", hour12: true });
|
||||
case "24": return d.toLocaleTimeString(resolveLocale(lang), { hour: "2-digit", minute: "2-digit", hour12: false });
|
||||
case "system": return d.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" });
|
||||
default: return d.toLocaleTimeString(langToLocale(lang), { hour: "2-digit", minute: "2-digit" });
|
||||
default: return d.toLocaleTimeString(resolveLocale(lang), { hour: "2-digit", minute: "2-digit" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1291,6 +1322,11 @@ export const sharedStyles = css`
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.prediction-cycles {
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.prediction-entity {
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
|
||||
@@ -248,6 +248,9 @@ export interface MaintenanceTask {
|
||||
degradation_rate?: number | null;
|
||||
degradation_trend?: string | null; // "rising" | "falling" | "stable" | "insufficient_data"
|
||||
degradation_r_squared?: number | null;
|
||||
/** How many prior service cycles the degradation rate learned from
|
||||
* (sawtooth sensors); 0 = current window only. */
|
||||
prediction_cycles?: number;
|
||||
days_until_threshold?: number | null;
|
||||
threshold_prediction_date?: string | null;
|
||||
threshold_prediction_confidence?: string | null; // "low" | "medium" | "high"
|
||||
@@ -259,6 +262,8 @@ export interface MaintenanceTask {
|
||||
responsible_user_id?: string | null;
|
||||
custom_icon?: string | null;
|
||||
nfc_tag_id?: string | null;
|
||||
/** Proof of presence: completion only via NFC/QR scan on the thing. */
|
||||
require_tag_scan?: boolean;
|
||||
/** Number of documents linked to this task (paperclip badge on the row). */
|
||||
document_count?: number;
|
||||
/** v2.20 (#83): display unit for `reading`-type tasks ("kWh", "m³", ...). */
|
||||
@@ -560,6 +565,9 @@ export interface HomeAssistant {
|
||||
// "system" | "DMY" | "MDY" | "YMD" resp. "language" | "system" | "12" | "24")
|
||||
// — issue #97: dates must follow them, not just the UI language.
|
||||
locale?: { language: string; number_format?: string; date_format?: string; time_format?: string };
|
||||
/** Server config — country (#140) feeds the "language"-default date
|
||||
* format ("en" + AU → en-AU → DD/MM/YYYY). */
|
||||
config?: { country?: string | null };
|
||||
localize(key: string, ...args: unknown[]): string;
|
||||
user?: { id: string; name: string; is_admin: boolean; is_owner: boolean };
|
||||
/** Current access token — used for authenticated `fetch()` to our HTTP
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Prázdné = půlnoc (výchozí). Časové pásmo HA.",
|
||||
"at_time": "v",
|
||||
"notes_optional": "Poznámky (volitelné)",
|
||||
"notes_markdown_hint": "Markdown je podporován — **tučně**, seznamy, [odkazy](…)",
|
||||
"cost_optional": "Náklady (volitelné)",
|
||||
"duration_minutes": "Doba trvání v minutách (volitelné)",
|
||||
"completed_at_optional": "Dokončeno dne (volitelné, prázdné = nyní)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Název panelu",
|
||||
"settings_notifications": "Oznámení",
|
||||
"settings_notify_service": "Služba oznámení",
|
||||
"settings_shopping_list": "Nákupní seznam (nákupní úkoly)",
|
||||
"settings_shopping_list_help": "Upozornění na nákup docházejících dílů se objeví v tomto seznamu; odškrtnutí doplní zásobu.",
|
||||
"shopping_list_none": "Vypnuto — žádný nákupní seznam",
|
||||
"settings_install_assist_sentences": "Nainstalovat věty pro Assist",
|
||||
"settings_install_assist_sentences_hint": "Zkopíruje hlasové věty do vaší konfigurace, aby je klasický agent Assist rozpoznal. Soubor, který jste upravili, nebude nikdy přepsán.",
|
||||
"test_notification": "Testovací oznámení",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Přidat krok",
|
||||
"phase_current": "Aktuální fáze",
|
||||
"phase_set": "Nastavit jako aktuální",
|
||||
"chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)"
|
||||
"chart_history_fallback": "Pro tuto entitu nejsou dlouhodobé statistiky — zobrazuje se historie stavů recorderu (obvykle posledních ~10 dní)",
|
||||
"chart_history_alarm": "Pohled spouštěče z historie stavů: 1 = stav výstrahy držen po dobu přidržení, 0 = v pořádku (obvykle posledních ~10 dní)",
|
||||
"chart_history_count": "Počet změn sestaven z historie stavů od posledního servisu (obvykle posledních ~10 dní)",
|
||||
"prediction_cycles": "Naučeno z cyklů",
|
||||
"phase_require_override": "Přepsat „Vyžadovat při dokončení“ pro tuto fázi",
|
||||
"history_add_past": "Přidat dřívější dokončení",
|
||||
"require_tag_scan": "Dokončit pouze naskenováním štítku",
|
||||
"require_tag_scan_help": "Důkaz přítomnosti: „Hotovo“ je všude blokováno, dokud nenaskenujete NFC štítek nebo QR kód na zařízení. Automatizace mohou službě complete předat 'via_tag_scan'.",
|
||||
"require_tag_scan_hint": "Tento úkol lze dokončit pouze naskenováním jeho NFC štítku nebo QR kódu na zařízení — uložení zde bude odmítnuto."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Tom = midnat (standard). HA-tidszone.",
|
||||
"at_time": "kl.",
|
||||
"notes_optional": "Noter (valgfrit)",
|
||||
"notes_markdown_hint": "Markdown understøttes — **fed**, lister, [links](…)",
|
||||
"cost_optional": "Omkostning (valgfrit)",
|
||||
"duration_minutes": "Varighed i minutter (valgfrit)",
|
||||
"completed_at_optional": "Udført den (valgfrit, tomt = nu)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Sidepanelets titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjeneste",
|
||||
"settings_shopping_list": "Indkøbsliste (købsopgaver)",
|
||||
"settings_shopping_list_help": "Købspåmindelser for dele med lav beholdning vises på denne liste; afkrydsning genopfylder delen.",
|
||||
"shopping_list_none": "Fra — ingen indkøbsliste",
|
||||
"settings_install_assist_sentences": "Installer Assist-sætninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer stemmesætningerne til din konfiguration, så den klassiske Assist-agent genkender dem. En fil, du selv har redigeret, overskrives aldrig.",
|
||||
"test_notification": "Testnotifikation",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Tilføj trin",
|
||||
"phase_current": "Aktuel fase",
|
||||
"phase_set": "Sæt som aktuel",
|
||||
"chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)"
|
||||
"chart_history_fallback": "Ingen langtidsstatistik for denne enhed — recorderens tilstandshistorik vises (typisk de seneste ~10 dage)",
|
||||
"chart_history_alarm": "Udløservisning fra tilstandshistorikken: 1 = alarmtilstand fastholdt i holdetiden, 0 = ok (typisk de seneste ~10 dage)",
|
||||
"chart_history_count": "Antal skift genopbygget fra tilstandshistorikken siden sidste service (typisk de seneste ~10 dage)",
|
||||
"prediction_cycles": "Lært af cyklusser",
|
||||
"phase_require_override": "Tilsidesæt „Kræv ved udførelse“ for denne fase",
|
||||
"history_add_past": "Tilføj tidligere udførelse",
|
||||
"require_tag_scan": "Kan kun udføres ved tag-scanning",
|
||||
"require_tag_scan_help": "Tilstedeværelsesbevis: „Udført“ er blokeret overalt, indtil NFC-tagget eller QR-koden på selve tingen er scannet. Automatiseringer kan give 'via_tag_scan' til complete-tjenesten.",
|
||||
"require_tag_scan_hint": "Denne opgave udføres kun ved at scanne dens NFC-tag eller QR-kode på selve tingen — at gemme her vil blive afvist."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Leer = Mitternacht (Default). HA-Zeitzone.",
|
||||
"at_time": "um",
|
||||
"notes_optional": "Notizen (optional)",
|
||||
"notes_markdown_hint": "Markdown wird unterstützt — **fett**, Listen, [Links](…)",
|
||||
"cost_optional": "Kosten (optional)",
|
||||
"duration_minutes": "Dauer in Minuten (optional)",
|
||||
"completed_at_optional": "Erledigt am (optional, leer = jetzt)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Panel-Titel",
|
||||
"settings_notifications": "Benachrichtigungen",
|
||||
"settings_notify_service": "Benachrichtigungsdienst",
|
||||
"settings_shopping_list": "Einkaufsliste (Kauf-Aufgaben)",
|
||||
"settings_shopping_list_help": "Kauf-Erinnerungen für knappe Teile erscheinen in dieser To-do-Liste; Abhaken füllt den Bestand auf.",
|
||||
"shopping_list_none": "Aus — keine Einkaufsliste",
|
||||
"settings_install_assist_sentences": "Assist-Sätze installieren",
|
||||
"settings_install_assist_sentences_hint": "Kopiert die Sprachbefehle in deine Konfiguration, damit der klassische Assist-Agent sie erkennt. Eine selbst bearbeitete Datei wird nie überschrieben.",
|
||||
"test_notification": "Test-Benachrichtigung",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Schritt anhängen",
|
||||
"phase_current": "Aktuelle Phase",
|
||||
"phase_set": "Als aktuell setzen",
|
||||
"chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)"
|
||||
"chart_history_fallback": "Keine Langzeitstatistik für diese Entität — angezeigt wird der Recorder-Zustandsverlauf (üblicherweise die letzten ~10 Tage)",
|
||||
"chart_history_alarm": "Trigger-Sicht aus dem Recorder-Zustandsverlauf: 1 = Alarmzustand über die Haltezeit gehalten, 0 = in Ordnung (typisch die letzten ~10 Tage)",
|
||||
"chart_history_count": "Wechselzähler aus dem Recorder-Zustandsverlauf seit dem letzten Service (typisch die letzten ~10 Tage)",
|
||||
"prediction_cycles": "Gelernt aus Zyklen",
|
||||
"phase_require_override": "„Bei Erledigung verlangen“ für diese Phase überschreiben",
|
||||
"history_add_past": "Vergangene Erledigung nachtragen",
|
||||
"require_tag_scan": "Nur per Tag-Scan abschließbar",
|
||||
"require_tag_scan_help": "Anwesenheitsnachweis: „Erledigt“ ist überall blockiert, bis das NFC-Tag oder der QR-Code am Gerät gescannt wurde. Automationen können 'via_tag_scan' an den complete-Dienst übergeben.",
|
||||
"require_tag_scan_hint": "Diese Aufgabe kann nur per Scan des NFC-Tags oder QR-Codes am Gerät erledigt werden — Speichern hier wird abgelehnt."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Empty = midnight (default). HA timezone.",
|
||||
"at_time": "at",
|
||||
"notes_optional": "Notes (optional)",
|
||||
"notes_markdown_hint": "Markdown is supported — **bold**, lists, [links](…)",
|
||||
"cost_optional": "Cost (optional)",
|
||||
"duration_minutes": "Duration in minutes (optional)",
|
||||
"completed_at_optional": "Completed at (optional, empty = now)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Sidebar panel title",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Notification service",
|
||||
"settings_shopping_list": "Shopping list (buy tasks)",
|
||||
"settings_shopping_list_help": "Low-part buy reminders appear in this to-do list; checking one off restocks the part.",
|
||||
"shopping_list_none": "Off — no shopping list",
|
||||
"settings_install_assist_sentences": "Install Assist sentences",
|
||||
"settings_install_assist_sentences_hint": "Copies the voice sentences into your configuration so the classic Assist agent recognises them. A file you edited yourself is never overwritten.",
|
||||
"test_notification": "Test notification",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Add step",
|
||||
"phase_current": "Current phase",
|
||||
"phase_set": "Set as current",
|
||||
"chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)"
|
||||
"chart_history_fallback": "No long-term statistics for this entity — showing recorder state history (typically the last ~10 days)",
|
||||
"chart_history_alarm": "Trigger view from recorder state history: 1 = alert state held for the hold time, 0 = fine (typically the last ~10 days)",
|
||||
"chart_history_count": "Change count rebuilt from recorder state history since the last service (typically the last ~10 days)",
|
||||
"prediction_cycles": "Learned from cycles",
|
||||
"phase_require_override": "Override “Require on completion” for this phase",
|
||||
"history_add_past": "Add past completion",
|
||||
"require_tag_scan": "Only complete by scanning the tag",
|
||||
"require_tag_scan_help": "Proof of presence: Done is blocked on every surface until the NFC tag or QR code on the thing itself is scanned. Automations can pass 'via_tag_scan' to the complete service.",
|
||||
"require_tag_scan_hint": "This task completes only by scanning its NFC tag or QR code on the thing itself — saving here will be refused."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vacío = medianoche (predeterminado). Zona horaria HA.",
|
||||
"at_time": "a las",
|
||||
"notes_optional": "Notas (opcional)",
|
||||
"notes_markdown_hint": "Se admite Markdown — **negrita**, listas, [enlaces](…)",
|
||||
"cost_optional": "Coste (opcional)",
|
||||
"duration_minutes": "Duración en minutos (opcional)",
|
||||
"completed_at_optional": "Completado el (opcional, vacío = ahora)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Título del panel lateral",
|
||||
"settings_notifications": "Notificaciones",
|
||||
"settings_notify_service": "Servicio de notificación",
|
||||
"settings_shopping_list": "Lista de la compra (tareas de compra)",
|
||||
"settings_shopping_list_help": "Los recordatorios de compra de piezas escasas aparecen en esta lista; marcar uno repone la pieza.",
|
||||
"shopping_list_none": "Desactivado — sin lista de la compra",
|
||||
"settings_install_assist_sentences": "Instalar frases de Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia las frases de voz en tu configuración para que el agente clásico de Assist las reconozca. Un archivo que hayas editado nunca se sobrescribe.",
|
||||
"test_notification": "Notificación de prueba",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Añadir paso",
|
||||
"phase_current": "Fase actual",
|
||||
"phase_set": "Establecer como actual",
|
||||
"chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)"
|
||||
"chart_history_fallback": "Sin estadísticas a largo plazo para esta entidad: se muestra el historial de estados del recorder (normalmente los últimos ~10 días)",
|
||||
"chart_history_alarm": "Vista del disparador desde el historial de estados: 1 = estado de alerta mantenido durante el tiempo de retención, 0 = correcto (normalmente los últimos ~10 días)",
|
||||
"chart_history_count": "Recuento de cambios reconstruido desde el historial de estados desde el último servicio (normalmente los últimos ~10 días)",
|
||||
"prediction_cycles": "Aprendido de ciclos",
|
||||
"phase_require_override": "Sobrescribir «Requerir al completar» para esta fase",
|
||||
"history_add_past": "Añadir una finalización pasada",
|
||||
"require_tag_scan": "Completar solo escaneando la etiqueta",
|
||||
"require_tag_scan_help": "Prueba de presencia: «Hecho» queda bloqueado en todas partes hasta escanear la etiqueta NFC o el código QR del propio objeto. Las automatizaciones pueden pasar 'via_tag_scan' al servicio complete.",
|
||||
"require_tag_scan_hint": "Esta tarea solo se completa escaneando su etiqueta NFC o código QR en el propio objeto; guardar aquí será rechazado."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Tyhjä = keskiyö (oletus). HA:n aikavyöhyke.",
|
||||
"at_time": "klo",
|
||||
"notes_optional": "Muistiinpanot (valinnainen)",
|
||||
"notes_markdown_hint": "Markdown on tuettu — **lihavointi**, listat, [linkit](…)",
|
||||
"cost_optional": "Kustannus (valinnainen)",
|
||||
"duration_minutes": "Kesto minuutteina (valinnainen)",
|
||||
"completed_at_optional": "Suoritettu (valinnainen, tyhjä = nyt)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Sivupalkin paneelin otsikko",
|
||||
"settings_notifications": "Ilmoitukset",
|
||||
"settings_notify_service": "Ilmoituspalvelu",
|
||||
"settings_shopping_list": "Ostoslista (ostotehtävät)",
|
||||
"settings_shopping_list_help": "Vähissä olevien osien ostomuistutukset näkyvät tässä luettelossa; kuittaus täydentää varaston.",
|
||||
"shopping_list_none": "Pois — ei ostoslistaa",
|
||||
"settings_install_assist_sentences": "Asenna Assist-lauseet",
|
||||
"settings_install_assist_sentences_hint": "Kopioi äänikomennot asetuksiisi, jotta perinteinen Assist-agentti tunnistaa ne. Itse muokkaamaasi tiedostoa ei koskaan korvata.",
|
||||
"test_notification": "Testi-ilmoitus",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Lisää askel",
|
||||
"phase_current": "Nykyinen vaihe",
|
||||
"phase_set": "Aseta nykyiseksi",
|
||||
"chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)"
|
||||
"chart_history_fallback": "Tälle entiteetille ei ole pitkän aikavälin tilastoja — näytetään recorderin tilahistoria (yleensä viimeiset ~10 päivää)",
|
||||
"chart_history_alarm": "Laukaisimen näkymä tilahistoriasta: 1 = hälytystila pysyi pitoajan, 0 = kunnossa (yleensä viimeiset ~10 päivää)",
|
||||
"chart_history_count": "Muutoslaskuri koottu tilahistoriasta viime huollon jälkeen (yleensä viimeiset ~10 päivää)",
|
||||
"prediction_cycles": "Opittu sykleistä",
|
||||
"phase_require_override": "Ohita ”Vaadi valmistuessa” tälle vaiheelle",
|
||||
"history_add_past": "Lisää aiempi suoritus",
|
||||
"require_tag_scan": "Suoritus vain tunnisteen skannauksella",
|
||||
"require_tag_scan_help": "Läsnäolotodistus: ”Valmis” on estetty kaikkialla, kunnes laitteen NFC-tunniste tai QR-koodi on skannattu. Automaatiot voivat välittää 'via_tag_scan' complete-palvelulle.",
|
||||
"require_tag_scan_hint": "Tämä tehtävä valmistuu vain skannaamalla sen NFC-tunniste tai QR-koodi itse laitteesta — tallennus tässä estetään."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vide = minuit (défaut). Fuseau horaire HA.",
|
||||
"at_time": "à",
|
||||
"notes_optional": "Notes (optionnel)",
|
||||
"notes_markdown_hint": "Markdown est pris en charge — **gras**, listes, [liens](…)",
|
||||
"cost_optional": "Coût (optionnel)",
|
||||
"duration_minutes": "Durée en minutes (optionnel)",
|
||||
"completed_at_optional": "Effectué le (optionnel, vide = maintenant)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Titre du panneau latéral",
|
||||
"settings_notifications": "Notifications",
|
||||
"settings_notify_service": "Service de notification",
|
||||
"settings_shopping_list": "Liste de courses (tâches d'achat)",
|
||||
"settings_shopping_list_help": "Les rappels d'achat pour pièces en rupture apparaissent dans cette liste ; cocher un élément réapprovisionne la pièce.",
|
||||
"shopping_list_none": "Désactivé — pas de liste de courses",
|
||||
"settings_install_assist_sentences": "Installer les phrases Assist",
|
||||
"settings_install_assist_sentences_hint": "Copie les phrases vocales dans votre configuration afin que l'agent Assist classique les reconnaisse. Un fichier que vous avez modifié n'est jamais écrasé.",
|
||||
"test_notification": "Notification de test",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Ajouter une étape",
|
||||
"phase_current": "Phase actuelle",
|
||||
"phase_set": "Définir comme actuelle",
|
||||
"chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)"
|
||||
"chart_history_fallback": "Pas de statistiques à long terme pour cette entité — affichage de l'historique d'états du recorder (généralement les ~10 derniers jours)",
|
||||
"chart_history_alarm": "Vue déclencheur issue de l'historique d'états : 1 = état d'alerte maintenu pendant le délai, 0 = normal (généralement les ~10 derniers jours)",
|
||||
"chart_history_count": "Nombre de changements reconstitué à partir de l'historique d'états depuis le dernier entretien (généralement les ~10 derniers jours)",
|
||||
"prediction_cycles": "Appris sur cycles",
|
||||
"phase_require_override": "Remplacer « Exiger à l’achèvement » pour cette phase",
|
||||
"history_add_past": "Ajouter une réalisation passée",
|
||||
"require_tag_scan": "Terminer uniquement en scannant le tag",
|
||||
"require_tag_scan_help": "Preuve de présence : « Terminé » est bloqué partout tant que le tag NFC ou le QR code sur l'objet n'a pas été scanné. Les automatisations peuvent passer 'via_tag_scan' au service complete.",
|
||||
"require_tag_scan_hint": "Cette tâche ne se termine qu'en scannant son tag NFC ou son QR code sur l'objet — l'enregistrement ici sera refusé."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "खाली = मध्यरात्रि (डिफ़ॉल्ट)। HA समय क्षेत्र।",
|
||||
"at_time": "बजे",
|
||||
"notes_optional": "टिप्पणियाँ (वैकल्पिक)",
|
||||
"notes_markdown_hint": "Markdown समर्थित है — **बोल्ड**, सूचियाँ, [लिंक](…)",
|
||||
"cost_optional": "लागत (वैकल्पिक)",
|
||||
"duration_minutes": "मिनटों में अवधि (वैकल्पिक)",
|
||||
"completed_at_optional": "पूर्ण होने का समय (वैकल्पिक, खाली = अभी)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "साइडबार पैनल शीर्षक",
|
||||
"settings_notifications": "सूचनाएँ",
|
||||
"settings_notify_service": "सूचना सेवा",
|
||||
"settings_shopping_list": "शॉपिंग सूची (खरीद कार्य)",
|
||||
"settings_shopping_list_help": "कम स्टॉक वाले पुर्जों के खरीद अनुस्मारक इस सूची में दिखते हैं; टिक करने पर पुर्जा री-स्टॉक होता है।",
|
||||
"shopping_list_none": "बंद — कोई शॉपिंग सूची नहीं",
|
||||
"settings_install_assist_sentences": "Assist वाक्य इंस्टॉल करें",
|
||||
"settings_install_assist_sentences_hint": "वॉइस वाक्यों को आपके कॉन्फ़िगरेशन में कॉपी करता है ताकि क्लासिक Assist एजेंट उन्हें पहचान सके। आपके द्वारा संपादित फ़ाइल कभी अधिलेखित नहीं होती।",
|
||||
"test_notification": "परीक्षण सूचना",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "कदम जोड़ें",
|
||||
"phase_current": "वर्तमान चरण",
|
||||
"phase_set": "वर्तमान बनाएँ",
|
||||
"chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)"
|
||||
"chart_history_fallback": "इस एंटिटी के लिए दीर्घकालिक आँकड़े नहीं हैं — recorder की स्थिति का इतिहास दिखाया जा रहा है (आमतौर पर पिछले ~10 दिन)",
|
||||
"chart_history_alarm": "स्थिति इतिहास से ट्रिगर दृश्य: 1 = चेतावनी स्थिति होल्ड समय तक बनी रही, 0 = सामान्य (आमतौर पर पिछले ~10 दिन)",
|
||||
"chart_history_count": "पिछली सेवा के बाद से स्थिति इतिहास से पुनर्निर्मित परिवर्तन गणना (आमतौर पर पिछले ~10 दिन)",
|
||||
"prediction_cycles": "चक्रों से सीखा",
|
||||
"phase_require_override": "इस चरण के लिए “पूर्ण करते समय आवश्यक” को ओवरराइड करें",
|
||||
"history_add_past": "पिछली पूर्णता जोड़ें",
|
||||
"require_tag_scan": "केवल टैग स्कैन से पूर्ण करें",
|
||||
"require_tag_scan_help": "उपस्थिति प्रमाण: जब तक वस्तु पर लगा NFC टैग या QR कोड स्कैन न हो, हर जगह “पूर्ण” अवरुद्ध रहता है। ऑटोमेशन complete सेवा को 'via_tag_scan' दे सकते हैं।",
|
||||
"require_tag_scan_hint": "यह कार्य केवल वस्तु पर लगे NFC टैग या QR कोड को स्कैन करके ही पूर्ण होता है — यहाँ सहेजना अस्वीकार होगा।"
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Üres = éjfél (alapértelmezés). HA időzóna.",
|
||||
"at_time": "ekkor:",
|
||||
"notes_optional": "Megjegyzések (opcionális)",
|
||||
"notes_markdown_hint": "A Markdown támogatott — **félkövér**, listák, [hivatkozások](…)",
|
||||
"cost_optional": "Költség (opcionális)",
|
||||
"duration_minutes": "Időtartam percben (opcionális)",
|
||||
"completed_at_optional": "Elvégezve ekkor (opcionális, üres = most)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Oldalsáv-panel címe",
|
||||
"settings_notifications": "Értesítések",
|
||||
"settings_notify_service": "Értesítési szolgáltatás",
|
||||
"settings_shopping_list": "Bevásárlólista (vásárlási feladatok)",
|
||||
"settings_shopping_list_help": "A fogyóban lévő alkatrészek vásárlási emlékeztetői ezen a listán jelennek meg; a kipipálás feltölti a készletet.",
|
||||
"shopping_list_none": "Ki — nincs bevásárlólista",
|
||||
"settings_install_assist_sentences": "Assist mondatok telepítése",
|
||||
"settings_install_assist_sentences_hint": "Átmásolja a hangmondatokat a konfigurációdba, hogy a klasszikus Assist ügynök felismerje őket. Az általad szerkesztett fájlt soha nem írja felül.",
|
||||
"test_notification": "Tesztértesítés",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Lépés hozzáadása",
|
||||
"phase_current": "Aktuális fázis",
|
||||
"phase_set": "Beállítás aktuálisként",
|
||||
"chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)"
|
||||
"chart_history_fallback": "Ehhez az entitáshoz nincs hosszú távú statisztika — a recorder állapotelőzményei jelennek meg (jellemzően az utolsó ~10 nap)",
|
||||
"chart_history_alarm": "Trigger nézet az állapotelőzményekből: 1 = riasztási állapot a tartási idő alatt fennállt, 0 = rendben (általában az utolsó ~10 nap)",
|
||||
"chart_history_count": "Változásszámláló az állapotelőzményekből az utolsó szerviz óta (általában az utolsó ~10 nap)",
|
||||
"prediction_cycles": "Ciklusokból tanulva",
|
||||
"phase_require_override": "A „Megkövetelés elvégzéskor” felülbírálása ennél a fázisnál",
|
||||
"history_add_past": "Korábbi elvégzés rögzítése",
|
||||
"require_tag_scan": "Csak címke beolvasásával fejezhető be",
|
||||
"require_tag_scan_help": "Jelenlét-igazolás: a „Kész” mindenhol blokkolva van, amíg be nem olvasod az eszközön lévő NFC-címkét vagy QR-kódot. Az automatizálások 'via_tag_scan' értéket adhatnak át a complete szolgáltatásnak.",
|
||||
"require_tag_scan_hint": "Ez a feladat csak az eszközön lévő NFC-címke vagy QR-kód beolvasásával fejezhető be — az itteni mentés el lesz utasítva."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vuoto = mezzanotte (default). Fuso orario HA.",
|
||||
"at_time": "alle",
|
||||
"notes_optional": "Note (opzionale)",
|
||||
"notes_markdown_hint": "Markdown è supportato — **grassetto**, elenchi, [link](…)",
|
||||
"cost_optional": "Costo (opzionale)",
|
||||
"duration_minutes": "Durata in minuti (opzionale)",
|
||||
"completed_at_optional": "Completato il (opzionale, vuoto = adesso)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Titolo pannello laterale",
|
||||
"settings_notifications": "Notifiche",
|
||||
"settings_notify_service": "Servizio di notifica",
|
||||
"settings_shopping_list": "Lista della spesa (attività di acquisto)",
|
||||
"settings_shopping_list_help": "I promemoria di acquisto per ricambi scarsi appaiono in questo elenco; spuntarne uno rifornisce il ricambio.",
|
||||
"shopping_list_none": "Disattivato — nessuna lista della spesa",
|
||||
"settings_install_assist_sentences": "Installa le frasi di Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia le frasi vocali nella tua configurazione affinché l'agente Assist classico le riconosca. Un file modificato da te non viene mai sovrascritto.",
|
||||
"test_notification": "Notifica di test",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Aggiungi passo",
|
||||
"phase_current": "Fase attuale",
|
||||
"phase_set": "Imposta come attuale",
|
||||
"chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)"
|
||||
"chart_history_fallback": "Nessuna statistica a lungo termine per questa entità — viene mostrata la cronologia degli stati del recorder (di solito gli ultimi ~10 giorni)",
|
||||
"chart_history_alarm": "Vista del trigger dallo storico stati: 1 = stato di allarme mantenuto per il tempo di attesa, 0 = ok (in genere gli ultimi ~10 giorni)",
|
||||
"chart_history_count": "Conteggio dei cambi ricostruito dallo storico stati dall'ultimo intervento (in genere gli ultimi ~10 giorni)",
|
||||
"prediction_cycles": "Appreso da cicli",
|
||||
"phase_require_override": "Sostituisci «Richiedi al completamento» per questa fase",
|
||||
"history_add_past": "Aggiungi un completamento passato",
|
||||
"require_tag_scan": "Completa solo scansionando il tag",
|
||||
"require_tag_scan_help": "Prova di presenza: «Fatto» è bloccato ovunque finché non viene scansionato il tag NFC o il codice QR sull'oggetto. Le automazioni possono passare 'via_tag_scan' al servizio complete.",
|
||||
"require_tag_scan_hint": "Questa attività si completa solo scansionando il suo tag NFC o codice QR sull'oggetto — il salvataggio qui verrà rifiutato."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "空欄=深夜0時(既定)。HAのタイムゾーン。",
|
||||
"at_time": "時刻",
|
||||
"notes_optional": "メモ(任意)",
|
||||
"notes_markdown_hint": "Markdownに対応 — **太字**、リスト、[リンク](…)",
|
||||
"cost_optional": "費用(任意)",
|
||||
"duration_minutes": "所要時間(分、任意)",
|
||||
"completed_at_optional": "完了日時(任意・空欄 = 現在)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "サイドバーパネルのタイトル",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知サービス",
|
||||
"settings_shopping_list": "買い物リスト(購入タスク)",
|
||||
"settings_shopping_list_help": "在庫が少ない部品の購入リマインダーがこのリストに表示され、チェックすると部品が補充されます。",
|
||||
"shopping_list_none": "オフ — 買い物リストなし",
|
||||
"settings_install_assist_sentences": "Assist の文を導入する",
|
||||
"settings_install_assist_sentences_hint": "音声フレーズを設定に複製し、従来の Assist エージェントが認識できるようにします。自分で編集したファイルが上書きされることはありません。",
|
||||
"test_notification": "テスト通知",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "ステップを追加",
|
||||
"phase_current": "現在のフェーズ",
|
||||
"phase_set": "現在に設定",
|
||||
"chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)"
|
||||
"chart_history_fallback": "このエンティティには長期統計がありません — レコーダーの状態履歴を表示しています(通常は直近約10日分)",
|
||||
"chart_history_alarm": "状態履歴からのトリガー表示:1 = 保持時間の間アラート状態が続いた、0 = 正常(通常は直近約10日)",
|
||||
"chart_history_count": "前回のサービス以降の状態履歴から再構築した変化回数(通常は直近約10日)",
|
||||
"prediction_cycles": "学習したサイクル数",
|
||||
"phase_require_override": "このフェーズで「完了時に必須」を上書き",
|
||||
"history_add_past": "過去の完了を追加",
|
||||
"require_tag_scan": "タグのスキャンでのみ完了可能",
|
||||
"require_tag_scan_help": "在席証明:対象物のNFCタグまたはQRコードをスキャンするまで、どの画面でも「完了」はブロックされます。オートメーションはcompleteサービスに 'via_tag_scan' を渡せます。",
|
||||
"require_tag_scan_hint": "このタスクは対象物のNFCタグまたはQRコードをスキャンしないと完了できません。ここでの保存は拒否されます。"
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "비워두면 자정(기본값)입니다. HA 시간대 기준.",
|
||||
"at_time": "시각",
|
||||
"notes_optional": "메모 (선택)",
|
||||
"notes_markdown_hint": "Markdown 지원 — **굵게**, 목록, [링크](…)",
|
||||
"cost_optional": "비용 (선택)",
|
||||
"duration_minutes": "소요 시간(분, 선택)",
|
||||
"completed_at_optional": "완료 시각 (선택, 비우면 지금)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "사이드바 패널 제목",
|
||||
"settings_notifications": "알림",
|
||||
"settings_notify_service": "알림 서비스",
|
||||
"settings_shopping_list": "쇼핑 목록 (구매 작업)",
|
||||
"settings_shopping_list_help": "부족한 부품의 구매 알림이 이 목록에 표시되며, 체크하면 부품이 재입고됩니다.",
|
||||
"shopping_list_none": "끄기 — 쇼핑 목록 없음",
|
||||
"settings_install_assist_sentences": "Assist 문장 설치",
|
||||
"settings_install_assist_sentences_hint": "음성 문장을 설정에 복사하여 기존 Assist 에이전트가 인식하도록 합니다. 직접 수정한 파일은 절대 덮어쓰지 않습니다.",
|
||||
"test_notification": "테스트 알림",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "스텝 추가",
|
||||
"phase_current": "현재 단계",
|
||||
"phase_set": "현재로 설정",
|
||||
"chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)"
|
||||
"chart_history_fallback": "이 엔티티에는 장기 통계가 없습니다 — 레코더 상태 기록을 표시합니다(보통 최근 약 10일)",
|
||||
"chart_history_alarm": "상태 기록 기반 트리거 보기: 1 = 유지 시간 동안 경보 상태 유지, 0 = 정상 (보통 최근 약 10일)",
|
||||
"chart_history_count": "마지막 서비스 이후 상태 기록으로 재구성한 변경 횟수 (보통 최근 약 10일)",
|
||||
"prediction_cycles": "학습한 주기 수",
|
||||
"phase_require_override": "이 단계에서 ‘완료 시 필수’ 재정의",
|
||||
"history_add_past": "지난 완료 추가",
|
||||
"require_tag_scan": "태그 스캔으로만 완료 가능",
|
||||
"require_tag_scan_help": "현장 확인: 기기의 NFC 태그나 QR 코드를 스캔할 때까지 모든 화면에서 '완료'가 차단됩니다. 자동화는 complete 서비스에 'via_tag_scan'을 전달할 수 있습니다.",
|
||||
"require_tag_scan_hint": "이 작업은 기기의 NFC 태그나 QR 코드를 스캔해야만 완료됩니다. 여기서 저장하면 거부됩니다."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Tom = midnatt (standard). HA-tidssone.",
|
||||
"at_time": "kl.",
|
||||
"notes_optional": "Notater (valgfritt)",
|
||||
"notes_markdown_hint": "Markdown støttes — **fet**, lister, [lenker](…)",
|
||||
"cost_optional": "Kostnad (valgfritt)",
|
||||
"duration_minutes": "Varighet i minutter (valgfritt)",
|
||||
"completed_at_optional": "Utført den (valgfritt, tomt = nå)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Tittel på sidepanel",
|
||||
"settings_notifications": "Varsler",
|
||||
"settings_notify_service": "Varslingstjeneste",
|
||||
"settings_shopping_list": "Handleliste (kjøpsoppgaver)",
|
||||
"settings_shopping_list_help": "Kjøpspåminnelser for deler med lav beholdning vises i denne listen; avkryssing fyller på beholdningen.",
|
||||
"shopping_list_none": "Av — ingen handleliste",
|
||||
"settings_install_assist_sentences": "Installer Assist-setninger",
|
||||
"settings_install_assist_sentences_hint": "Kopierer talesetningene til konfigurasjonen din slik at den klassiske Assist-agenten gjenkjenner dem. En fil du selv har endret, blir aldri overskrevet.",
|
||||
"test_notification": "Testvarsel",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Legg til trinn",
|
||||
"phase_current": "Gjeldende fase",
|
||||
"phase_set": "Sett som gjeldende",
|
||||
"chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)"
|
||||
"chart_history_fallback": "Ingen langtidsstatistikk for denne enheten — recorderens tilstandshistorikk vises (vanligvis de siste ~10 dagene)",
|
||||
"chart_history_alarm": "Utløservisning fra tilstandshistorikken: 1 = alarmtilstand holdt gjennom holdetiden, 0 = ok (vanligvis de siste ~10 dagene)",
|
||||
"chart_history_count": "Antall endringer gjenoppbygd fra tilstandshistorikken siden siste service (vanligvis de siste ~10 dagene)",
|
||||
"prediction_cycles": "Lært av sykluser",
|
||||
"phase_require_override": "Overstyr «Krev ved fullføring» for denne fasen",
|
||||
"history_add_past": "Legg til tidligere fullføring",
|
||||
"require_tag_scan": "Kan bare fullføres ved tag-skanning",
|
||||
"require_tag_scan_help": "Tilstedeværelsesbevis: «Fullført» er blokkert overalt til NFC-taggen eller QR-koden på selve tingen er skannet. Automatiseringer kan sende 'via_tag_scan' til complete-tjenesten.",
|
||||
"require_tag_scan_hint": "Denne oppgaven fullføres bare ved å skanne NFC-taggen eller QR-koden på selve tingen — lagring her vil bli avvist."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Leeg = middernacht (standaard). HA-tijdzone.",
|
||||
"at_time": "om",
|
||||
"notes_optional": "Notities (optioneel)",
|
||||
"notes_markdown_hint": "Markdown wordt ondersteund — **vet**, lijsten, [links](…)",
|
||||
"cost_optional": "Kosten (optioneel)",
|
||||
"duration_minutes": "Duur in minuten (optioneel)",
|
||||
"completed_at_optional": "Voltooid op (optioneel, leeg = nu)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Titel zijbalkpaneel",
|
||||
"settings_notifications": "Meldingen",
|
||||
"settings_notify_service": "Meldingsservice",
|
||||
"settings_shopping_list": "Boodschappenlijst (kooptaken)",
|
||||
"settings_shopping_list_help": "Koopherinneringen voor schaarse onderdelen verschijnen in deze lijst; afvinken vult de voorraad aan.",
|
||||
"shopping_list_none": "Uit — geen boodschappenlijst",
|
||||
"settings_install_assist_sentences": "Assist-zinnen installeren",
|
||||
"settings_install_assist_sentences_hint": "Kopieert de spraakzinnen naar je configuratie zodat de klassieke Assist-agent ze herkent. Een bestand dat je zelf hebt bewerkt wordt nooit overschreven.",
|
||||
"test_notification": "Testmelding",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Stap toevoegen",
|
||||
"phase_current": "Huidige fase",
|
||||
"phase_set": "Instellen als huidige",
|
||||
"chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)"
|
||||
"chart_history_fallback": "Geen langetermijnstatistieken voor deze entiteit — de recorder-statusgeschiedenis wordt getoond (meestal de laatste ~10 dagen)",
|
||||
"chart_history_alarm": "Triggerweergave uit de statusgeschiedenis: 1 = alarmstatus gedurende de wachttijd aangehouden, 0 = in orde (meestal de laatste ~10 dagen)",
|
||||
"chart_history_count": "Aantal wisselingen opnieuw opgebouwd uit de statusgeschiedenis sinds de laatste service (meestal de laatste ~10 dagen)",
|
||||
"prediction_cycles": "Geleerd uit cycli",
|
||||
"phase_require_override": "„Vereisen bij afronden” voor deze fase overschrijven",
|
||||
"history_add_past": "Eerdere afronding toevoegen",
|
||||
"require_tag_scan": "Alleen af te ronden via tag-scan",
|
||||
"require_tag_scan_help": "Aanwezigheidsbewijs: „Klaar” is overal geblokkeerd totdat de NFC-tag of QR-code op het apparaat zelf is gescand. Automatiseringen kunnen 'via_tag_scan' aan de complete-service meegeven.",
|
||||
"require_tag_scan_hint": "Deze taak wordt alleen afgerond door de NFC-tag of QR-code op het apparaat te scannen — opslaan hier wordt geweigerd."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Puste = północ (domyślnie). Strefa czasowa HA.",
|
||||
"at_time": "o",
|
||||
"notes_optional": "Notatki (opcjonalne)",
|
||||
"notes_markdown_hint": "Obsługiwany jest Markdown — **pogrubienie**, listy, [linki](…)",
|
||||
"cost_optional": "Koszt (opcjonalne)",
|
||||
"duration_minutes": "Czas trwania w minutach (opcjonalne)",
|
||||
"completed_at_optional": "Wykonano dnia (opcjonalne, puste = teraz)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Tytuł panelu bocznego",
|
||||
"settings_notifications": "Powiadomienia",
|
||||
"settings_notify_service": "Usługa powiadomień",
|
||||
"settings_shopping_list": "Lista zakupów (zadania zakupowe)",
|
||||
"settings_shopping_list_help": "Przypomnienia o zakupie brakujących części pojawiają się na tej liście; odhaczenie uzupełnia zapas.",
|
||||
"shopping_list_none": "Wyłączone — brak listy zakupów",
|
||||
"settings_install_assist_sentences": "Zainstaluj zdania Assist",
|
||||
"settings_install_assist_sentences_hint": "Kopiuje zdania głosowe do Twojej konfiguracji, aby klasyczny agent Assist je rozpoznawał. Plik zmieniony przez Ciebie nigdy nie zostanie nadpisany.",
|
||||
"test_notification": "Powiadomienie testowe",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Dodaj krok",
|
||||
"phase_current": "Bieżąca faza",
|
||||
"phase_set": "Ustaw jako bieżącą",
|
||||
"chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)"
|
||||
"chart_history_fallback": "Brak statystyk długoterminowych dla tej encji — wyświetlana jest historia stanów recordera (zwykle ostatnie ~10 dni)",
|
||||
"chart_history_alarm": "Widok wyzwalacza z historii stanów: 1 = stan alarmu utrzymany przez czas podtrzymania, 0 = w porządku (zwykle ostatnie ~10 dni)",
|
||||
"chart_history_count": "Licznik zmian odtworzony z historii stanów od ostatniego serwisu (zwykle ostatnie ~10 dni)",
|
||||
"prediction_cycles": "Nauczone z cykli",
|
||||
"phase_require_override": "Nadpisz „Wymagaj przy ukończeniu” dla tej fazy",
|
||||
"history_add_past": "Dodaj wcześniejsze ukończenie",
|
||||
"require_tag_scan": "Ukończenie tylko przez skan tagu",
|
||||
"require_tag_scan_help": "Dowód obecności: „Gotowe” jest wszędzie zablokowane, dopóki nie zeskanujesz tagu NFC lub kodu QR na urządzeniu. Automatyzacje mogą przekazać 'via_tag_scan' do usługi complete.",
|
||||
"require_tag_scan_hint": "To zadanie można ukończyć tylko skanując jego tag NFC lub kod QR na urządzeniu — zapis tutaj zostanie odrzucony."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Vazio = meia-noite (padrão). Fuso horário do HA.",
|
||||
"at_time": "às",
|
||||
"notes_optional": "Observações (opcional)",
|
||||
"notes_markdown_hint": "Markdown é suportado — **negrito**, listas, [links](…)",
|
||||
"cost_optional": "Custo (opcional)",
|
||||
"duration_minutes": "Duração em minutos (opcional)",
|
||||
"completed_at_optional": "Concluído em (opcional, vazio = agora)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Título do painel na barra lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_shopping_list": "Lista de compras (tarefas de compra)",
|
||||
"settings_shopping_list_help": "Os lembretes de compra de peças em falta aparecem nesta lista; marcar um repõe a peça.",
|
||||
"shopping_list_none": "Desligado — sem lista de compras",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um arquivo que você editou nunca é sobrescrito.",
|
||||
"test_notification": "Notificação de teste",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Adicionar passo",
|
||||
"phase_current": "Fase atual",
|
||||
"phase_set": "Definir como atual",
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)"
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — mostrando o histórico de estados do recorder (normalmente os últimos ~10 dias)",
|
||||
"chart_history_alarm": "Visão do gatilho a partir do histórico de estados: 1 = estado de alerta mantido pelo tempo de espera, 0 = normal (normalmente os últimos ~10 dias)",
|
||||
"chart_history_count": "Contagem de mudanças reconstruída a partir do histórico de estados desde o último serviço (normalmente os últimos ~10 dias)",
|
||||
"prediction_cycles": "Aprendido de ciclos",
|
||||
"phase_require_override": "Substituir “Exigir na conclusão” para esta fase",
|
||||
"history_add_past": "Adicionar conclusão passada",
|
||||
"require_tag_scan": "Concluir apenas escaneando a etiqueta",
|
||||
"require_tag_scan_help": "Prova de presença: “Concluído” fica bloqueado em toda parte até escanear a etiqueta NFC ou o código QR no próprio objeto. Automações podem passar 'via_tag_scan' ao serviço complete.",
|
||||
"require_tag_scan_hint": "Esta tarefa só é concluída escaneando sua etiqueta NFC ou código QR no próprio objeto — salvar aqui será recusado."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Vazio = meia-noite (padrão). Fuso horário HA.",
|
||||
"at_time": "às",
|
||||
"notes_optional": "Notas (opcional)",
|
||||
"notes_markdown_hint": "Markdown é suportado — **negrito**, listas, [ligações](…)",
|
||||
"cost_optional": "Custo (opcional)",
|
||||
"duration_minutes": "Duração em minutos (opcional)",
|
||||
"completed_at_optional": "Concluído em (opcional, vazio = agora)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Título do painel lateral",
|
||||
"settings_notifications": "Notificações",
|
||||
"settings_notify_service": "Serviço de notificação",
|
||||
"settings_shopping_list": "Lista de compras (tarefas de compra)",
|
||||
"settings_shopping_list_help": "Os lembretes de compra de peças em falta aparecem nesta lista; marcar um repõe a peça.",
|
||||
"shopping_list_none": "Desligado — sem lista de compras",
|
||||
"settings_install_assist_sentences": "Instalar frases do Assist",
|
||||
"settings_install_assist_sentences_hint": "Copia as frases de voz para a sua configuração para que o agente Assist clássico as reconheça. Um ficheiro que editou nunca é substituído.",
|
||||
"test_notification": "Notificação de teste",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Adicionar passo",
|
||||
"phase_current": "Fase atual",
|
||||
"phase_set": "Definir como atual",
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)"
|
||||
"chart_history_fallback": "Sem estatísticas de longo prazo para esta entidade — a mostrar o histórico de estados do recorder (normalmente os últimos ~10 dias)",
|
||||
"chart_history_alarm": "Vista do disparador a partir do histórico de estados: 1 = estado de alerta mantido pelo tempo de espera, 0 = normal (normalmente os últimos ~10 dias)",
|
||||
"chart_history_count": "Contagem de mudanças reconstruída a partir do histórico de estados desde o último serviço (normalmente os últimos ~10 dias)",
|
||||
"prediction_cycles": "Aprendido de ciclos",
|
||||
"phase_require_override": "Substituir «Exigir na conclusão» para esta fase",
|
||||
"history_add_past": "Adicionar conclusão passada",
|
||||
"require_tag_scan": "Concluir apenas lendo a etiqueta",
|
||||
"require_tag_scan_help": "Prova de presença: «Concluído» fica bloqueado em todo o lado até ler a etiqueta NFC ou o código QR no próprio objeto. As automações podem passar 'via_tag_scan' ao serviço complete.",
|
||||
"require_tag_scan_hint": "Esta tarefa só é concluída lendo a sua etiqueta NFC ou código QR no próprio objeto — guardar aqui será recusado."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Пусто = полночь (по умолчанию). Часовой пояс HA.",
|
||||
"at_time": "в",
|
||||
"notes_optional": "Примечания (опционально)",
|
||||
"notes_markdown_hint": "Поддерживается Markdown — **жирный**, списки, [ссылки](…)",
|
||||
"cost_optional": "Стоимость (опционально)",
|
||||
"duration_minutes": "Длительность в минутах (опционально)",
|
||||
"completed_at_optional": "Выполнено (необязательно, пусто = сейчас)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Заголовок панели",
|
||||
"settings_notifications": "Уведомления",
|
||||
"settings_notify_service": "Сервис уведомлений",
|
||||
"settings_shopping_list": "Список покупок (задачи покупки)",
|
||||
"settings_shopping_list_help": "Напоминания о покупке заканчивающихся деталей появляются в этом списке; отметка пополняет запас.",
|
||||
"shopping_list_none": "Выкл. — без списка покупок",
|
||||
"settings_install_assist_sentences": "Установить фразы Assist",
|
||||
"settings_install_assist_sentences_hint": "Копирует голосовые фразы в вашу конфигурацию, чтобы классический агент Assist их распознавал. Файл, который вы изменили, никогда не перезаписывается.",
|
||||
"test_notification": "Тестовое уведомление",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Добавить шаг",
|
||||
"phase_current": "Текущая фаза",
|
||||
"phase_set": "Сделать текущей",
|
||||
"chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)"
|
||||
"chart_history_fallback": "Для этой сущности нет долгосрочной статистики — показана история состояний recorder (обычно последние ~10 дней)",
|
||||
"chart_history_alarm": "Вид триггера из истории состояний: 1 = состояние тревоги удерживалось в течение времени удержания, 0 = норма (обычно последние ~10 дней)",
|
||||
"chart_history_count": "Счётчик изменений восстановлен из истории состояний с момента последнего обслуживания (обычно последние ~10 дней)",
|
||||
"prediction_cycles": "Изучено по циклам",
|
||||
"phase_require_override": "Переопределить «Требовать при выполнении» для этой фазы",
|
||||
"history_add_past": "Добавить прошлое выполнение",
|
||||
"require_tag_scan": "Завершение только по скану метки",
|
||||
"require_tag_scan_help": "Подтверждение присутствия: «Готово» заблокировано везде, пока не отсканирована NFC-метка или QR-код на самом устройстве. Автоматизации могут передавать 'via_tag_scan' в службу complete.",
|
||||
"require_tag_scan_hint": "Эта задача завершается только сканированием её NFC-метки или QR-кода на самом устройстве — сохранение здесь будет отклонено."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Tomt = midnatt (standard). HA-tidszon.",
|
||||
"at_time": "kl.",
|
||||
"notes_optional": "Anteckningar (valfritt)",
|
||||
"notes_markdown_hint": "Markdown stöds — **fetstil**, listor, [länkar](…)",
|
||||
"cost_optional": "Kostnad (valfritt)",
|
||||
"duration_minutes": "Varaktighet i minuter (valfritt)",
|
||||
"completed_at_optional": "Utförd den (valfritt, tomt = nu)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Sidopanelens titel",
|
||||
"settings_notifications": "Notifikationer",
|
||||
"settings_notify_service": "Notifikationstjänst",
|
||||
"settings_shopping_list": "Inköpslista (köpuppgifter)",
|
||||
"settings_shopping_list_help": "Köppåminnelser för delar som tar slut visas i denna lista; att bocka av en fyller på lagret.",
|
||||
"shopping_list_none": "Av — ingen inköpslista",
|
||||
"settings_install_assist_sentences": "Installera Assist-meningar",
|
||||
"settings_install_assist_sentences_hint": "Kopierar rösmeningarna till din konfiguration så att den klassiska Assist-agenten känner igen dem. En fil du själv har ändrat skrivs aldrig över.",
|
||||
"test_notification": "Testnotifikation",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Lägg till steg",
|
||||
"phase_current": "Aktuell fas",
|
||||
"phase_set": "Ange som aktuell",
|
||||
"chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)"
|
||||
"chart_history_fallback": "Ingen långtidsstatistik för denna entitet — recorderns tillståndshistorik visas (vanligtvis de senaste ~10 dagarna)",
|
||||
"chart_history_alarm": "Utlösarvy från tillståndshistoriken: 1 = larmläge hållet under hålltiden, 0 = ok (vanligtvis de senaste ~10 dagarna)",
|
||||
"chart_history_count": "Antal ändringar återskapat från tillståndshistoriken sedan senaste service (vanligtvis de senaste ~10 dagarna)",
|
||||
"prediction_cycles": "Lärt av cykler",
|
||||
"phase_require_override": "Åsidosätt ”Kräv vid slutförande” för denna fas",
|
||||
"history_add_past": "Lägg till tidigare slutförande",
|
||||
"require_tag_scan": "Kan endast slutföras via tagg-skanning",
|
||||
"require_tag_scan_help": "Närvarobevis: ”Klar” är blockerad överallt tills NFC-taggen eller QR-koden på själva saken har skannats. Automationer kan skicka 'via_tag_scan' till complete-tjänsten.",
|
||||
"require_tag_scan_hint": "Den här uppgiften slutförs endast genom att skanna dess NFC-tagg eller QR-kod på själva saken — att spara här kommer att nekas."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "Boş = gece yarısı (varsayılan). HA saat dilimi.",
|
||||
"at_time": "saat",
|
||||
"notes_optional": "Notlar (isteğe bağlı)",
|
||||
"notes_markdown_hint": "Markdown desteklenir — **kalın**, listeler, [bağlantılar](…)",
|
||||
"cost_optional": "Maliyet (isteğe bağlı)",
|
||||
"duration_minutes": "Dakika cinsinden süre (isteğe bağlı)",
|
||||
"completed_at_optional": "Tamamlanma zamanı (isteğe bağlı, boş = şimdi)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "Kenar çubuğu paneli başlığı",
|
||||
"settings_notifications": "Bildirimler",
|
||||
"settings_notify_service": "Bildirim servisi",
|
||||
"settings_shopping_list": "Alışveriş listesi (satın alma görevleri)",
|
||||
"settings_shopping_list_help": "Azalan parçalar için satın alma hatırlatıcıları bu listede görünür; işaretlemek parçayı yeniden stoklar.",
|
||||
"shopping_list_none": "Kapalı — alışveriş listesi yok",
|
||||
"settings_install_assist_sentences": "Assist cümlelerini yükle",
|
||||
"settings_install_assist_sentences_hint": "Sesli komut cümlelerini yapılandırmanıza kopyalar, böylece klasik Assist aracısı bunları tanır. Kendi düzenlediğiniz bir dosyanın üzerine asla yazılmaz.",
|
||||
"test_notification": "Test bildirimi",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Adım ekle",
|
||||
"phase_current": "Geçerli aşama",
|
||||
"phase_set": "Geçerli olarak ayarla",
|
||||
"chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)"
|
||||
"chart_history_fallback": "Bu varlık için uzun dönem istatistik yok — recorder durum geçmişi gösteriliyor (genellikle son ~10 gün)",
|
||||
"chart_history_alarm": "Durum geçmişinden tetikleyici görünümü: 1 = uyarı durumu bekletme süresi boyunca sürdü, 0 = normal (genellikle son ~10 gün)",
|
||||
"chart_history_count": "Değişim sayısı son servisten bu yana durum geçmişinden yeniden oluşturuldu (genellikle son ~10 gün)",
|
||||
"prediction_cycles": "Döngülerden öğrenildi",
|
||||
"phase_require_override": "Bu aşama için “Tamamlarken zorunlu tut” ayarını geçersiz kıl",
|
||||
"history_add_past": "Geçmiş tamamlama ekle",
|
||||
"require_tag_scan": "Yalnızca etiket taranarak tamamlanır",
|
||||
"require_tag_scan_help": "Yerinde olma kanıtı: Cihazın üzerindeki NFC etiketi veya QR kodu taranana kadar 'Tamamlandı' her yerde engellenir. Otomasyonlar complete servisine 'via_tag_scan' iletebilir.",
|
||||
"require_tag_scan_hint": "Bu görev yalnızca cihazın üzerindeki NFC etiketi veya QR kodu taranarak tamamlanır — buradan kaydetme reddedilecektir."
|
||||
}
|
||||
@@ -126,6 +126,7 @@
|
||||
"schedule_time_help": "Порожньо = опівночі (за замовчуванням). Часовий пояс HA.",
|
||||
"at_time": "о",
|
||||
"notes_optional": "Примітки (необов'язково)",
|
||||
"notes_markdown_hint": "Підтримується Markdown — **жирний**, списки, [посилання](…)",
|
||||
"cost_optional": "Вартість (необов'язково)",
|
||||
"duration_minutes": "Тривалість у хвилинах (необов'язково)",
|
||||
"completed_at_optional": "Виконано (необов'язково, порожньо = зараз)",
|
||||
@@ -451,6 +452,9 @@
|
||||
"settings_panel_title": "Заголовок панелі",
|
||||
"settings_notifications": "Сповіщення",
|
||||
"settings_notify_service": "Служба сповіщень",
|
||||
"settings_shopping_list": "Список покупок (завдання покупки)",
|
||||
"settings_shopping_list_help": "Нагадування про купівлю деталей, що закінчуються, з'являються в цьому списку; відмітка поповнює запас.",
|
||||
"shopping_list_none": "Вимк. — без списку покупок",
|
||||
"settings_install_assist_sentences": "Встановити фрази Assist",
|
||||
"settings_install_assist_sentences_hint": "Копіює голосові фрази до вашої конфігурації, щоб класичний агент Assist їх розпізнавав. Файл, який ви редагували, ніколи не перезаписується.",
|
||||
"test_notification": "Тестове сповіщення",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "Додати крок",
|
||||
"phase_current": "Поточна фаза",
|
||||
"phase_set": "Зробити поточною",
|
||||
"chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)"
|
||||
"chart_history_fallback": "Для цієї сутності немає довгострокової статистики — показано історію станів recorder (зазвичай останні ~10 днів)",
|
||||
"chart_history_alarm": "Вигляд тригера з історії станів: 1 = стан тривоги утримувався протягом часу утримання, 0 = норма (зазвичай останні ~10 днів)",
|
||||
"chart_history_count": "Лічильник змін відновлено з історії станів від останнього обслуговування (зазвичай останні ~10 днів)",
|
||||
"prediction_cycles": "Вивчено з циклів",
|
||||
"phase_require_override": "Перевизначити «Вимагати при виконанні» для цієї фази",
|
||||
"history_add_past": "Додати минуле виконання",
|
||||
"require_tag_scan": "Завершення лише скануванням мітки",
|
||||
"require_tag_scan_help": "Підтвердження присутності: «Готово» заблоковано всюди, доки не відскановано NFC-мітку або QR-код на самому пристрої. Автоматизації можуть передати 'via_tag_scan' службі complete.",
|
||||
"require_tag_scan_hint": "Це завдання завершується лише скануванням його NFC-мітки або QR-коду на самому пристрої — збереження тут буде відхилено."
|
||||
}
|
||||
@@ -128,6 +128,7 @@
|
||||
"schedule_time_help": "留空 = 午夜 (默认)。基于 HA 时区。",
|
||||
"at_time": "于",
|
||||
"notes_optional": "备注 (可选)",
|
||||
"notes_markdown_hint": "支持 Markdown — **加粗**、列表、[链接](…)",
|
||||
"cost_optional": "成本 (可选)",
|
||||
"duration_minutes": "耗时 (分钟, 可选)",
|
||||
"completed_at_optional": "完成时间(可选,留空 = 现在)",
|
||||
@@ -453,6 +454,9 @@
|
||||
"settings_panel_title": "侧边栏面板标题",
|
||||
"settings_notifications": "通知",
|
||||
"settings_notify_service": "通知服务",
|
||||
"settings_shopping_list": "购物清单(购买任务)",
|
||||
"settings_shopping_list_help": "库存不足部件的购买提醒会显示在此清单中;勾选即可补充库存。",
|
||||
"shopping_list_none": "关闭 — 无购物清单",
|
||||
"settings_install_assist_sentences": "安装 Assist 语句",
|
||||
"settings_install_assist_sentences_hint": "将语音语句复制到你的配置中,让传统 Assist 代理能够识别它们。你自己修改过的文件永远不会被覆盖。",
|
||||
"test_notification": "测试通知",
|
||||
@@ -898,5 +902,13 @@
|
||||
"phase_sequence_add_step": "添加步骤",
|
||||
"phase_current": "当前阶段",
|
||||
"phase_set": "设为当前",
|
||||
"chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)"
|
||||
"chart_history_fallback": "该实体没有长期统计数据——显示 recorder 状态历史(通常为最近约 10 天)",
|
||||
"chart_history_alarm": "来自状态历史的触发视图:1 = 警报状态保持了保持时间,0 = 正常(通常为最近约 10 天)",
|
||||
"chart_history_count": "根据上次维护以来的状态历史重建的变化次数(通常为最近约 10 天)",
|
||||
"prediction_cycles": "已学习的周期数",
|
||||
"phase_require_override": "为此阶段覆盖“完成时要求”",
|
||||
"history_add_past": "补录过去的完成记录",
|
||||
"require_tag_scan": "仅可通过扫描标签完成",
|
||||
"require_tag_scan_help": "在场证明:在扫描设备上的 NFC 标签或二维码之前,所有界面的“完成”均被阻止。自动化可向 complete 服务传递 'via_tag_scan'。",
|
||||
"require_tag_scan_hint": "此任务只能通过扫描设备上的 NFC 标签或二维码来完成——在此保存将被拒绝。"
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
/*! maintenance_supporter frontend 2.65.0 */
|
||||
var $t=Object.defineProperty;var ts=Object.getOwnPropertyDescriptor;var w=(r,s,e)=>()=>{if(e)throw e[0];try{return r&&(s=r(r=0)),s}catch(t){throw e=[t],t}};var is=(r,s)=>{for(var e in s)$t(r,e,{get:s[e],enumerable:!0})};var c=(r,s,e,t)=>{for(var i=t>1?void 0:t?ts(s,e):s,n=r.length-1,o;n>=0;n--)(o=r[n])&&(i=(t?o(s,e,i):o(i))||i);return t&&i&&$t(s,e,i),i};var je,Ne,Xe,kt,be,Et,S,St,et,tt=w(()=>{je=globalThis,Ne=je.ShadowRoot&&(je.ShadyCSS===void 0||je.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Xe=Symbol(),kt=new WeakMap,be=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Xe)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=s,this.t=e}get styleSheet(){let s=this.o,e=this.t;if(Ne&&s===void 0){let t=e!==void 0&&e.length===1;t&&(s=kt.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&kt.set(e,s))}return s}toString(){return this.cssText}},Et=r=>new be(typeof r=="string"?r:r+"",void 0,Xe),S=(r,...s)=>{let e=r.length===1?r[0]:s.reduce((t,i,n)=>t+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+r[n+1],r[0]);return new be(e,r,Xe)},St=(r,s)=>{if(Ne)r.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of s){let t=document.createElement("style"),i=je.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,r.appendChild(t)}},et=Ne?r=>r:r=>r instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return Et(e)})(r):r});var ss,rs,as,ns,os,ls,He,At,cs,ds,xe,we,De,Tt,G,$e=w(()=>{tt();tt();({is:ss,defineProperty:rs,getOwnPropertyDescriptor:as,getOwnPropertyNames:ns,getOwnPropertySymbols:os,getPrototypeOf:ls}=Object),He=globalThis,At=He.trustedTypes,cs=At?At.emptyScript:"",ds=He.reactiveElementPolyfillSupport,xe=(r,s)=>r,we={toAttribute(r,s){switch(s){case Boolean:r=r?cs:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,s){let e=r;switch(s){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},De=(r,s)=>!ss(r,s),Tt={attribute:!0,type:String,converter:we,reflect:!1,useDefault:!1,hasChanged:De};Symbol.metadata??=Symbol("metadata"),He.litPropertyMetadata??=new WeakMap;G=class extends HTMLElement{static addInitializer(s){this._$Ei(),(this.l??=[]).push(s)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(s,e=Tt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(s)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(s,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(s,t,e);i!==void 0&&rs(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=as(this.prototype,s)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let p=i?.call(this);n?.call(this,o),this.requestUpdate(s,p,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??Tt}static _$Ei(){if(this.hasOwnProperty(xe("elementProperties")))return;let s=ls(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(xe("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(xe("properties"))){let e=this.properties,t=[...ns(e),...os(e)];for(let i of t)this.createProperty(i,e[i])}let s=this[Symbol.metadata];if(s!==null){let e=litPropertyMetadata.get(s);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(s){let e=[];if(Array.isArray(s)){let t=new Set(s.flat(1/0).reverse());for(let i of t)e.unshift(et(i))}else s!==void 0&&e.push(et(s));return e}static _$Eu(s,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof s=="string"?s.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(s=>this.enableUpdating=s),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(s=>s(this))}addController(s){(this._$EO??=new Set).add(s),this.renderRoot!==void 0&&this.isConnected&&s.hostConnected?.()}removeController(s){this._$EO?.delete(s)}_$E_(){let s=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(s.set(t,this[t]),delete this[t]);s.size>0&&(this._$Ep=s)}createRenderRoot(){let s=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return St(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=thiLine truncated
|
||||
\f\r]`,ke=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Pt=/-->/g,Lt=/>/g,ie=RegExp(`>|${it}(?:([^\\s"'>=/]+)(${it}*=${it}*(?:[^
|
||||
\f\r"'\`<>=]|("|')|))|$)`,"g"),Rt=/'/g,qt=/"/g,Dt=/^(?:script|style|textarea|title)$/i,dt=r=>(s,...e)=>({_$litType$:r,strings:s,values:e}),l=dt(1),_e=dt(2),vr=dt(3),ae=Symbol.for("lit-noChange"),_=Symbol.for("lit-nothing"),jt=new WeakMap,se=re.createTreeWalker(re,129);hs=(r,s)=>{let e=r.length-1,t=[],i,n=s===2?"<svg>":s===3?"<math>":"",o=ke;for(let p=0;p<e;p++){let d=r[p],u,m,f=-1,v=0;for(;v<d.length&&(o.lastIndex=v,m=o.exec(d),m!==null);)v=o.lastIndex,o===ke?m[1]==="!--"?o=Pt:m[1]!==void 0?o=Lt:m[2]!==void 0?(Dt.test(m[2])&&(i=RegExp("</"+m[2],"g")),o=ie):m[3]!==void 0&&(o=ie):o===ie?m[0]===">"?(o=i??ke,f=-1):m[1]===void 0?f=-2:(f=o.lastIndex-m[2].length,u=m[1],o=m[3]===void 0?ie:m[3]==='"'?qt:Rt):o===qt||o===Rt?o=ie:o===Pt||o===Lt?o=ke:(o=ie,i=void 0);let b=o===ie&&r[p+1].startsWith("/>")?" ":"";n+=o===ke?d+ps:f>=0?(t.push(u),d.slice(0,f)+Nt+d.slice(f)+ee+b):d+ee+(f===-2?p:b)}return[Mt(r,n+(r[e]||"<?>")+(s===2?"</svg>":s===3?"</math>":"")),t]},Ae=class r{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,o=0,p=s.length-1,d=this.parts,[u,m]=hs(s,e);if(this.el=r.createElement(u,t),se.currentNode=this.el.content,e===2||e===3){let f=this.el.content.firstChild;f.replaceWith(...f.childNodes)}for(;(i=se.nextNode())!==null&&d.length<p;){if(i.nodeType===1){if(i.hasAttributes())for(let f of i.getAttributeNames())if(f.endsWith(Nt)){let v=m[o++],b=i.getAttribute(f).split(ee),k=/([.?@])?(.*)/.exec(v);d.push({type:1,index:n,name:k[2],strings:b,ctor:k[1]==="."?rt:k[1]==="?"?at:k[1]==="@"?nt:he}),i.removeAttribute(f)}else f.startsWith(ee)&&(d.push({type:6,index:n}),i.removeAttribute(f));if(Dt.test(i.tagName)){let f=i.textContent.split(ee),v=f.length-1;if(v>0){i.textContent=Me?Me.emptyScript:"";for(let b=0;b<v;b++)i.append(f[b],Ee()),se.nextNode(),d.push({type:2,index:++n});i.append(f[v],Ee())}}}else if(i.nodeType===8)if(i.data===Ht)d.push({type:2,index:n});else{let f=-1;for(;(f=i.data.indexOf(ee,f+1))!==-1;)d.push({type:7,index:n}),f+=ee.length-1}n++}}static createElement(s,e){let t=re.createElement("template");return t.innerHTML=s,t}};st=class{constructor(s,e){this._$AV=[],this._$AN=void 0,this._$AD=s,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(s){let{el:{content:e},parts:t}=this._$AD,i=(s?.creationScope??re).importNode(e,!0);se.currentNode=i;let n=se.nextNode(),o=0,p=0,d=t[0];for(;d!==void 0;){if(o===d.index){let u;d.type===2?u=new Te(n,n.nextSibling,this,s):d.type===1?u=new d.ctor(n,d.name,d.strings,this,s):d.type===6&&(u=new ot(n,this,s)),this._$AV.push(u),d=t[++p]}o!==d?.index&&(n=se.nextNode(),o++)}return se.currentNode=re,i}p(s){let e=0;for(let t of this._$AV)t!==void 0&&(t.strings!==void 0?(t._$AI(s,t,e),e+=t.strings.length-2):t._$AI(s[e])),e++}},Te=class r{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(s,e,t,i){this.type=2,this._$AH=_,this._$AN=void 0,this._$AA=s,this._$AB=e,this._$AM=t,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let s=this._$AA.parentNode,e=this._$AM;return e!==void 0&&s?.nodeType===11&&(s=e.parentNode),s}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(s,e=this){s=ue(this,s,e),Se(s)?s===_||s==null||s===""?(this._$AH!==_&&this._$AR(),this._$AH=_):s!==this._$AH&&s!==ae&&this._(s):s._$litType$!==void 0?this.$(s):s.nodeType!==void 0?this.T(s):us(s)?this.k(s):this._(s)}O(s){return this._$AA.parentNode.insertBefore(s,this._$AB)}T(s){this._$AH!==s&&(this._$AR(),this._$AH=this.O(s))}_(s){this._$AH!==_&&Se(this._$AH)?this._$AA.nextSibling.data=s:this.T(re.createTextNode(s)),this._$AH=s}$(s){let{values:e,_$litType$:t}=s,i=typeof t=="number"?this._$AC(s):(t.el===void 0&&(t.el=Ae.createElement(Mt(t.h,t.h[0]),this.options)),t);if(this._$AH?._$AD===i)this._$AH.p(e);else{let n=new st(i,this),o=n.u(this.options);n.p(e),this.T(o),this._$AH=n}}_$AC(s){let e=jt.get(s.strings);return e===void 0&&jt.set(s.strings,e=new Ae(s)),e}k(s){ct(this._$AH)||(this._$AH=[],this._$AR());let e=this._$AH,t,i=0;for(let n of s)i===e.length?e.push(t=new r(this.O(Ee()),this.O(Ee()),this,this.options)):t=e[i],t._$AI(n),i++;i<e.length&&(this._$AR(t&&t._$AB.nextSibling,i),e.length=i)}_$AR(s=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);s!==this._$AB;){let t=Ct(s).nextSibling;Ct(s).remove(),s=t}}setConnected(s){this._$AM===void 0&&(this._$Cv=s,this._$AP?.(s))}},he=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(s,e,t,i,n){this.type=1,this._$AH=_,this._$AN=void 0,this.element=s,this.name=e,this._$AM=i,this.options=n,t.length>2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=_}_$AI(s,e=this,t,i){let n=this.strings,o=!1;if(n===void 0)s=ue(this,s,e,0),o=!Se(s)||s!==this._$AH&&s!==ae,o&&(this._$AH=s);else{let p=s,d,u;for(s=n[0],d=0;d<n.length-1;d++)u=ue(this,p[t+d],e,d),u===ae&&(u=this._$AH[d]),o||=!Se(u)||u!==this._$AH[d],u===_?s=_:s!==_&&(s+=(u??"")+n[d+1]),this._$AH[d]=u}o&&!i&&this.j(s)}j(s){s===_?this.element.removeAttribute(this.naLine truncated
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
var St=Object.defineProperty;var cs=Object.getOwnPropertyDescriptor;var w=(r,s,e)=>()=>{if(e)throw e[0];try{return r&&(s=r(r=0)),s}catch(t){throw e=[t],t}};var ds=(r,s)=>{for(var e in s)St(r,e,{get:s[e],enumerable:!0})};var c=(r,s,e,t)=>{for(var i=t>1?void 0:t?cs(s,e):s,n=r.length-1,o;n>=0;n--)(o=r[n])&&(i=(t?o(s,e,i):o(i))||i);return t&&i&&St(s,e,i),i};var De,Me,Xe,At,xe,Tt,S,It,et,tt=w(()=>{De=globalThis,Me=De.ShadowRoot&&(De.ShadyCSS===void 0||De.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,Xe=Symbol(),At=new WeakMap,xe=class{constructor(s,e,t){if(this._$cssResult$=!0,t!==Xe)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=s,this.t=e}get styleSheet(){let s=this.o,e=this.t;if(Me&&s===void 0){let t=e!==void 0&&e.length===1;t&&(s=At.get(e)),s===void 0&&((this.o=s=new CSSStyleSheet).replaceSync(this.cssText),t&&At.set(e,s))}return s}toString(){return this.cssText}},Tt=r=>new xe(typeof r=="string"?r:r+"",void 0,Xe),S=(r,...s)=>{let e=r.length===1?r[0]:s.reduce((t,i,n)=>t+(o=>{if(o._$cssResult$===!0)return o.cssText;if(typeof o=="number")return o;throw Error("Value passed to 'css' function must be a 'css' function result: "+o+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+r[n+1],r[0]);return new xe(e,r,Xe)},It=(r,s)=>{if(Me)r.adoptedStyleSheets=s.map(e=>e instanceof CSSStyleSheet?e:e.styleSheet);else for(let e of s){let t=document.createElement("style"),i=De.litNonce;i!==void 0&&t.setAttribute("nonce",i),t.textContent=e.cssText,r.appendChild(t)}},et=Me?r=>r:r=>r instanceof CSSStyleSheet?(s=>{let e="";for(let t of s.cssRules)e+=t.cssText;return Tt(e)})(r):r});var ps,us,hs,_s,gs,ms,Oe,Ct,fs,vs,we,$e,Fe,Pt,Y,ke=w(()=>{tt();tt();({is:ps,defineProperty:us,getOwnPropertyDescriptor:hs,getOwnPropertyNames:_s,getOwnPropertySymbols:gs,getPrototypeOf:ms}=Object),Oe=globalThis,Ct=Oe.trustedTypes,fs=Ct?Ct.emptyScript:"",vs=Oe.reactiveElementPolyfillSupport,we=(r,s)=>r,$e={toAttribute(r,s){switch(s){case Boolean:r=r?fs:null;break;case Object:case Array:r=r==null?r:JSON.stringify(r)}return r},fromAttribute(r,s){let e=r;switch(s){case Boolean:e=r!==null;break;case Number:e=r===null?null:Number(r);break;case Object:case Array:try{e=JSON.parse(r)}catch{e=null}}return e}},Fe=(r,s)=>!ps(r,s),Pt={attribute:!0,type:String,converter:$e,reflect:!1,useDefault:!1,hasChanged:Fe};Symbol.metadata??=Symbol("metadata"),Oe.litPropertyMetadata??=new WeakMap;Y=class extends HTMLElement{static addInitializer(s){this._$Ei(),(this.l??=[]).push(s)}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(s,e=Pt){if(e.state&&(e.attribute=!1),this._$Ei(),this.prototype.hasOwnProperty(s)&&((e=Object.create(e)).wrapped=!0),this.elementProperties.set(s,e),!e.noAccessor){let t=Symbol(),i=this.getPropertyDescriptor(s,t,e);i!==void 0&&us(this.prototype,s,i)}}static getPropertyDescriptor(s,e,t){let{get:i,set:n}=hs(this.prototype,s)??{get(){return this[e]},set(o){this[e]=o}};return{get:i,set(o){let p=i?.call(this);n?.call(this,o),this.requestUpdate(s,p,t)},configurable:!0,enumerable:!0}}static getPropertyOptions(s){return this.elementProperties.get(s)??Pt}static _$Ei(){if(this.hasOwnProperty(we("elementProperties")))return;let s=ms(this);s.finalize(),s.l!==void 0&&(this.l=[...s.l]),this.elementProperties=new Map(s.elementProperties)}static finalize(){if(this.hasOwnProperty(we("finalized")))return;if(this.finalized=!0,this._$Ei(),this.hasOwnProperty(we("properties"))){let e=this.properties,t=[..._s(e),...gs(e)];for(let i of t)this.createProperty(i,e[i])}let s=this[Symbol.metadata];if(s!==null){let e=litPropertyMetadata.get(s);if(e!==void 0)for(let[t,i]of e)this.elementProperties.set(t,i)}this._$Eh=new Map;for(let[e,t]of this.elementProperties){let i=this._$Eu(e,t);i!==void 0&&this._$Eh.set(i,e)}this.elementStyles=this.finalizeStyles(this.styles)}static finalizeStyles(s){let e=[];if(Array.isArray(s)){let t=new Set(s.flat(1/0).reverse());for(let i of t)e.unshift(et(i))}else s!==void 0&&e.push(et(s));return e}static _$Eu(s,e){let t=e.attribute;return t===!1?void 0:typeof t=="string"?t:typeof s=="string"?s.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Em=null,this._$Ev()}_$Ev(){this._$ES=new Promise(s=>this.enableUpdating=s),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach(s=>s(this))}addController(s){(this._$EO??=new Set).add(s),this.renderRoot!==void 0&&this.isConnected&&s.hostConnected?.()}removeController(s){this._$EO?.delete(s)}_$E_(){let s=new Map,e=this.constructor.elementProperties;for(let t of e.keys())this.hasOwnProperty(t)&&(s.set(t,this[t]),delete this[t]);s.size>0&&(this._$Ep=s)}createRenderRoot(){let s=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return It(s,this.constructor.elementStyles),s}connectedCallback(){this.renderRoot??=thiLine truncated
|
||||
\f\r]`,Ee=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,Rt=/-->/g,jt=/>/g,re=RegExp(`>|${it}(?:([^\\s"'>=/]+)(${it}*=${it}*(?:[^
|
||||
\f\r"'\`<>=]|("|')|))|$)`,"g"),Nt=/'/g,Ht=/"/g,Ft=/^(?:script|style|textarea|title)$/i,dt=r=>(s,...e)=>({_$litType$:r,strings:s,values:e}),l=dt(1),_e=dt(2),Er=dt(3),oe=Symbol.for("lit-noChange"),_=Symbol.for("lit-nothing"),Dt=new WeakMap,ae=ne.createTreeWalker(ne,129);xs=(r,s)=>{let e=r.length-1,t=[],i,n=s===2?"<svg>":s===3?"<math>":"",o=Ee;for(let p=0;p<e;p++){let d=r[p],u,m,f=-1,v=0;for(;v<d.length&&(o.lastIndex=v,m=o.exec(d),m!==null);)v=o.lastIndex,o===Ee?m[1]==="!--"?o=Rt:m[1]!==void 0?o=jt:m[2]!==void 0?(Ft.test(m[2])&&(i=RegExp("</"+m[2],"g")),o=re):m[3]!==void 0&&(o=re):o===re?m[0]===">"?(o=i??Ee,f=-1):m[1]===void 0?f=-2:(f=o.lastIndex-m[2].length,u=m[1],o=m[3]===void 0?re:m[3]==='"'?Ht:Nt):o===Ht||o===Nt?o=re:o===Rt||o===jt?o=Ee:(o=re,i=void 0);let y=o===re&&r[p+1].startsWith("/>")?" ":"";n+=o===Ee?d+ys:f>=0?(t.push(u),d.slice(0,f)+Mt+d.slice(f)+ie+y):d+ie+(f===-2?p:y)}return[zt(r,n+(r[e]||"<?>")+(s===2?"</svg>":s===3?"</math>":"")),t]},Te=class r{constructor({strings:s,_$litType$:e},t){let i;this.parts=[];let n=0,o=0,p=s.length-1,d=this.parts,[u,m]=xs(s,e);if(this.el=r.createElement(u,t),ae.currentNode=this.el.content,e===2||e===3){let f=this.el.content.firstChild;f.replaceWith(...f.childNodes)}for(;(i=ae.nextNode())!==null&&d.length<p;){if(i.nodeType===1){if(i.hasAttributes())for(let f of i.getAttributeNames())if(f.endsWith(Mt)){let v=m[o++],y=i.getAttribute(f).split(ie),k=/([.?@])?(.*)/.exec(v);d.push({type:1,index:n,name:k[2],strings:y,ctor:k[1]==="."?rt:k[1]==="?"?at:k[1]==="@"?nt:he}),i.removeAttribute(f)}else f.startsWith(ie)&&(d.push({type:6,index:n}),i.removeAttribute(f));if(Ft.test(i.tagName)){let f=i.textContent.split(ie),v=f.length-1;if(v>0){i.textContent=ze?ze.emptyScript:"";for(let y=0;y<v;y++)i.append(f[y],Se()),ae.nextNode(),d.push({type:2,index:++n});i.append(f[v],Se())}}}else if(i.nodeType===8)if(i.data===Ot)d.push({type:2,index:n});else{let f=-1;for(;(f=i.data.indexOf(ie,f+1))!==-1;)d.push({type:7,index:n}),f+=ie.length-1}n++}}static createElement(s,e){let t=ne.createElement("template");return t.innerHTML=s,t}};st=class{constructor(s,e){this._$AV=[],this._$AN=void 0,this._$AD=s,this._$AM=e}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(s){let{el:{content:e},parts:t}=this._$AD,i=(s?.creationScope??ne).importNode(e,!0);ae.currentNode=i;let n=ae.nextNode(),o=0,p=0,d=t[0];for(;d!==void 0;){if(o===d.index){let u;d.type===2?u=new Ie(n,n.nextSibling,this,s):d.type===1?u=new d.ctor(n,d.name,d.strings,this,s):d.type===6&&(u=new ot(n,this,s)),this._$AV.push(u),d=t[++p]}o!==d?.index&&(n=ae.nextNode(),o++)}return ae.currentNode=ne,i}p(s){let e=0;for(let t of this._$AV)t!==void 0&&(t.strings!==void 0?(t._$AI(s,t,e),e+=t.strings.length-2):t._$AI(s[e])),e++}},Ie=class r{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(s,e,t,i){this.type=2,this._$AH=_,this._$AN=void 0,this._$AA=s,this._$AB=e,this._$AM=t,this.options=i,this._$Cv=i?.isConnected??!0}get parentNode(){let s=this._$AA.parentNode,e=this._$AM;return e!==void 0&&s?.nodeType===11&&(s=e.parentNode),s}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(s,e=this){s=ue(this,s,e),Ae(s)?s===_||s==null||s===""?(this._$AH!==_&&this._$AR(),this._$AH=_):s!==this._$AH&&s!==oe&&this._(s):s._$litType$!==void 0?this.$(s):s.nodeType!==void 0?this.T(s):bs(s)?this.k(s):this._(s)}O(s){return this._$AA.parentNode.insertBefore(s,this._$AB)}T(s){this._$AH!==s&&(this._$AR(),this._$AH=this.O(s))}_(s){this._$AH!==_&&Ae(this._$AH)?this._$AA.nextSibling.data=s:this.T(ne.createTextNode(s)),this._$AH=s}$(s){let{values:e,_$litType$:t}=s,i=typeof t=="number"?this._$AC(s):(t.el===void 0&&(t.el=Te.createElement(zt(t.h,t.h[0]),this.options)),t);if(this._$AH?._$AD===i)this._$AH.p(e);else{let n=new st(i,this),o=n.u(this.options);n.p(e),this.T(o),this._$AH=n}}_$AC(s){let e=Dt.get(s.strings);return e===void 0&&Dt.set(s.strings,e=new Te(s)),e}k(s){ct(this._$AH)||(this._$AH=[],this._$AR());let e=this._$AH,t,i=0;for(let n of s)i===e.length?e.push(t=new r(this.O(Se()),this.O(Se()),this,this.options)):t=e[i],t._$AI(n),i++;i<e.length&&(this._$AR(t&&t._$AB.nextSibling,i),e.length=i)}_$AR(s=this._$AA.nextSibling,e){for(this._$AP?.(!1,!0,e);s!==this._$AB;){let t=Lt(s).nextSibling;Lt(s).remove(),s=t}}setConnected(s){this._$AM===void 0&&(this._$Cv=s,this._$AP?.(s))}},he=class{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(s,e,t,i,n){this.type=1,this._$AH=_,this._$AN=void 0,this.element=s,this.name=e,this._$AM=i,this.options=n,t.length>2||t[0]!==""||t[1]!==""?(this._$AH=Array(t.length-1).fill(new String),this.strings=t):this._$AH=_}_$AI(s,e=this,t,i){let n=this.strings,o=!1;if(n===void 0)s=ue(this,s,e,0),o=!Ae(s)||s!==this._$AH&&s!==oe,o&&(this._$AH=s);else{let p=s,d,u;for(s=n[0],d=0;d<n.length-1;d++)u=ue(this,p[t+d],e,d),u===oe&&(u=this._$AH[d]),o||=!Ae(u)||u!==this._$AH[d],u===_?s=_:s!==_&&(s+=(u??"")+n[d+1]),this._$AH[d]=u}o&&!i&&this.j(s)}j(s){s===_?this.element.removeAttribute(this.naLine truncated
|
||||
Replace seal
|
||||
Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_future_error:"The completion date cannot be in the future.",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",rotation_random:"Random",no_user_assigned:"(No user assigned)",all_users:"All Users",m
Line truncated
|
||||
Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"{field}: too long (max {n} characters)",err_too_short:"{field}: too short (min {n} characters)",err_value_too_high:"{field}: too large (max {n})",err_value_too_low:"{field}: too small (min {n})",err_required:"{field}: required",err_wrong_type:"{field}: wrong type (expected: {type})",err_invalid_choice:"{field}: not an allowed value",err_invalid_value:"{field}: invalid value",feat_schedule_time:"Time-of-day scheduling",feat_schedule_time_desc:"Tasks become overdue at a specific time of day instead of midnight.",schedule_time_optional:"Due at time (optional, HH:MM)",schedule_time_help:"Empty = midnight (default). HA timezone.",at_time:"at",notes_optional:"Notes (optional)",notes_markdown_hint:"Markdown is supported \u2014 **bold**, lists, [links](\u2026)",cost_optional:"Cost (optional)",duration_minutes:"Duration in minutes (optional)",completed_at_optional:"Completed at (optional, empty = now)",completed_at_future_error:"The completion date cannot be in the future.",days:"days",day:"day",today:"Today",d_overdue:"d overdue",no_tasks:"No maintenance tasks yet. Create an object to get started.",no_tasks_short:"No tasks",no_history:"No history entries yet.",show_all:"Show all",cost_duration_chart:"Cost & Duration",installed:"Installed",confirm_delete_object:"Delete this object and all its tasks?",confirm_delete_task:"Delete this task?",min:"Min",max:"Max",save:"Save",saving:"Saving\u2026",edit_task:"Edit Task",new_task:"New Maintenance Task",task_name:"Task name",maintenance_type:"Maintenance type",priority:"Priority",labels:"Labels",labels_placeholder:"e.g. safety, seasonal, tenant-visible",labels_help:"Comma-separated tags for filtering and reporting.",priority_low:"Low",priority_normal:"Normal",priority_high:"High",all_priorities:"All priorities",schedule_type:"Schedule type",interval_days:"Interval (days)",warning_days:"Warning days",earliest_completion_days:"Earliest completion (days before due)",earliest_completion_days_help:"Leave empty to allow completing any time. 0 = only on/after the due date.",last_performed_optional:"Last performed (optional)",interval_anchor:"Interval anchor",anchor_completion:"From completion date",anchor_planned:"From planned date (no drift)",edit_object:"Edit Object",name:"Name",manufacturer_optional:"Manufacturer (optional)",model_optional:"Model (optional)",serial_number_optional:"Serial number (optional)",serial_number_label:"S/N",documentation_url_label:"Manual",object_notes_label:"Notes",sort_due_date:"Due date",sort_object:"Object name",sort_type:"Type",sort_task_name:"Task name",all_objects:"All objects",all_parts:"All parts",tasks_lower:"tasks",no_tasks_yet:"No tasks yet",add_first_task:"Add first task",trigger_configuration:"Trigger Configuration",entity_id:"Entity ID",comma_separated:"comma-separated",entity_logic:"Entity logic",entity_logic_any:"Any entity triggers",entity_logic_all:"All entities must trigger",entities:"entities",attribute_optional:"Attribute (optional, blank = state)",use_entity_state:"Use entity state (no attribute)",trigger_above:"Trigger above",trigger_below:"Trigger below",trigger_equals:"Trigger when equal to (=)",trigger_not_equals:"Trigger when different from (\u2260)",for_at_least_minutes:"For at least (minutes)",safety_interval_days:"Safety interval (days, optional)",safety_interval:"Safety interval (optional)",trigger_combinator:"Combine trigger and interval",trigger_combinator_any:"Trigger or interval (whichever first)",trigger_combinator_all:"Trigger and interval (both required)",delta_mode:"Delta mode",from_state_optional:"From state (optional)",to_state_optional:"To state (optional)",documentation_url_optional:"Documentation URL (optional)",object_notes_optional:"Notes (optional)",nfc_tag_id_optional:"NFC Tag ID (optional)",nfc_tags_empty_help:"No NFC tags registered in Home Assistant yet.",nfc_tags_open_settings:"Open Tags settings",nfc_tags_refresh:"Refresh",environmental_entity_optional:"Environmental sensor (optional)",environmental_entity_helper:"e.g. sensor.outdoor_temperature \u2014 adjusts the interval based on environmental conditions",adaptive_prediction_enabled:"Enable sensor-driven predictions",adaptive_seasonal_enabled:"Enable seasonal awareness",adaptive_max_interval:"Maximum interval (days)",adaptive_min_interval:"Minimum interval (days)",adaptive_ewa_alpha:"Learning rate (alpha)",adaptive_enabled:"Enable adaptive scheduling",adaptive_section_title:"Adaptive Scheduling",environmental_attribute_optional:"Environmental attribute (optional)",nfc_tag_id:"NFC Tag ID",nfc_linked:"NFC tag linked",nfc_link_hint:"Click to link NFC tag",responsible_user:"Responsible User",shared_with:"Shared with (rotation)",shared_with_help:"Pick multiple people to share this task; the responsible person rotates on each completion.",rotation_strategy:"Rotation",rotation_none:"No rotation",rotation_round_robin:"Round-robin",rotation_least_completed:"Least completed",ro
Line truncated
|
||||
.field { display: flex; flex-direction: column; gap: 4px; }
|
||||
.field-label { font-size: 12px; color: var(--secondary-text-color); }
|
||||
.field-input {
|
||||
@@ -14,7 +14,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
font-family: inherit; width: 100%; box-sizing: border-box;
|
||||
}
|
||||
.field-input:focus { outline: none; border-color: var(--primary-color); }
|
||||
`,Ve=S`
|
||||
`,Ge=S`
|
||||
:host {
|
||||
--maint-ok-color: var(--success-color, #4caf50);
|
||||
--maint-due-soon-color: var(--warning-color, #ff9800);
|
||||
@@ -945,6 +945,11 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.prediction-cycles {
|
||||
font-size: 11px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.prediction-entity {
|
||||
font-size: 10px;
|
||||
opacity: 0.6;
|
||||
@@ -1144,9 +1149,19 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.stat-item .stat-label { font-size: 11px; white-space: normal; text-align: center; line-height: 1.2; }
|
||||
.stat-value { font-size: 20px; }
|
||||
}
|
||||
`});function Ls(r,s){let e=Ps[r];if(!e)return r;let t=a(e,s);return t&&t!==e?t:r}function Rs(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function q(r,s,e){if(e=e??a("action_error",s),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,i=t.message||t.error?.message||"";if(!i)return e;let n=Rs(i),o=n.field?Ls(n.field,s):"",p=d=>a(d,s).replace("{field}",o).replace("{n}",n.param??"");switch(n.rule){case"too_long":return p("err_too_long");case"too_short":return p("err_too_short");case"value_too_high":return p("err_value_too_high");case"value_too_low":return p("err_value_too_low");case"required":return p("err_required");case"wrong_type":return p("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return p("err_invalid_choice");case"invalid_value":return p("err_invalid_value");default:return i||e}}var Ps,le=w(()=>{"use strict";R();Ps={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"}});var N,gt=w(()=>{"use strict";L();U();N=class extends A{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return l`
|
||||
`});function Os(r,s){let e=Ms[r];if(!e)return r;let t=a(e,s);return t&&t!==e?t:r}function Fs(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 N(r,s,e){if(e=e??a("action_error",s),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,i=t.message||t.error?.message||"";if(!i)return e;let n=Fs(i),o=n.field?Os(n.field,s):"",p=d=>a(d,s).replace("{field}",o).replace("{n}",n.param??"");switch(n.rule){case"too_long":return p("err_too_long");case"too_short":return p("err_too_short");case"value_too_high":return p("err_value_too_high");case"value_too_low":return p("err_value_too_low");case"required":return p("err_required");case"wrong_type":return p("err_wrong_type").replace("{type}",n.param??"");case"invalid_choice":return p("err_invalid_choice");case"invalid_value":return p("err_invalid_value");default:return i||e}}var Ms,de=w(()=>{"use strict";j();Ms={entry_id:"object",name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"}});var L,gt=w(()=>{"use strict";P();W();L=class extends A{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1;this.multiline=!1;this.rows=3}_onInput(e){let t=e.target.value;this.value=t,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:t}}))}render(){return l`
|
||||
<label class="field">
|
||||
${this.label?l`<span class="label">${this.label}${this.required?l`<span class="req">*</span>`:_}</span>`:_}
|
||||
${this.multiline?l`
|
||||
<textarea
|
||||
.value=${this.value??""}
|
||||
rows=${this.rows}
|
||||
?required=${this.required}
|
||||
?disabled=${this.disabled}
|
||||
placeholder=${this.placeholder}
|
||||
@input=${this._onInput}
|
||||
@change=${this._onInput}
|
||||
></textarea>`:l`
|
||||
<input
|
||||
.value=${this.value??""}
|
||||
.type=${this.type}
|
||||
@@ -1159,10 +1174,10 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
pattern=${this.pattern??_}
|
||||
@input=${this._onInput}
|
||||
@change=${this._onInput}
|
||||
/>
|
||||
/>`}
|
||||
${this.helper?l`<span class="helper">${this.helper}</span>`:_}
|
||||
</label>
|
||||
`}};N.styles=S`
|
||||
`}};L.styles=S`
|
||||
:host { display: block; }
|
||||
.field {
|
||||
display: flex;
|
||||
@@ -1191,12 +1206,27 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
input:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
textarea {
|
||||
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;
|
||||
resize: vertical;
|
||||
}
|
||||
textarea:focus { border-color: var(--primary-color); }
|
||||
textarea:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
.helper {
|
||||
font-size: 11px;
|
||||
color: var(--secondary-text-color);
|
||||
font-style: italic;
|
||||
}
|
||||
`,c([x()],N.prototype,"label",2),c([x()],N.prototype,"value",2),c([x()],N.prototype,"placeholder",2),c([x()],N.prototype,"type",2),c([x({type:Boolean})],N.prototype,"required",2),c([x({type:Boolean})],N.prototype,"disabled",2),c([x()],N.prototype,"step",2),c([x()],N.prototype,"min",2),c([x()],N.prototype,"max",2),c([x()],N.prototype,"pattern",2),c([x()],N.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",N)});var T,yi=w(()=>{"use strict";L();U();R();le();gt();T=class extends A{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return j(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(e,t){this._entryId=e,this._name=t.name||"",this._manufacturer=t.manufacturer||"",this._model=t.model||"",this._serialNumber=t.serial_number||"",this._areaId=t.area_id||"",this._installationDate=t.installation_date||"",this._warrantyExpiry=t.warranty_expiry||"",this._documentationUrl=t.documentation_url||"",this._notes=t.notes||"",this._haDeviceId=t.ha_device_id||"",this._parentEntryId=t.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=q(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this._lang,t=this._entryId?a("edit_object",e):a("new_object",e);return l`
|
||||
`,c([x()],L.prototype,"label",2),c([x()],L.prototype,"value",2),c([x()],L.prototype,"placeholder",2),c([x()],L.prototype,"type",2),c([x({type:Boolean})],L.prototype,"required",2),c([x({type:Boolean})],L.prototype,"disabled",2),c([x()],L.prototype,"step",2),c([x()],L.prototype,"min",2),c([x()],L.prototype,"max",2),c([x()],L.prototype,"pattern",2),c([x()],L.prototype,"helper",2),c([x({type:Boolean})],L.prototype,"multiline",2),c([x({type:Number})],L.prototype,"rows",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",L)});var T,$i=w(()=>{"use strict";P();W();j();de();gt();T=class extends A{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return D(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(e,t){this._entryId=e,this._name=t.name||"",this._manufacturer=t.manufacturer||"",this._model=t.model||"",this._serialNumber=t.serial_number||"",this._areaId=t.area_id||"",this._installationDate=t.installation_date||"",this._warrantyExpiry=t.warranty_expiry||"",this._documentationUrl=t.documentation_url||"",this._notes=t.notes||"",this._haDeviceId=t.ha_device_id||"",this._parentEntryId=t.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=N(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(e=>e.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let e=this._lang,t=this._entryId?a("edit_object",e):a("new_object",e);return l`
|
||||
<ha-dialog open @closed=${this._close}>
|
||||
<div class="dialog-title">${t}</div>
|
||||
<div class="content">
|
||||
@@ -1276,6 +1306,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${this._notes}
|
||||
@input=${i=>this._notes=i.target.value}
|
||||
></textarea>
|
||||
<span class="md-hint">${a("notes_markdown_hint",e)}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
@@ -1327,6 +1358,9 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.textarea-field textarea:focus {
|
||||
outline: none; border-color: var(--primary-color);
|
||||
}
|
||||
.md-hint {
|
||||
font-size: 11px; color: var(--secondary-text-color); font-style: italic;
|
||||
}
|
||||
.parent-select {
|
||||
padding: 8px 10px; font-size: 14px; font-family: inherit;
|
||||
background: var(--secondary-background-color, rgba(0,0,0,0.06));
|
||||
@@ -1337,9 +1371,9 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
color: var(--error-color, #f44336);
|
||||
font-size: 13px;
|
||||
}
|
||||
`,c([x({attribute:!1})],T.prototype,"hass",2),c([x({attribute:!1})],T.prototype,"objects",2),c([h()],T.prototype,"_open",2),c([h()],T.prototype,"_loading",2),c([h()],T.prototype,"_error",2),c([h()],T.prototype,"_name",2),c([h()],T.prototype,"_manufacturer",2),c([h()],T.prototype,"_model",2),c([h()],T.prototype,"_serialNumber",2),c([h()],T.prototype,"_areaId",2),c([h()],T.prototype,"_installationDate",2),c([h()],T.prototype,"_warrantyExpiry",2),c([h()],T.prototype,"_documentationUrl",2),c([h()],T.prototype,"_notes",2),c([h()],T.prototype,"_haDeviceId",2),c([h()],T.prototype,"_parentEntryId",2),c([h()],T.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",T)});var Ke,bi=w(()=>{"use strict";Ke=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}}});function B(r){return`${r.entry_id??""}\0${r.part_id}`}function qs(r,s,e,t){let i=!!r.entry_id&&r.entry_id!==s,n=i?r.entry_id:s,o=e.find(m=>m.entry_id===n),p=(o?.parts||[]).find(m=>m.id===r.part_id)||null,d=i&&o?.object?.name||"",u=p?.name||a("shared_part_unknown",t);return{part:p,foreign:i,ownerName:d,label:d?`${u} (${d})`:u}}function xi(r,s,e,t){let n=(e.find(p=>p.entry_id===s)?.parts||[]).map(p=>({...p})),o=new Set(n.map(p=>B({part_id:p.id})));for(let p of r?.consumes_parts||[]){if(!p.entry_id||p.entry_id===s)continue;let d=B(p);if(o.has(d))continue;o.add(d);let{part:u,ownerName:m}=qs(p,s,e,t);n.push({id:p.part_id,name:u?.name||a("shared_part_unknown",t),unit:u?.unit,stock:u?.stock??null,storage_location:u?.storage_location,entry_id:p.entry_id,owner_name:m})}return n}var Ge=w(()=>{"use strict";R()});var mt,wi,$i,ki=w(()=>{"use strict";mt=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock"],wi=["sensor"],$i=["temperature","humidity","pressure"]});var Ei,Ye,ft=w(()=>{"use strict";Ei=["notes","cost","duration","photo","user"],Ye={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"}});function Ms(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",carry:{}}}function zs(r){return{entityIds:(r.entity_ids||(r.entity_id?[r.entity_id]:[])).join(", "),type:r.type||"threshold",attribute:r.attribute||"",above:r.trigger_above?.toString()??"",below:r.trigger_below?.toString()??"",equals:r.trigger_equals?.toString()??"",notEquals:r.trigger_not_equals?.toString()??"",forMinutes:r.trigger_for_minutes?.toString()??"0",targetValue:r.trigger_target_value?.toString()??"",deltaMode:r.trigger_delta_mode||!1,fromState:r.trigger_from_state||"",toState:r.trigger_to_state||"",targetChanges:r.trigger_target_changes?.toString()??"",runtimeHours:r.trigger_runtime_hours?.toString()??"",onStates:(r.trigger_on_states||[]).join(", "),carry:Object.fromEntries(Object.entries(r).filter(([e])=>!Os.has(e)&&!e.startsWith("_")))}}function Fs(r){let s=r.entityIds.split(",").map(t=>t.trim()).filter(Boolean);if(s.length===0)return null;let e={...r.carry||{},entity_id:s[0],entity_ids:s,type:r.type};if(r.attribute&&(e.attribute=r.attribute),r.type==="threshold"){let t=parseFloat(r.above);isNaN(t)||(e.trigger_above=t);let i=parseFloat(r.below);isNaN(i)||(e.trigger_below=i);let n=parseFloat(r.equals);isNaN(n)||(e.trigger_equals=n);let o=parseFloat(r.notEquals);isNaN(o)||(e.trigger_not_equals=o);let p=parseInt(r.forMinutes,10);isNaN(p)||(e.trigger_for_minutes=p)}else if(r.type==="counter"){let t=parseFloat(r.targetValue);isNaN(t)||(e.trigger_target_value=t),e.trigger_delta_mode=r.deltaMode}else if(r.type==="state_change"){r.fromState&&(e.trigger_from_state=r.fromState),r.toState&&(e.trigger_to_state=r.toState);let t=parseInt(r.targetChanges,10
Line truncated
|
||||
`),this._phaseDefs=Object.entries(t.phases||{}).map(([u,m])=>{let{name:f,checklist:v,consumes_parts:b,...k}=m;return{id:u,name:m.name||u,checklistText:(m.checklist||[]).join(`
|
||||
`),partId:m.consumes_parts?.[0]?.part_id||"",partQty:m.consumes_parts?.[0]?.quantity!=null?String(m.consumes_parts[0].quantity):"",extraParts:(m.consumes_parts||[]).slice(1),carry:k}}),this._phaseSeq=[...t.phase_sequence||[]],this._requiredCompletion=[...t.required_completion_fields||[]],this._scheduleTime=t.schedule_time||"";let o=t.on_complete_action;if(o&&o.service){this._actionService=o.service;let u=o.target?.entity_id;this._actionTargetEntity=Array.isArray(u)?u[0]||"":u||"",this._actionData=o.data&&typeof o.data=="object"?{...o.data}:{},this._actionDataJsonFallback=""}else this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="";let p=t.quick_complete_defaults;this._qcNotes=p?.notes||"",this._qcCost=p?.cost!=null?String(p.cost):"",this._qcDuration=p?.duration!=null?String(p.duration):"",this._qcFeedback=p?.feedback||"";let d=t.adaptive_config||{};if(this._environmentalEntity=d.environmental_entity||"",this._environmentalAttribute=d.environmental_attribute||"",this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute,this._adaptiveEnabled=!!d.enabled,this._adaptiveAlpha=d.ewa_alpha?.toString()??Q.alpha,this._adaptiveMin=d.min_interval_days?.toString()??Q.min,this._adaptiveMax=d.max_interval_days?.toString()??Q.max,this._adaptiveSeasonal=d.seasonal_enabled!==!1,this._adaptivePrediction=d.sensor_prediction_enabled!==!1,this._adaptiveInitial=this._adaptiveSnapshot(),t.trigger_config){let u=t.trigger_config;this._triggerEntityId=u.entity_id||u.entity_ids&&u.entity_ids[0]||"",this._triggerEntityIds=u.entity_ids||(u.entity_id?[u.entity_id]:[]),this._triggerEntityLogic=u.entity_logic||"any",this._triggerAttribute=u.attribute||"",this._triggerType=u.type||"threshold",this._triggerAbove=u.trigger_above?.toString()||"",this._triggerBelow=u.trigger_below?.toString()||"",this._triggerEquals=u.trigger_equals?.toString()||"",this._triggerNotEquals=u.trigger_not_equals?.toString()||"",this._triggerForMinutes=u.trigger_for_minutes?.toString()||"0",this._triggerCombinator=u.trigger_combinator==="all"?"all":"any",this._triggerTargetValue=u.trigger_target_value?.toString()||"",this._triggerDeltaMode=u.trigger_delta_mode||!1,this._triggerBaselineValue=u.trigger_baseline_value?.toString()||"",this._liveBaselineValue=t.trigger_baseline_value??null,this._autoCompleteOnRecovery=u.auto_complete_on_recovery||!1,this._triggerFromState=u.trigger_from_state||"",this._triggerToState=u.trigger_to_state||"",this._triggerTargetChanges=u.trigger_target_changes?.toString()||"",this._triggerRuntimeHours=u.trigger_runtime_hours?.toString()||"",this._triggerOnStates=(u.trigger_on_states||[]).join(", "),u.type==="compound"?(this._compoundLogic=u.compound_logic==="OR"?"OR":"AND",this._compoundConditions=(u.conditions||[]).map(zs)):(this._compoundLogic="AND",this._compoundConditions=[])}else this._resetTriggerFields();this._triggerEntityId&&this._fetchEntityAttributes(this._triggerEntityId),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}_resetFields(){this._name="",this._type="custom",this._scheduleType="time_based",this._intervalDays="30",this._intervalUnit="days",this._dueDate="",this._warningDays=String(this.defaultWarningDays),this._earliestCompletionDays="",this._intervalAnchor="completion",this._weekdays=[],this._nth="1",this._nthWeekday="5",this._domDay="1",this._domLastDay=!1,this._domBusiness=!1,this._calOffset="0",this._seasonMonths=[],this._endsMode="never",this._endsCount="",this._endsUntil="",this._notes="",this._documentationUrl="",this._customIcon="",this._priority="normal",this._labels="",this._enabled=!0,this._lastPerformed="",this._nfcTagId="",this._readingUnit="",this._consumesParts={},this._responsibleUserId=null,this._assigneePool=[],this._rotationStrategy="",this._checklistText="",this._phaseDefs=[],this._phaseSeq=[],this._requiredCompletion=[],this._scheduleTime="",this._environmentalEntity="",this._environmentalAttribute="",this._environmentalInitial="",this._environmentalAttributeInitial="",this._adaptiveEnabled=!1,this._adaptiveAlpha=Q.alpha,this._adaptiveMin=Q.min,this._adaptiveMax=Q.max,this._adaptiveSeasonal=!0,this._adaptivePrediction=!0,this._adaptiveInitial=this._adaptiveSnapshot(),this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="",this._actionTesting=!1,this._actionTestResult="",this._qcNotes="",this._qcCost="",this._qcDuration="",this._qcFeedback="",this._resetTriggerFields()}_resetTriggerFields(){this._triggerEntityId="",this._triggerEntityIds=[],this._triggerEntityLogic="any",this._triggerAttribute="",this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="",this._triggerType="threshold",this._triggerAbove="",this._triggerBelow="",this._triggerEquals="",this._triggerNotEquals="",this._triggerForMinutes="0",this._triggerCombinator="any",this._triggerTargetValue="",t
Line truncated
|
||||
`,c([x({attribute:!1})],T.prototype,"hass",2),c([x({attribute:!1})],T.prototype,"objects",2),c([h()],T.prototype,"_open",2),c([h()],T.prototype,"_loading",2),c([h()],T.prototype,"_error",2),c([h()],T.prototype,"_name",2),c([h()],T.prototype,"_manufacturer",2),c([h()],T.prototype,"_model",2),c([h()],T.prototype,"_serialNumber",2),c([h()],T.prototype,"_areaId",2),c([h()],T.prototype,"_installationDate",2),c([h()],T.prototype,"_warrantyExpiry",2),c([h()],T.prototype,"_documentationUrl",2),c([h()],T.prototype,"_notes",2),c([h()],T.prototype,"_haDeviceId",2),c([h()],T.prototype,"_parentEntryId",2),c([h()],T.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",T)});var Ye,ki=w(()=>{"use strict";Ye=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}}});function V(r){return`${r.entry_id??""}\0${r.part_id}`}function Ei(r,s,e,t){let i=!!r.entry_id&&r.entry_id!==s,n=i?r.entry_id:s,o=e.find(m=>m.entry_id===n),p=(o?.parts||[]).find(m=>m.id===r.part_id)||null,d=i&&o?.object?.name||"",u=p?.name||a("shared_part_unknown",t);return{part:p,foreign:i,ownerName:d,label:d?`${u} (${d})`:u}}function Si(r,s,e,t){let{part:i,label:n}=Ei(r,s,e,t),o=i&&i.stock!==null&&i.stock!==void 0?` (${i.stock}${i.unit?" "+i.unit:""})`:"",p=i?.storage_location?` \u2014 ${i.storage_location}`:"";return`${r.quantity}\xD7 ${n}${o}${p}`}function Ai(r,s,e,t){let n=(e.find(p=>p.entry_id===s)?.parts||[]).map(p=>({...p})),o=new Set(n.map(p=>V({part_id:p.id})));for(let p of r?.consumes_parts||[]){if(!p.entry_id||p.entry_id===s)continue;let d=V(p);if(o.has(d))continue;o.add(d);let{part:u,ownerName:m}=Ei(p,s,e,t);n.push({id:p.part_id,name:u?.name||a("shared_part_unknown",t),unit:u?.unit,stock:u?.stock??null,storage_location:u?.storage_location,entry_id:p.entry_id,owner_name:m})}return n}var Qe=w(()=>{"use strict";j()});var mt,Ti,Ii,Ci=w(()=>{"use strict";mt=["sensor","binary_sensor","number","input_number","input_boolean","switch","climate","vacuum","cover","fan","light","water_heater","humidifier","media_player","weather","air_quality","valve","lawn_mower","lock"],Ti=["sensor"],Ii=["temperature","humidity","pressure"]});var ft,Ne,vt=w(()=>{"use strict";ft=["notes","cost","duration","photo","user"],Ne={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"}});function Vs(){return{entityIds:"",type:"threshold",attribute:"",above:"",below:"",equals:"",notEquals:"",forMinutes:"0",targetValue:"",deltaMode:!1,fromState:"",toState:"",targetChanges:"",runtimeHours:"",onStates:"",carry:{}}}function Gs(r){return{entityIds:(r.entity_ids||(r.entity_id?[r.entity_id]:[])).join(", "),type:r.type||"threshold",attribute:r.attribute||"",above:r.trigger_above?.toString()??"",below:r.trigger_below?.toString()??"",equals:r.trigger_equals?.toString()??"",notEquals:r.trigger_not_equals?.toString()??"",forMinutes:r.trigger_for_minutes?.toString()??"0",targetValue:r.trigger_target_value?.toString()??"",deltaMode:r.trigger_delta_mode||!1,fromState:r.trigger_from_state||"",toState:r.trigger_to_state||"",targetChanges:r.trigger_target_changes?.toString()??"",runtimeHours:r.trigger_runtime_hours?.toString()??"",onStates:(r.trigger_on_states||[]).join(", "),carry:Object.fromEntries(Object.entries(r).filter(([e])=>!Ks.has(e)&&!e.startsWith("_")))}}function Ys(r){let s=r.entityIds.split(",").map(t=>t.trim()).filter(Boolean);if(s.length===0)return null;let e={...r.carry||{},entity_id:s[0],entity_ids:s,type:r.type};if(r.attribute&&(e.attribute=r.attribute),r.type==="threshold"){let t=parseFloat(r.above);isNaN(t)||(e.trigger_above=t);let i=parseFloat(r.below);isNaN(i)||(e.trigger_below=i);let n=parseFloat(r.equals);isNaN(n)||(e.trigger_equals=n);let o=parseFloat(r.notEquals);isNaN(o)||(e.trigger_not_equals=o);let p=parseInt(r.forMinutes,10);isNaN(p)||(e.trigger_for_minutes=p)}else if(r.type==="counter"){let t=parseFloat(r.targetValu
Line truncated
|
||||
`),this._phaseDefs=Object.entries(t.phases||{}).map(([u,m])=>{let{name:f,checklist:v,consumes_parts:y,required_completion_fields:k,...E}=m,H=m.consumes_parts||[],M=H.findIndex(z=>!z.entry_id),q=M>=0?H[M]:void 0;return{id:u,name:m.name||u,checklistText:(m.checklist||[]).join(`
|
||||
`),partId:q?.part_id||"",partQty:q?.quantity!=null?String(q.quantity):"",reqOverride:m.required_completion_fields!==void 0,reqFields:[...m.required_completion_fields||[]],extraParts:H.filter((z,te)=>te!==M).map(z=>({...z})),carry:E}}),this._phaseSeq=[...t.phase_sequence||[]],this._requiredCompletion=[...t.required_completion_fields||[]],this._scheduleTime=t.schedule_time||"";let o=t.on_complete_action;if(o&&o.service){this._actionService=o.service;let u=o.target?.entity_id;this._actionTargetEntity=Array.isArray(u)?u[0]||"":u||"",this._actionData=o.data&&typeof o.data=="object"?{...o.data}:{},this._actionDataJsonFallback=""}else this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="";let p=t.quick_complete_defaults;this._qcNotes=p?.notes||"",this._qcCost=p?.cost!=null?String(p.cost):"",this._qcDuration=p?.duration!=null?String(p.duration):"",this._qcFeedback=p?.feedback||"";let d=t.adaptive_config||{};if(this._environmentalEntity=d.environmental_entity||"",this._environmentalAttribute=d.environmental_attribute||"",this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute,this._adaptiveEnabled=!!d.enabled,this._adaptiveAlpha=d.ewa_alpha?.toString()??J.alpha,this._adaptiveMin=d.min_interval_days?.toString()??J.min,this._adaptiveMax=d.max_interval_days?.toString()??J.max,this._adaptiveSeasonal=d.seasonal_enabled!==!1,this._adaptivePrediction=d.sensor_prediction_enabled!==!1,this._adaptiveInitial=this._adaptiveSnapshot(),t.trigger_config){let u=t.trigger_config;this._triggerEntityId=u.entity_id||u.entity_ids&&u.entity_ids[0]||"",this._triggerEntityIds=u.entity_ids||(u.entity_id?[u.entity_id]:[]),this._triggerEntityLogic=u.entity_logic||"any",this._triggerAttribute=u.attribute||"",this._triggerType=u.type||"threshold",this._triggerAbove=u.trigger_above?.toString()||"",this._triggerBelow=u.trigger_below?.toString()||"",this._triggerEquals=u.trigger_equals?.toString()||"",this._triggerNotEquals=u.trigger_not_equals?.toString()||"",this._triggerForMinutes=u.trigger_for_minutes?.toString()||"0",this._triggerCombinator=u.trigger_combinator==="all"?"all":"any",this._triggerTargetValue=u.trigger_target_value?.toString()||"",this._triggerDeltaMode=u.trigger_delta_mode||!1,this._triggerBaselineValue=u.trigger_baseline_value?.toString()||"",this._liveBaselineValue=t.trigger_baseline_value??null,this._autoCompleteOnRecovery=u.auto_complete_on_recovery||!1,this._triggerFromState=u.trigger_from_state||"",this._triggerToState=u.trigger_to_state||"",this._triggerTargetChanges=u.trigger_target_changes?.toString()||"",this._triggerRuntimeHours=u.trigger_runtime_hours?.toString()||"",this._triggerOnStates=(u.trigger_on_states||[]).join(", "),u.type==="compound"?(this._compoundLogic=u.compound_logic==="OR"?"OR":"AND",this._compoundConditions=(u.conditions||[]).map(Gs)):(this._compoundLogic="AND",this._compoundConditions=[])}else this._resetTriggerFields();this._triggerEntityId&&this._fetchEntityAttributes(this._triggerEntityId),await Promise.all([this._loadUsers(),this._loadTags(),this._loadParts(),this._loadForeignPools()]),this._open=!0}_resetFields(){this._name="",this._type="custom",this._scheduleType="time_based",this._intervalDays="30",this._intervalUnit="days",this._dueDate="",this._warningDays=String(this.defaultWarningDays),this._earliestCompletionDays="",this._intervalAnchor="completion",this._weekdays=[],this._nth="1",this._nthWeekday="5",this._domDay="1",this._domLastDay=!1,this._domBusiness=!1,this._calOffset="0",this._seasonMonths=[],this._endsMode="never",this._endsCount="",this._endsUntil="",this._notes="",this._documentationUrl="",this._customIcon="",this._priority="normal",this._labels="",this._enabled=!0,this._lastPerformed="",this._nfcTagId="",this._requireTagScan=!1,this._readingUnit="",this._consumesParts={},this._responsibleUserId=null,this._assigneePool=[],this._rotationStrategy="",this._checklistText="",this._phaseDefs=[],this._phaseSeq=[],this._requiredCompletion=[],this._scheduleTime="",this._environmentalEntity="",this._environmentalAttribute="",this._environmentalInitial="",this._environmentalAttributeInitial="",this._adaptiveEnabled=!1,this._adaptiveAlpha=J.alpha,this._adaptiveMin=J.min,this._adaptiveMax=J.max,this._adaptiveSeasonal=!0,this._adaptivePrediction=!0,this._adaptiveInitial=this._adaptiveSnapshot(),this._actionService="",this._actionTargetEntity="",this._actionData={},this._actionDataJsonFallback="",this._actionTesting=!1,this._actionTestResult="",this._qcNotes="",this._qcCost="",this._qcDuration="",this._qcFeedback="",this._resetTriggerFields()}_resetTriggerFields(){this._triggerEntityId="",this._triggerEntityIds=[],this._triggerEntityLogic="any",this._triggerAttribute="",this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="",this._triggerType="threshold",this._triggerAbove="",this._triggerBelow="",this._triggerEquals="",this._triggerNotEquals="",this._trigger
Line truncated
|
||||
<details class="ca-section">
|
||||
<summary>${a("on_complete_action_title",e)}</summary>
|
||||
<p class="field-help">${a("on_complete_action_desc",e)}</p>
|
||||
@@ -1415,7 +1449,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
<option value="not_needed">${a("quick_complete_defaults_feedback_not_needed",e)}</option>
|
||||
</select>
|
||||
</details>
|
||||
`}async _loadParts(){if(this.parts=[],!!this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this.parts=e.parts||[],this._partsLoadFailed=!1}catch{this.parts=[],this._partsLoadFailed=!0}}async _loadForeignPools(){if(this._foreignOwners=[],!!this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._foreignOwners=(e.objects||[]).filter(t=>t.entry_id!==this._entryId&&(t.parts||[]).length>0).map(t=>({entry_id:t.entry_id,name:t.object?.name||t.entry_id,parts:t.parts||[]})).sort((t,i)=>t.name.localeCompare(i.name))}catch{this._foreignOwners=[]}}async _loadTags(){try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tags/list"});this._availableTags=e.tags||[]}catch{this._availableTags=[]}}_fetchConditionAttributes(e){!e||!this.hass||this._conditionAttrOptions[e]||this._conditionAttrPending.has(e)||(this._conditionAttrPending.add(e),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/entity/attributes",entity_id:e}).then(t=>{let i=t;this._conditionAttrOptions={...this._conditionAttrOptions,[e]:{suggested:i.suggested_attributes||[],available:i.available_attributes||[]}}}).catch(()=>{this._conditionAttrOptions={...this._conditionAttrOptions,[e]:{suggested:[],available:[]}}}))}async _fetchEntityAttributes(e){if(!e||!this.hass){this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="";return}try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/entity/attributes",entity_id:e});this._entityDomain=t.domain||"",this._suggestedAttributes=t.suggested_attributes||[],this._availableAttributes=t.available_attributes||[]}catch{this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain=""}}get _hasForeignPick(){return Object.values(this._consumesParts).some(e=>!!e.entry_id)}_renderConsumesRow(e,t){let i=B({part_id:e.id,entry_id:t}),n=this._consumesParts[i],o=t?{part_id:e.id,quantity:1,entry_id:t}:{part_id:e.id,quantity:1};return l`
|
||||
`}async _loadParts(){if(this.parts=[],!!this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this.parts=e.parts||[],this._partsLoadFailed=!1}catch{this.parts=[],this._partsLoadFailed=!0}}async _loadForeignPools(){if(this._foreignOwners=[],!!this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._foreignOwners=(e.objects||[]).filter(t=>t.entry_id!==this._entryId&&(t.parts||[]).length>0).map(t=>({entry_id:t.entry_id,name:t.object?.name||t.entry_id,parts:t.parts||[]})).sort((t,i)=>t.name.localeCompare(i.name))}catch{this._foreignOwners=[]}}async _loadTags(){try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tags/list"});this._availableTags=e.tags||[]}catch{this._availableTags=[]}}_fetchConditionAttributes(e){!e||!this.hass||this._conditionAttrOptions[e]||this._conditionAttrPending.has(e)||(this._conditionAttrPending.add(e),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/entity/attributes",entity_id:e}).then(t=>{let i=t;this._conditionAttrOptions={...this._conditionAttrOptions,[e]:{suggested:i.suggested_attributes||[],available:i.available_attributes||[]}}}).catch(()=>{this._conditionAttrOptions={...this._conditionAttrOptions,[e]:{suggested:[],available:[]}}}))}async _fetchEntityAttributes(e){if(!e||!this.hass){this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain="";return}try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/entity/attributes",entity_id:e});this._entityDomain=t.domain||"",this._suggestedAttributes=t.suggested_attributes||[],this._availableAttributes=t.available_attributes||[]}catch{this._suggestedAttributes=[],this._availableAttributes=[],this._entityDomain=""}}get _hasForeignPick(){return Object.values(this._consumesParts).some(e=>!!e.entry_id)}_renderConsumesRow(e,t){let i=V({part_id:e.id,entry_id:t}),n=this._consumesParts[i],o=t?{part_id:e.id,quantity:1,entry_id:t}:{part_id:e.id,quantity:1};return l`
|
||||
<div class="consumes-row">
|
||||
<label class="consumes-check">
|
||||
<input
|
||||
@@ -1435,7 +1469,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
@input=${p=>{let d=parseFloat(p.target.value);this._consumesParts={...this._consumesParts,[i]:{...o,quantity:Number.isFinite(d)&&d>=.01?d:1}}}}
|
||||
/>`:_}
|
||||
</div>
|
||||
`}_toggleRequired(e,t){let i=new Set(this._requiredCompletion);t?i.add(e):i.delete(e),this._requiredCompletion=[...i]}_phaseSlug(e){let t=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"phase",i=t,n=2;for(;this._phaseDefs.some(o=>o.id===i);)i=`${t}-${n++}`;return i}_addPhaseDef(){let e=this._phaseSlug(`phase-${this._phaseDefs.length+1}`);this._phaseDefs=[...this._phaseDefs,{id:e,name:"",checklistText:"",partId:"",partQty:"",extraParts:[],carry:{}}]}_removePhaseDef(e){this._phaseDefs=this._phaseDefs.filter(t=>t.id!==e),this._phaseSeq=this._phaseSeq.filter(t=>t!==e)}_patchPhaseDef(e,t){this._phaseDefs=this._phaseDefs.map(i=>i.id===e?{...i,...t}:i)}_renderPhasesEditor(e){let t=i=>this._phaseDefs.find(n=>n.id===i)?.name||i;return l`
|
||||
`}_toggleRequired(e,t){let i=new Set(this._requiredCompletion);t?i.add(e):i.delete(e),this._requiredCompletion=[...i]}_phaseSlug(e){let t=e.toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,24)||"phase",i=t,n=2;for(;this._phaseDefs.some(o=>o.id===i);)i=`${t}-${n++}`;return i}_addPhaseDef(){let e=this._phaseSlug(`phase-${this._phaseDefs.length+1}`);this._phaseDefs=[...this._phaseDefs,{id:e,name:"",checklistText:"",partId:"",partQty:"",reqOverride:!1,reqFields:[],extraParts:[],carry:{}}]}_removePhaseDef(e){this._phaseDefs=this._phaseDefs.filter(t=>t.id!==e),this._phaseSeq=this._phaseSeq.filter(t=>t!==e)}_patchPhaseDef(e,t){this._phaseDefs=this._phaseDefs.map(i=>i.id===e?{...i,...t}:i)}_renderPhasesEditor(e){let t=i=>this._phaseDefs.find(n=>n.id===i)?.name||i;return l`
|
||||
<h3>${a("phases_section",e)}</h3>
|
||||
<div class="field-help">${a("phases_hint",e)}</div>
|
||||
${this._phaseDefs.map(i=>l`
|
||||
@@ -1473,6 +1507,28 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
@input=${n=>this._patchPhaseDef(i.id,{checklistText:n.target.value})}
|
||||
></textarea>
|
||||
`:_}
|
||||
<label class="req-option phase-req-toggle">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${i.reqOverride}
|
||||
@change=${n=>this._patchPhaseDef(i.id,{reqOverride:n.target.checked})}
|
||||
/>
|
||||
<span>${a("phase_require_override",e)}</span>
|
||||
</label>
|
||||
${i.reqOverride?l`
|
||||
<div class="required-completion phase-req-fields">
|
||||
${ft.map(n=>l`
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${i.reqFields.includes(n)}
|
||||
@change=${o=>{let p=o.target.checked,d=new Set(i.reqFields);p?d.add(n):d.delete(n),this._patchPhaseDef(i.id,{reqFields:[...d]})}}
|
||||
/>
|
||||
<span>${a(Ne[n],e)}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
`:_}
|
||||
</div>
|
||||
`)}
|
||||
<ha-button appearance="plain" @click=${this._addPhaseDef}>
|
||||
@@ -1497,9 +1553,9 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
</select>
|
||||
</div>
|
||||
`:_}
|
||||
`}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${a("adaptive_min_interval",this._lang)} > ${a("adaptive_max_interval",this._lang)}`;return}}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):vt.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(p=>p.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.reading_unit=this._readingUnit.trim()||null;{let p={};for(let u of this._phaseDefs){if(!u.name.trim())continue;let m={...u.carry,name:u.name.trim()},f=u.checklistText.split(`
|
||||
`).map(b=>b.trim()).filter(Boolean);f.length&&(m.checklist=f);let v=[];if(u.partId){let b=parseFloat(u.partQty);v.push({part_id:u.partId,quantity:Number.isFinite(b)&&b>0?b:1})}v.push(...u.extraParts),v.length&&(m.consumes_parts=v),p[u.id]=m}let d=this._phaseSeq.filter(u=>u in p);e.phases=Object.keys(p).length&&d.length?p:null,e.phase_sequence=e.phases?d:null}if((this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(p=>p.entry_id?{part_id:p.part_id,quantity:p.quantity,entry_id:p.entry_id}:{part_id:p.part_id,quantity:p.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let p=this._compoundConditions.map(Fs).filter(d=>d!==null);if(p.length>0){let d={type:"compound",compound_logic:this._compoundLogic,conditions:p};this._autoCompleteOnRecovery&&(d.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(d.trigger_combinator="all"),e.trigger_config=d}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let p=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],d={entity_id:p[0],entity_ids:p,type:this._triggerType};if(this._triggerAttribute&&(d.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(d.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(d.trigger_combinator="all"),p.length>1&&(d.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let u=parseFloat(this._triggerAbove);isNaN(u)||(d.trigger_above=u)}if(this._triggerBelow){let u=parseFloat(this._triggerBelow);isNaN(u)||(d.trigger_below=u)}if(this._triggerEquals){let u=parseFloat(this._triggerEquals);isNaN(u)||(d.trigger_equals=u)}if(this._triggerNotEquals){let u=parseFloat(this._triggerNotEquals);isNaN(u)||(d.trigger_not_equals=u)}if(this._triggerForMinutes){let u=parseInt(this._triggerForMinutes,10);isNaN(u)||(d.trigger_for_minutes=u)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let u=parseFloat(this._triggerTargetValue);isNaN(u)||(d.trigger_target_value=u)}if(d.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let u=parseFloat(this._triggerBaselineValue);!isNaN(u)&&u>=0&&(d.trigger_baseline_value=u)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(d.trigger_from_state=this._triggerFromState),this._triggerToState&&(d.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let u=parseInt(this._triggerTargetChanges,10);isNaN(u)||(d.trigger_target_changes=u)}if(this._triggerForMinutes){let u=parseInt(this._triggerForMinutes,10);isNaN(u)||(d.trigger_for_minutes=u)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let m=parseFloat(this._triggerRuntimeHours);isNaN(m)||(d.trigger_runtime_hours=m)}let u=this._triggerOnStates.split(",").map(m=>m.trim()).filter(Boolean);u.length>0&&(d.trigger_on_states=u)}e.trigger_config=d}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&this._scheduleType==="time_based"){let p=this._scheduleTime.trim();e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(p)?p:null}if(this.checklistsEnabled){let p=this._checklistText.split(`
|
||||
`).map(d=>d.trim()).filter(Boolean).slice(0,100);e.checklist=p.length?p:null}if(this.completionActionsEnabled){let p=this._actionService.trim();if(p&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(p)){let f={service:p},v=this._actionTargetEntity.trim();v&&(f.target={entity_id:v});let b=this._buildActionData();Object.keys(b).length>0&&(f.data=b),e.on_complete_action=f}else e.on_complete_action=null;let d={};this._qcNotes.trim()&&(d.notes=this._qcNotes.trim());let u=parseFloat(this._qcCost);!isNaN(u)&&u>=0&&(d.cost=u);let m=parseInt(this._qcDuration,10);!isNaN(m)&&m>=0&&(d.duration=m),this._qcFeedback&&(d.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(d).length?d:null}let i=await this.hass.connection.sendMessagePromise(e),n=this._taskId||i?.task_id,o=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(n&&this._scheduleType==="sensor_based"&&o)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:n,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null}),this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute}catch{}if(n&&this._adaptiveSnapshot()!==this._adaptiveInitial){let p=parseFloat(this._adaptiveAlpha),d=parseInt(this._adaptiveMin,10),u=parseInt(this._adaptiveMax,10);try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:n,enabled:this._adaptiveEnabled,...p>=.1&&p<=.9?{ewa_alpha:p}:{},...!isNaN(d)&&d>=1?{min_interval_days:d}:{},...!isNaN(u)&&u>=1?{max_interval_days:u}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction}),this._adaptiveInitial=this._adaptiveSnapshot()}catch{}}this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=q(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return _;let e=this._lang,t=this._triggerType==="compound";return l`
|
||||
`}async _save(){if(!this._loading&&this._name.trim()){if(this._adaptiveSnapshot()!==this._adaptiveInitial){let e=parseInt(this._adaptiveMin,10),t=parseInt(this._adaptiveMax,10);if(!isNaN(e)&&!isNaN(t)&&e>t){this._error=`${a("adaptive_min_interval",this._lang)} > ${a("adaptive_max_interval",this._lang)}`;return}}this._loading=!0,this._error="";try{let e={type:this._taskId?"maintenance_supporter/task/update":"maintenance_supporter/task/create",entry_id:this._entryId,name:this._name,task_type:this._type,schedule_type:this._scheduleType,warning_days:Number.isNaN(parseInt(this._warningDays,10))?this.defaultWarningDays:Math.max(0,parseInt(this._warningDays,10))},t=this._earliestCompletionDays.trim();e.earliest_completion_days=t===""?null:Math.max(0,parseInt(t,10)||0),this._taskId&&(e.task_id=this._taskId),this._scheduleType==="one_time"?(e.due_date=this._dueDate||null,e.interval_days=null):yt.includes(this._scheduleType)?(e.schedule={...this._buildSchedule(),...this._recurrenceExtras()},e.interval_days=null,this._taskId&&(e.due_date=null)):(this._taskId&&(e.due_date=null),this._scheduleType!=="manual"&&this._intervalDays?(e.interval_days=parseInt(this._intervalDays,10),e.interval_unit=this._intervalUnit,e.interval_anchor=this._intervalAnchor,this._scheduleType==="time_based"&&(e.schedule={kind:"interval",...this._recurrenceExtras()})):this._taskId&&(e.interval_days=null,e.interval_anchor="completion")),e.notes=this._notes||null,e.documentation_url=this._documentationUrl||null,e.custom_icon=this._customIcon||null,e.priority=this._priority,e.labels=this._labels.split(",").map(p=>p.trim()).filter(Boolean),e.enabled=this._enabled,e.last_performed=this._lastPerformed||null,e.nfc_tag_id=this._nfcTagId||null,e.require_tag_scan=this._requireTagScan,e.reading_unit=this._readingUnit.trim()||null;{let p={};for(let u of this._phaseDefs){if(!u.name.trim())continue;let m={...u.carry,name:u.name.trim()},f=u.checklistText.split(`
|
||||
`).map(y=>y.trim()).filter(Boolean);f.length&&(m.checklist=f);let v=[];if(u.partId){let y=parseFloat(u.partQty);v.push({part_id:u.partId,quantity:Number.isFinite(y)&&y>0?y:1})}for(let y of u.extraParts)v.push(y.entry_id?{part_id:y.part_id,quantity:y.quantity,entry_id:y.entry_id}:{part_id:y.part_id,quantity:y.quantity});v.length&&(m.consumes_parts=v),u.reqOverride&&(m.required_completion_fields=[...u.reqFields]),p[u.id]=m}let d=this._phaseSeq.filter(u=>u in p);e.phases=Object.keys(p).length&&d.length?p:null,e.phase_sequence=e.phases?d:null}if((this.parts.length||this._foreignOwners.length)&&(e.consumes_parts=Object.values(this._consumesParts).map(p=>p.entry_id?{part_id:p.part_id,quantity:p.quantity,entry_id:p.entry_id}:{part_id:p.part_id,quantity:p.quantity})),e.responsible_user_id=this._responsibleUserId,e.assignee_pool=this._assigneePool,e.required_completion_fields=this._requiredCompletion,e.rotation_strategy=this._assigneePool.length>=2&&this._rotationStrategy?this._rotationStrategy:null,this._scheduleType==="sensor_based"&&this._triggerType==="compound"){let p=this._compoundConditions.map(Ys).filter(d=>d!==null);if(p.length>0){let d={type:"compound",compound_logic:this._compoundLogic,conditions:p};this._autoCompleteOnRecovery&&(d.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(d.trigger_combinator="all"),e.trigger_config=d}else this._taskId&&(e.trigger_config=null)}else if(this._scheduleType==="sensor_based"&&this._triggerEntityId){let p=this._triggerEntityIds.length>0?this._triggerEntityIds:[this._triggerEntityId],d={entity_id:p[0],entity_ids:p,type:this._triggerType};if(this._triggerAttribute&&(d.attribute=this._triggerAttribute),this._autoCompleteOnRecovery&&(d.auto_complete_on_recovery=!0),this._triggerCombinator==="all"&&(d.trigger_combinator="all"),p.length>1&&(d.entity_logic=this._triggerEntityLogic),this._triggerType==="threshold"){if(this._triggerAbove){let u=parseFloat(this._triggerAbove);isNaN(u)||(d.trigger_above=u)}if(this._triggerBelow){let u=parseFloat(this._triggerBelow);isNaN(u)||(d.trigger_below=u)}if(this._triggerEquals){let u=parseFloat(this._triggerEquals);isNaN(u)||(d.trigger_equals=u)}if(this._triggerNotEquals){let u=parseFloat(this._triggerNotEquals);isNaN(u)||(d.trigger_not_equals=u)}if(this._triggerForMinutes){let u=parseInt(this._triggerForMinutes,10);isNaN(u)||(d.trigger_for_minutes=u)}}else if(this._triggerType==="counter"){if(this._triggerTargetValue){let u=parseFloat(this._triggerTargetValue);isNaN(u)||(d.trigger_target_value=u)}if(d.trigger_delta_mode=this._triggerDeltaMode,this._triggerDeltaMode&&this._triggerBaselineValue){let u=parseFloat(this._triggerBaselineValue);!isNaN(u)&&u>=0&&(d.trigger_baseline_value=u)}}else if(this._triggerType==="state_change"){if(this._triggerFromState&&(d.trigger_from_state=this._triggerFromState),this._triggerToState&&(d.trigger_to_state=this._triggerToState),this._triggerTargetChanges){let u=parseInt(this._triggerTargetChanges,10);isNaN(u)||(d.trigger_target_changes=u)}if(this._triggerForMinutes){let u=parseInt(this._triggerForMinutes,10);isNaN(u)||(d.trigger_for_minutes=u)}}else if(this._triggerType==="runtime"){if(this._triggerRuntimeHours){let m=parseFloat(this._triggerRuntimeHours);isNaN(m)||(d.trigger_runtime_hours=m)}let u=this._triggerOnStates.split(",").map(m=>m.trim()).filter(Boolean);u.length>0&&(d.trigger_on_states=u)}e.trigger_config=d}else this._taskId&&(e.trigger_config=null);if(this.scheduleTimeEnabled&&this._scheduleType==="time_based"){let p=this._scheduleTime.trim();e.schedule_time=/^([01]\d|2[0-3]):[0-5]\d$/.test(p)?p:null}if(this.checklistsEnabled){let p=this._checklistText.split(`
|
||||
`).map(d=>d.trim()).filter(Boolean).slice(0,100);e.checklist=p.length?p:null}if(this.completionActionsEnabled){let p=this._actionService.trim();if(p&&/^[a-z][a-z0-9_]*\.[a-z0-9_]+$/.test(p)){let f={service:p},v=this._actionTargetEntity.trim();v&&(f.target={entity_id:v});let y=this._buildActionData();Object.keys(y).length>0&&(f.data=y),e.on_complete_action=f}else e.on_complete_action=null;let d={};this._qcNotes.trim()&&(d.notes=this._qcNotes.trim());let u=parseFloat(this._qcCost);!isNaN(u)&&u>=0&&(d.cost=u);let m=parseInt(this._qcDuration,10);!isNaN(m)&&m>=0&&(d.duration=m),this._qcFeedback&&(d.feedback=this._qcFeedback),e.quick_complete_defaults=Object.keys(d).length?d:null}let i=await this.hass.connection.sendMessagePromise(e),n=this._taskId||i?.task_id,o=this._environmentalEntity!==this._environmentalInitial||this._environmentalAttribute!==this._environmentalAttributeInitial;if(n&&this._scheduleType==="sensor_based"&&o)try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_environmental_entity",entry_id:this._entryId,task_id:n,environmental_entity:this._environmentalEntity||null,environmental_attribute:this._environmentalAttribute||null}),this._environmentalInitial=this._environmentalEntity,this._environmentalAttributeInitial=this._environmentalAttribute}catch{}if(n&&this._adaptiveSnapshot()!==this._adaptiveInitial){let p=parseFloat(this._adaptiveAlpha),d=parseInt(this._adaptiveMin,10),u=parseInt(this._adaptiveMax,10);try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/set_adaptive",entry_id:this._entryId,task_id:n,enabled:this._adaptiveEnabled,...p>=.1&&p<=.9?{ewa_alpha:p}:{},...!isNaN(d)&&d>=1?{min_interval_days:d}:{},...!isNaN(u)&&u>=1?{max_interval_days:u}:{},seasonal_enabled:this._adaptiveSeasonal,sensor_prediction_enabled:this._adaptivePrediction}),this._adaptiveInitial=this._adaptiveSnapshot()}catch{}}this._open=!1,this.dispatchEvent(new CustomEvent("task-saved"))}catch(e){this._error=N(e,this._lang,a("save_error",this._lang))}finally{this._loading=!1}}}_close(){this._open=!1,this._pickerProbeTimer!==void 0&&(clearTimeout(this._pickerProbeTimer),this._pickerProbeTimer=void 0),this._pickerProbeStrikes=0}_renderTriggerFields(){if(this._scheduleType!=="sensor_based")return _;let e=this._lang,t=this._triggerType==="compound";return l`
|
||||
<h3>${a("trigger_configuration",e)}</h3>
|
||||
<div class="select-row">
|
||||
<label>${a("trigger_type",e)}</label>
|
||||
@@ -1507,7 +1563,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${this._triggerType}
|
||||
@change=${i=>this._triggerType=i.target.value}
|
||||
>
|
||||
${Ds.map(i=>l`<option value=${i} ?selected=${i===this._triggerType}>${a(i,e)}</option>`)}
|
||||
${Ws.map(i=>l`<option value=${i} ?selected=${i===this._triggerType}>${a(i,e)}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
${t?this._renderCompoundEditor():l`
|
||||
@@ -1569,7 +1625,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
</select>
|
||||
</div>
|
||||
`:_}
|
||||
`}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Ms()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l`
|
||||
`}_patchCondition(e,t){this._compoundConditions=this._compoundConditions.map((i,n)=>n===e?{...i,...t}:i)}_addCondition(){this._compoundConditions=[...this._compoundConditions,Vs()]}_removeCondition(e){this._compoundConditions=this._compoundConditions.filter((t,i)=>i!==e)}_renderCompoundEditor(){let e=this._lang;return l`
|
||||
<div class="select-row">
|
||||
<label>${a("compound_logic",e)}</label>
|
||||
<select
|
||||
@@ -1618,7 +1674,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${e.type}
|
||||
@change=${o=>this._patchCondition(t,{type:o.target.value})}
|
||||
>
|
||||
${Si.map(o=>l`<option value=${o} ?selected=${o===e.type}>${a(o,i)}</option>`)}
|
||||
${Pi.map(o=>l`<option value=${o} ?selected=${o===e.type}>${a(o,i)}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
${this._renderConditionTypeFields(e,t)}
|
||||
@@ -1762,12 +1818,12 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
>
|
||||
${["days","weeks","months","years"].map(t=>l`<option value=${t} ?selected=${t===this._intervalUnit}>${a("unit_"+t,e)}</option>`)}
|
||||
</select>
|
||||
</div>`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(vt.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(g._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(d,u,m=0)=>{if(!(!d||m>10)){(d.tagName?.toLowerCase()??"")==="ha-entity-picker"&&u.push(d);for(let f of[d.shadowRoot,d])if(f)for(let v of Array.from(f.children??[]))i(v,u,m+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let d of n)i(d,o);let p=o.length===0||o.some(d=>d.offsetHeight===0);if(e.offsetHeight===0||p){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return _;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,p)=>{let d=new Date(`${o}T12:00:00`).getDay();return`${Le(d===0?6:d-1,e,"short")} ${Y(o,e)}${p===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`<div class="field-help">${a("schedule_preview_ontime",e)}</div>`:_;return l`
|
||||
</div>`}_toggleWeekday(e){this._weekdays=this._weekdays.includes(e)?this._weekdays.filter(t=>t!==e):[...this._weekdays,e]}_previewScheduleDict(){if(this._scheduleType==="one_time")return this._dueDate?{kind:"one_time",due_date:this._dueDate}:null;if(yt.includes(this._scheduleType))return{...this._buildSchedule(),...this._recurrenceExtras()};let e=parseInt(this._intervalDays,10);return this._scheduleType==="manual"||!e||e<=0?null:{kind:"interval",every:e,unit:this._intervalUnit,anchor:this._intervalAnchor,...this._recurrenceExtras()}}updated(e){super.updated?.(e),this._scheduleEntityPickerProbe();for(let t of e.keys())if(g._PREVIEW_RELEVANT.has(String(t))){this._schedulePreviewRefresh();return}}_scheduleEntityPickerProbe(){this._entityPickerFallback||this._pickerProbeTimer!==void 0||!this._open||this._scheduleType!=="sensor_based"||(this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),1500))}_probeEntityPickers(){if(this._pickerProbeTimer=void 0,this._entityPickerFallback||!this._open)return;let e=this.shadowRoot?.querySelector("ha-form.entity-picker-form"),t=(this.shadowRoot?.querySelector(".content")?.offsetHeight??0)>0;if(!e||!t){this._pickerProbeStrikes=0;return}let i=(d,u,m=0)=>{if(!(!d||m>10)){(d.tagName?.toLowerCase()??"")==="ha-entity-picker"&&u.push(d);for(let f of[d.shadowRoot,d])if(f)for(let v of Array.from(f.children??[]))i(v,u,m+1)}},n=[...this.shadowRoot?.querySelectorAll("ha-form.entity-picker-form")??[]],o=[];for(let d of n)i(d,o);let p=o.length===0||o.some(d=>d.offsetHeight===0);if(e.offsetHeight===0||p){if(this._pickerProbeStrikes+=1,this._pickerProbeStrikes>=2){this._entityPickerFallback=!0;return}this._pickerProbeTimer=setTimeout(()=>this._probeEntityPickers(),700)}else this._pickerProbeStrikes=0}_schedulePreviewRefresh(){this._previewTimer&&clearTimeout(this._previewTimer),this._previewTimer=setTimeout(()=>{this._fetchSchedulePreview()},300)}async _fetchSchedulePreview(){let e=this._open?this._previewScheduleDict():null;if(!e){this._schedulePreview=[],this._schedulePreviewEnded=!1;return}let t=++this._previewSeq;try{let i=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/schedule/preview",schedule:e,...this._lastPerformed?{last_performed:this._lastPerformed}:{}});if(t!==this._previewSeq)return;this._schedulePreview=i.occurrences||[],this._schedulePreviewEnded=!!i.series_ended}catch{}}_renderSchedulePreview(){if(this._schedulePreview.length===0)return _;let e=this._lang,t=this.scheduleTimeEnabled&&this._scheduleTime?` ${this._scheduleTime}`:"",i=this._schedulePreview.map((o,p)=>{let d=new Date(`${o}T12:00:00`).getDay();return`${Le(d===0?6:d-1,e,"short")} ${Q(o,e)}${p===0?t:""}`}).join(" \xB7 "),n=this._scheduleType==="time_based"&&this._intervalAnchor==="completion"?l`<div class="field-help">${a("schedule_preview_ontime",e)}</div>`:_;return l`
|
||||
<div class="trigger-live-hint schedule-preview">
|
||||
${a("schedule_preview_title",e)}: ${i}${this._schedulePreviewEnded?l` <span class="field-help">${a("schedule_preview_ends",e)}</span>`:_}
|
||||
${n}
|
||||
</div>
|
||||
`}_buildSchedule(){let e=i=>{let n=parseInt(this._calOffset,10)||0;return n&&(i.offset=Math.max(-15,Math.min(n,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,n)=>i-n)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||vt.includes(this._scheduleType)))return _;let i=Bs(e);return l`
|
||||
`}_buildSchedule(){let e=i=>{let n=parseInt(this._calOffset,10)||0;return n&&(i.offset=Math.max(-15,Math.min(n,15))),i};if(this._scheduleType==="weekdays")return e({kind:"weekdays",weekdays:[...this._weekdays].sort((i,n)=>i-n)});if(this._scheduleType==="nth_weekday")return e({kind:"nth_weekday",nth:parseInt(this._nth,10),weekday:parseInt(this._nthWeekday,10)});let t={kind:"day_of_month",day:this._domLastDay?-1:parseInt(this._domDay,10)||1};return this._domBusiness&&(t.business=!0),e(t)}_recurrenceExtras(){let e={};if(this._seasonMonths.length&&(e.season_months=[...this._seasonMonths].sort((t,i)=>t-i)),this._endsMode==="count"){let t=parseInt(this._endsCount,10);t>=1&&(e.ends={count:t})}else this._endsMode==="until"&&this._endsUntil&&(e.ends={until:this._endsUntil});return e}_toggleSeasonMonth(e){this._seasonMonths=this._seasonMonths.includes(e)?this._seasonMonths.filter(t=>t!==e):[...this._seasonMonths,e]}_renderRecurrenceExtras(){let e=this._lang;if(!(this._scheduleType==="time_based"||yt.includes(this._scheduleType)))return _;let i=Js(e);return l`
|
||||
<label class="field-label">${a("season_window_label",e)}</label>
|
||||
<div class="field-help">${a("season_window_hint",e)}</div>
|
||||
<div class="weekday-chips season-chips">
|
||||
@@ -1802,7 +1858,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${this._endsUntil}
|
||||
@input=${n=>this._endsUntil=n.target.value}
|
||||
></ms-textfield>`:_}
|
||||
`}_renderCalendarFields(){let e=this._lang,t=Us(e);if(this._scheduleType==="weekdays")return l`
|
||||
`}_renderCalendarFields(){let e=this._lang,t=Qs(e);if(this._scheduleType==="weekdays")return l`
|
||||
<label class="field-label">${a("recurrence_on_days",e)}</label>
|
||||
<div class="weekday-chips">
|
||||
${t.map((i,n)=>l`
|
||||
@@ -1854,7 +1910,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
max="15"
|
||||
.value=${this._calOffset}
|
||||
@input=${t=>this._calOffset=t.target.value}
|
||||
></ms-textfield>`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return _;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return _;let t=this.hass.states[e];if(!t)return _;let i=this._lang,n=t.attributes?.unit_of_measurement,o=typeof n=="string"&&n?` ${n}`:"",p=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,d=typeof p=="number"?p:parseFloat(String(p)),u=p!=="unknown"&&p!=="unavailable"&&p!=null&&!isNaN(d),m=v=>Number.isInteger(v)?String(v):String(Math.round(v*10)/10),f=[];if(this._triggerType==="threshold"){let v=parseFloat(this._triggerAbove),b=parseFloat(this._triggerBelow);if(isNaN(v)&&isNaN(b))return _;u&&f.push(a("trigger_hint_now",i).replace("{value}",m(d)+o)),isNaN(v)||f.push(a("trigger_hint_above",i).replace("{target}",m(v)+o)),isNaN(b)||f.push(a("trigger_hint_below",i).replace("{target}",m(b)+o))}else if(this._triggerType==="counter"){let v=parseFloat(this._triggerTargetValue);if(isNaN(v))return _;this._triggerDeltaMode?this._taskId?f.push(a("trigger_hint_counter_delta_edit",i).replace("{target}",m(v)+o)):u?f.push(a("trigger_hint_counter_delta",i).replace("{value}",m(d)+o).replace("{due}",m(d+v)+o).replace("{target}",m(v)+o)):f.push(a("trigger_hint_counter_delta_edit",i).replace("{target}",m(v)+o)):(u&&f.push(a("trigger_hint_now",i).replace("{value}",m(d)+o)),f.push(a("trigger_hint_counter_abs",i).replace("{target}",m(v)+o)))}else if(this._triggerType==="runtime"){let v=parseFloat(this._triggerRuntimeHours);if(isNaN(v))return _;f.push(a("trigger_hint_runtime",i).replace("{hours}",m(v))),f.push(a("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let v=parseInt(this._triggerTargetChanges,10)||1,b=this._triggerToState.trim();f.push((b?a("trigger_hint_state_change_to",i).replace("{state}",b):a("trigger_hint_state_change",i)).replace("{count}",String(v))),f.push(a("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return f.length?l`<div class="trigger-live-hint">${f.join(" ")}</div>`:_}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l`
|
||||
></ms-textfield>`}_renderTriggerLiveHint(){if(this._triggerType==="compound")return _;let e=this._triggerEntityId||this._triggerEntityIds[0];if(!e||!this.hass?.states)return _;let t=this.hass.states[e];if(!t)return _;let i=this._lang,n=t.attributes?.unit_of_measurement,o=typeof n=="string"&&n?` ${n}`:"",p=this._triggerAttribute?t.attributes?.[this._triggerAttribute]:t.state,d=typeof p=="number"?p:parseFloat(String(p)),u=p!=="unknown"&&p!=="unavailable"&&p!=null&&!isNaN(d),m=v=>Number.isInteger(v)?String(v):String(Math.round(v*10)/10),f=[];if(this._triggerType==="threshold"){let v=parseFloat(this._triggerAbove),y=parseFloat(this._triggerBelow);if(isNaN(v)&&isNaN(y))return _;u&&f.push(a("trigger_hint_now",i).replace("{value}",m(d)+o)),isNaN(v)||f.push(a("trigger_hint_above",i).replace("{target}",m(v)+o)),isNaN(y)||f.push(a("trigger_hint_below",i).replace("{target}",m(y)+o))}else if(this._triggerType==="counter"){let v=parseFloat(this._triggerTargetValue);if(isNaN(v))return _;this._triggerDeltaMode?this._taskId?f.push(a("trigger_hint_counter_delta_edit",i).replace("{target}",m(v)+o)):u?f.push(a("trigger_hint_counter_delta",i).replace("{value}",m(d)+o).replace("{due}",m(d+v)+o).replace("{target}",m(v)+o)):f.push(a("trigger_hint_counter_delta_edit",i).replace("{target}",m(v)+o)):(u&&f.push(a("trigger_hint_now",i).replace("{value}",m(d)+o)),f.push(a("trigger_hint_counter_abs",i).replace("{target}",m(v)+o)))}else if(this._triggerType==="runtime"){let v=parseFloat(this._triggerRuntimeHours);if(isNaN(v))return _;f.push(a("trigger_hint_runtime",i).replace("{hours}",m(v))),f.push(a("trigger_hint_state_now",i).replace("{value}",String(t.state)))}else if(this._triggerType==="state_change"){let v=parseInt(this._triggerTargetChanges,10)||1,y=this._triggerToState.trim();f.push((y?a("trigger_hint_state_change_to",i).replace("{state}",y):a("trigger_hint_state_change",i)).replace("{count}",String(v))),f.push(a("trigger_hint_state_now",i).replace("{value}",String(t.state)))}return f.length?l`<div class="trigger-live-hint">${f.join(" ")}</div>`:_}_renderTriggerTypeFields(){let e=this._lang;return this._triggerType==="threshold"?l`
|
||||
<ms-textfield
|
||||
label="${a("trigger_above",e)}"
|
||||
type="number"
|
||||
@@ -1955,7 +2011,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
<div class="dialog-title">${t}</div>
|
||||
<div class="content">
|
||||
${this._error?l`<div class="error">${this._error}</div>`:_}
|
||||
${this._objectChoices.length>0?l`
|
||||
${this._taskId===null&&this._objectChoices.length>0?l`
|
||||
<div class="select-row">
|
||||
<label>${a("object",e)}</label>
|
||||
<select
|
||||
@@ -1978,7 +2034,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${this._type}
|
||||
@change=${i=>this._type=i.target.value}
|
||||
>
|
||||
${js.map(i=>l`<option value=${i} ?selected=${i===this._type}>${a(i,e)}</option>`)}
|
||||
${zs.map(i=>l`<option value=${i} ?selected=${i===this._type}>${a(i,e)}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
${this._type==="reading"?l`
|
||||
@@ -2012,7 +2068,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${this._priority}
|
||||
@change=${i=>this._priority=i.target.value}
|
||||
>
|
||||
${Ns.map(i=>l`<option value=${i} ?selected=${i===this._priority}>${a("priority_"+i,e)}</option>`)}
|
||||
${Us.map(i=>l`<option value=${i} ?selected=${i===this._priority}>${a("priority_"+i,e)}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
@@ -2031,7 +2087,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.value=${this._scheduleType}
|
||||
@change=${i=>this._scheduleType=i.target.value}
|
||||
>
|
||||
${Hs.map(i=>l`<option value=${i} ?selected=${i===this._scheduleType}>${a(i,e)}</option>`)}
|
||||
${Bs.map(i=>l`<option value=${i} ?selected=${i===this._scheduleType}>${a(i,e)}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
${this._scheduleType==="time_based"?l`
|
||||
@@ -2103,14 +2159,14 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
${this._renderPhasesEditor(e)}
|
||||
<h3>${a("require_on_completion",e)}</h3>
|
||||
<div class="required-completion">
|
||||
${Ei.map(i=>l`
|
||||
${ft.map(i=>l`
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this._requiredCompletion.includes(i)}
|
||||
@change=${n=>this._toggleRequired(i,n.target.checked)}
|
||||
/>
|
||||
<span>${a(Ye[i],e)}</span>
|
||||
<span>${a(Ne[i],e)}</span>
|
||||
</label>
|
||||
`)}
|
||||
</div>
|
||||
@@ -2169,7 +2225,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
<ha-form
|
||||
class="entity-picker-form"
|
||||
.hass=${this.hass}
|
||||
.schema=${[{name:"environmental_entity",selector:{entity:{domain:wi,device_class:$i}}}]}
|
||||
.schema=${[{name:"environmental_entity",selector:{entity:{domain:Ti,device_class:Ii}}}]}
|
||||
.data=${{environmental_entity:this._environmentalEntity}}
|
||||
.computeLabel=${()=>a("environmental_entity_optional",e)}
|
||||
.computeHelper=${()=>a("environmental_entity_helper",e)}
|
||||
@@ -2180,6 +2236,9 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
${this._renderAdaptiveSection(e)}
|
||||
<ms-textfield
|
||||
label="${a("notes_optional",e)}"
|
||||
multiline
|
||||
.rows=${3}
|
||||
.helper=${a("notes_markdown_hint",e)}
|
||||
.value=${this._notes}
|
||||
@input=${i=>this._notes=i.target.value}
|
||||
></ms-textfield>
|
||||
@@ -2222,6 +2281,15 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
</button>
|
||||
</div>
|
||||
`}
|
||||
<label class="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${this._requireTagScan}
|
||||
@change=${i=>this._requireTagScan=i.target.checked}
|
||||
/>
|
||||
<span>${a("require_tag_scan",e)}</span>
|
||||
</label>
|
||||
${this._requireTagScan?l`<div class="field-help">${a("require_tag_scan_help",e)}</div>`:_}
|
||||
<label class="toggle-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -2391,6 +2459,15 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
min-height: 56px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.phase-req-toggle {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.phase-req-fields {
|
||||
margin: 2px 0 0 22px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.phase-remove {
|
||||
--mdc-icon-button-size: 36px;
|
||||
color: var(--secondary-text-color);
|
||||
@@ -2609,7 +2686,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`,c([x({attribute:!1})],g.prototype,"hass",2),c([x({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),c([x({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),c([x({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),c([x({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),c([h()],g.prototype,"parts",2),c([h()],g.prototype,"_foreignOwners",2),c([h()],g.prototype,"_open",2),c([h()],g.prototype,"_entityPickerFallback",2),c([h()],g.prototype,"_loading",2),c([h()],g.prototype,"_error",2),c([h()],g.prototype,"_entryId",2),c([h()],g.prototype,"_taskId",2),c([h()],g.prototype,"_objectChoices",2),c([h()],g.prototype,"_name",2),c([h()],g.prototype,"_type",2),c([h()],g.prototype,"_scheduleType",2),c([h()],g.prototype,"_intervalDays",2),c([h()],g.prototype,"_intervalUnit",2),c([h()],g.prototype,"_dueDate",2),c([h()],g.prototype,"_warningDays",2),c([h()],g.prototype,"_earliestCompletionDays",2),c([h()],g.prototype,"_intervalAnchor",2),c([h()],g.prototype,"_weekdays",2),c([h()],g.prototype,"_nth",2),c([h()],g.prototype,"_nthWeekday",2),c([h()],g.prototype,"_domDay",2),c([h()],g.prototype,"_domLastDay",2),c([h()],g.prototype,"_domBusiness",2),c([h()],g.prototype,"_calOffset",2),c([h()],g.prototype,"_seasonMonths",2),c([h()],g.prototype,"_endsMode",2),c([h()],g.prototype,"_endsCount",2),c([h()],g.prototype,"_endsUntil",2),c([h()],g.prototype,"_schedulePreview",2),c([h()],g.prototype,"_schedulePreviewEnded",2),c([h()],g.prototype,"_notes",2),c([h()],g.prototype,"_documentationUrl",2),c([h()],g.prototype,"_customIcon",2),c([h()],g.prototype,"_priority",2),c([h()],g.prototype,"_labels",2),c([h()],g.prototype,"_enabled",2),c([h()],g.prototype,"_triggerEntityId",2),c([h()],g.prototype,"_triggerEntityIds",2),c([h()],g.prototype,"_triggerEntityLogic",2),c([h()],g.prototype,"_triggerAttribute",2),c([h()],g.prototype,"_triggerType",2),c([h()],g.prototype,"_triggerAbove",2),c([h()],g.prototype,"_triggerBelow",2),c([h()],g.prototype,"_triggerEquals",2),c([h()],g.prototype,"_triggerNotEquals",2),c([h()],g.prototype,"_triggerForMinutes",2),c([h()],g.prototype,"_triggerCombinator",2),c([h()],g.prototype,"_triggerTargetValue",2),c([h()],g.prototype,"_triggerDeltaMode",2),c([h()],g.prototype,"_triggerBaselineValue",2),c([h()],g.prototype,"_liveBaselineValue",2),c([h()],g.prototype,"_autoCompleteOnRecovery",2),c([h()],g.prototype,"_triggerFromState",2),c([h()],g.prototype,"_triggerToState",2),c([h()],g.prototype,"_triggerTargetChanges",2),c([h()],g.prototype,"_triggerRuntimeHours",2),c([h()],g.prototype,"_triggerOnStates",2),c([h()],g.prototype,"_compoundLogic",2),c([h()],g.prototype,"_compoundConditions",2),c([h()],g.prototype,"_suggestedAttributes",2),c([h()],g.prototype,"_availableAttributes",2),c([h()],g.prototype,"_entityDomain",2),c([h()],g.prototype,"_lastPerformed",2),c([h()],g.prototype,"_nfcTagId",2),c([h()],g.prototype,"_readingUnit",2),c([h()],g.prototype,"_consumesParts",2),c([h()],g.prototype,"_partsLoadFailed",2),c([h()],g.prototype,"_availableTags",2),c([h()],g.prototype,"_responsibleUserId",2),c([h()],g.prototype,"_assigneePool",2),c([h()],g.prototype,"_rotationStrategy",2),c([h()],g.prototype,"_availableUsers",2),c([h()],g.prototype,"_checklistText",2),c([h()],g.prototype,"_phaseDefs",2),c([h()],g.prototype,"_phaseSeq",2),c([h()],g.prototype,"_requiredCompletion",2),c([h()],g.prototype,"_scheduleTime",2),c([h()],g.prototype,"_actionService",2),c([h()],g.prototype,"_actionTargetEntity",2),c([h()],g.prototype,"_actionData",2),c([h()],g.prototype,"_actionDataJsonFallback",2),c([h()],g.prototype,"_actionTesting",2),c([h()],g.prototype,"_actionTestResult",2),c([h()],g.prototype,"_actionTestError",2),c([h()],g.prototype,"_qcNotes",2),c([h()],g.prototype,"_qcCost",2),c([h()],g.prototype,"_qcDuration",2),c([h()],g.prototype,"_qcFeedback",2),c([h()],g.prototype,"_environmentalEntity",2),c([h()],g.prototype,"_environmentalAttribute",2),c([h()],g.prototype,"_adaptiveEnabled",2),c([h()],g.prototype,"_adaptiveAlpha",2),c([h()],g.prototype,"_adaptiveMin",2),c([h()],g.prototype,"_adaptiveMax",2),c([h()],g.prototype,"_adaptiveSeasonal",2),c([h()],g.prototype,"_adaptivePrediction",2),c([h()],g.prototype,"_conditionAttrOptions",2);yt=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",yt)});var $,Ti=w(()=>{"use strict";L();U();R();le();Ge();ft();$=class extends A{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this
Line truncated
|
||||
`,c([x({attribute:!1})],g.prototype,"hass",2),c([x({type:Boolean,attribute:"checklists-enabled"})],g.prototype,"checklistsEnabled",2),c([x({type:Boolean,attribute:"schedule-time-enabled"})],g.prototype,"scheduleTimeEnabled",2),c([x({type:Boolean,attribute:"completion-actions-enabled"})],g.prototype,"completionActionsEnabled",2),c([x({type:Number,attribute:"default-warning-days"})],g.prototype,"defaultWarningDays",2),c([h()],g.prototype,"parts",2),c([h()],g.prototype,"_foreignOwners",2),c([h()],g.prototype,"_open",2),c([h()],g.prototype,"_entityPickerFallback",2),c([h()],g.prototype,"_loading",2),c([h()],g.prototype,"_error",2),c([h()],g.prototype,"_entryId",2),c([h()],g.prototype,"_taskId",2),c([h()],g.prototype,"_objectChoices",2),c([h()],g.prototype,"_name",2),c([h()],g.prototype,"_type",2),c([h()],g.prototype,"_scheduleType",2),c([h()],g.prototype,"_intervalDays",2),c([h()],g.prototype,"_intervalUnit",2),c([h()],g.prototype,"_dueDate",2),c([h()],g.prototype,"_warningDays",2),c([h()],g.prototype,"_earliestCompletionDays",2),c([h()],g.prototype,"_intervalAnchor",2),c([h()],g.prototype,"_weekdays",2),c([h()],g.prototype,"_nth",2),c([h()],g.prototype,"_nthWeekday",2),c([h()],g.prototype,"_domDay",2),c([h()],g.prototype,"_domLastDay",2),c([h()],g.prototype,"_domBusiness",2),c([h()],g.prototype,"_calOffset",2),c([h()],g.prototype,"_seasonMonths",2),c([h()],g.prototype,"_endsMode",2),c([h()],g.prototype,"_endsCount",2),c([h()],g.prototype,"_endsUntil",2),c([h()],g.prototype,"_schedulePreview",2),c([h()],g.prototype,"_schedulePreviewEnded",2),c([h()],g.prototype,"_notes",2),c([h()],g.prototype,"_documentationUrl",2),c([h()],g.prototype,"_customIcon",2),c([h()],g.prototype,"_priority",2),c([h()],g.prototype,"_labels",2),c([h()],g.prototype,"_enabled",2),c([h()],g.prototype,"_triggerEntityId",2),c([h()],g.prototype,"_triggerEntityIds",2),c([h()],g.prototype,"_triggerEntityLogic",2),c([h()],g.prototype,"_triggerAttribute",2),c([h()],g.prototype,"_triggerType",2),c([h()],g.prototype,"_triggerAbove",2),c([h()],g.prototype,"_triggerBelow",2),c([h()],g.prototype,"_triggerEquals",2),c([h()],g.prototype,"_triggerNotEquals",2),c([h()],g.prototype,"_triggerForMinutes",2),c([h()],g.prototype,"_triggerCombinator",2),c([h()],g.prototype,"_triggerTargetValue",2),c([h()],g.prototype,"_triggerDeltaMode",2),c([h()],g.prototype,"_triggerBaselineValue",2),c([h()],g.prototype,"_liveBaselineValue",2),c([h()],g.prototype,"_autoCompleteOnRecovery",2),c([h()],g.prototype,"_triggerFromState",2),c([h()],g.prototype,"_triggerToState",2),c([h()],g.prototype,"_triggerTargetChanges",2),c([h()],g.prototype,"_triggerRuntimeHours",2),c([h()],g.prototype,"_triggerOnStates",2),c([h()],g.prototype,"_compoundLogic",2),c([h()],g.prototype,"_compoundConditions",2),c([h()],g.prototype,"_suggestedAttributes",2),c([h()],g.prototype,"_availableAttributes",2),c([h()],g.prototype,"_entityDomain",2),c([h()],g.prototype,"_lastPerformed",2),c([h()],g.prototype,"_nfcTagId",2),c([h()],g.prototype,"_requireTagScan",2),c([h()],g.prototype,"_readingUnit",2),c([h()],g.prototype,"_consumesParts",2),c([h()],g.prototype,"_partsLoadFailed",2),c([h()],g.prototype,"_availableTags",2),c([h()],g.prototype,"_responsibleUserId",2),c([h()],g.prototype,"_assigneePool",2),c([h()],g.prototype,"_rotationStrategy",2),c([h()],g.prototype,"_availableUsers",2),c([h()],g.prototype,"_checklistText",2),c([h()],g.prototype,"_phaseDefs",2),c([h()],g.prototype,"_phaseSeq",2),c([h()],g.prototype,"_requiredCompletion",2),c([h()],g.prototype,"_scheduleTime",2),c([h()],g.prototype,"_actionService",2),c([h()],g.prototype,"_actionTargetEntity",2),c([h()],g.prototype,"_actionData",2),c([h()],g.prototype,"_actionDataJsonFallback",2),c([h()],g.prototype,"_actionTesting",2),c([h()],g.prototype,"_actionTestResult",2),c([h()],g.prototype,"_actionTestError",2),c([h()],g.prototype,"_qcNotes",2),c([h()],g.prototype,"_qcCost",2),c([h()],g.prototype,"_qcDuration",2),c([h()],g.prototype,"_qcFeedback",2),c([h()],g.prototype,"_environmentalEntity",2),c([h()],g.prototype,"_environmentalAttribute",2),c([h()],g.prototype,"_adaptiveEnabled",2),c([h()],g.prototype,"_adaptiveAlpha",2),c([h()],g.prototype,"_adaptiveMin",2),c([h()],g.prototype,"_adaptiveMax",2),c([h()],g.prototype,"_adaptiveSeasonal",2),c([h()],g.prototype,"_adaptivePrediction",2),c([h()],g.prototype,"_conditionAttrOptions",2);bt=g;customElements.get("maintenance-task-dialog")||customElements.define("maintenance-task-dialog",bt)});var $,qi=w(()=>{"use strict";P();W();j();de();Qe();vt();$=class extends A{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this.requireTagScan=!1;this.viaTagScan=!1;this._open=!1;this._notes="";this._cost="";this._duration="";t
Line truncated
|
||||
type="button"
|
||||
class="cost-suggestion"
|
||||
@click=${()=>this._cost=t.toFixed(2)}
|
||||
@@ -2617,6 +2694,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
<ha-dialog open @closed=${this._close}>
|
||||
<div class="dialog-title">${a("complete_title",e)}${this.taskName}</div>
|
||||
${this.phaseLabel?l`<div class="phase-line">${a("phase_current",e)}: ${this.phaseLabel}</div>`:_}
|
||||
${this.requireTagScan&&!this.viaTagScan?l`<div class="scan-required-note">${a("require_tag_scan_hint",e)}</div>`:_}
|
||||
<div class="content">
|
||||
${this._error?l`<div class="error">${this._error}</div>`:_}
|
||||
${this.checklist.length>0?l`
|
||||
@@ -2639,7 +2717,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
</label>`:_}
|
||||
${this.parts.length?l`<div class="used-parts">
|
||||
<span class="field-label">${a("complete_parts_used",e)}</span>
|
||||
${this.parts.map(t=>{let i=B({part_id:t.id,entry_id:t.entry_id}),n=this._usedParts[i],o=n!==void 0,p=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return l`<div class="used-part-row">
|
||||
${this.parts.map(t=>{let i=V({part_id:t.id,entry_id:t.entry_id}),n=this._usedParts[i],o=n!==void 0,p=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return l`<div class="used-part-row">
|
||||
<label class="used-part-check">
|
||||
<input type="checkbox" .checked=${o}
|
||||
@change=${d=>{let u={...this._usedParts};d.target.checked?u[i]=u[i]||p:delete u[i],this._usedParts=u}} />
|
||||
@@ -2736,13 +2814,13 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
<ha-button
|
||||
@click=${this._complete}
|
||||
.disabled=${this._loading||this._missingRequired.length>0}
|
||||
title=${this._missingRequired.length?this._missingRequired.map(t=>a("err_required",e).replace("{field}",a(Ye[t]??t,e))).join(" \xB7 "):""}
|
||||
title=${this._missingRequired.length?this._missingRequired.map(t=>a("err_required",e).replace("{field}",a(Ne[t]??t,e))).join(" \xB7 "):""}
|
||||
>
|
||||
${this._loading?a("completing",e):a("complete",e)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`}};$.styles=[fi,S`
|
||||
`}};$.styles=[xi,S`
|
||||
.req-mark {
|
||||
color: var(--error-color, #f44336);
|
||||
margin-left: 2px;
|
||||
@@ -2766,6 +2844,14 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
font-weight: 500;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.scan-required-note {
|
||||
margin: -4px 0 12px;
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 152, 0, 0.12);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
.phase-line {
|
||||
margin-top: -8px;
|
||||
padding-bottom: 12px;
|
||||
@@ -2913,7 +2999,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
color: var(--text-primary-color, #fff);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
`],c([x({attribute:!1})],$.prototype,"hass",2),c([x()],$.prototype,"entryId",2),c([x()],$.prototype,"taskId",2),c([x()],$.prototype,"taskName",2),c([x()],$.prototype,"lang",2),c([x({type:Array})],$.prototype,"checklist",2),c([x({type:Boolean})],$.prototype,"adaptiveEnabled",2),c([x()],$.prototype,"taskType",2),c([x()],$.prototype,"readingUnit",2),c([x({attribute:!1})],$.prototype,"restockDefault",2),c([x({attribute:!1})],$.prototype,"restockUnitCost",2),c([x()],$.prototype,"currencySymbol",2),c([x({attribute:!1})],$.prototype,"parts",2),c([x({attribute:!1})],$.prototype,"consumesParts",2),c([x({type:Array})],$.prototype,"consumesInfo",2),c([x({type:Array})],$.prototype,"requiredFields",2),c([x()],$.prototype,"phaseLabel",2),c([h()],$.prototype,"_open",2),c([h()],$.prototype,"_notes",2),c([h()],$.prototype,"_cost",2),c([h()],$.prototype,"_duration",2),c([h()],$.prototype,"_loading",2),c([h()],$.prototype,"_error",2),c([h()],$.prototype,"_checklistState",2),c([h()],$.prototype,"_feedback",2),c([h()],$.prototype,"_photoDocId",2),c([h()],$.prototype,"_photoPreview",2),c([h()],$.prototype,"_photoUploading",2),c([h()],$.prototype,"_readingValue",2),c([h()],$.prototype,"_restockQty",2),c([h()],$.prototype,"_completedAt",2),c([h()],$.prototype,"_usedParts",2),c([x({attribute:!1})],$.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",$)});var z,Ci=w(()=>{"use strict";L();U();R();le();z=class extends A{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return j(this.hass)}openEdit(e){this._draft={...e},this._originalSnapshot={...e},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let e=this._draft;if(e)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),i=[];for(let o of t.parts||[]){let p=o.entry_id===e.entry_id,d=o.consumers.some(u=>u.entry_id===e.entry_id&&u.task_id===e.task_id);!p&&!d||i.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!p,object_name:o.object_name})}for(let o of e.used_parts||[]){let p=o.entry_id||e.entry_id;i.some(d=>d.part_id===o.part_id&&d.entry_id===p)||i.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:p,foreign:p!==e.entry_id,object_name:null})}let n={};for(let o of e.used_parts||[])n[`${o.entry_id||e.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={type:"maintenance_supporter/task/history/update",entry_id:this._draft.entry_id,task_id:this._draft.task_id,original_timestamp:this._originalSnapshot.original_timestamp};if(this._draft.timestamp!==this._originalSnapshot.timestamp&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),this.dispatchEvent(new CustomEvent("history-entry-saved",{detail:{entry_id:this._draft.entry_id,task_id:this._draft.task_id,new_timestamp:this._draft.timestamp},bubbles:!0,composed:!0})),this.close()}catch(e){this._error=q(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return _;let e=this._lang,t=this._draft;return l`
|
||||
`],c([x({attribute:!1})],$.prototype,"hass",2),c([x()],$.prototype,"entryId",2),c([x()],$.prototype,"taskId",2),c([x()],$.prototype,"taskName",2),c([x()],$.prototype,"lang",2),c([x({type:Array})],$.prototype,"checklist",2),c([x({type:Boolean})],$.prototype,"adaptiveEnabled",2),c([x()],$.prototype,"taskType",2),c([x()],$.prototype,"readingUnit",2),c([x({attribute:!1})],$.prototype,"restockDefault",2),c([x({attribute:!1})],$.prototype,"restockUnitCost",2),c([x()],$.prototype,"currencySymbol",2),c([x({attribute:!1})],$.prototype,"parts",2),c([x({attribute:!1})],$.prototype,"consumesParts",2),c([x({type:Array})],$.prototype,"consumesInfo",2),c([x({type:Array})],$.prototype,"requiredFields",2),c([x()],$.prototype,"phaseLabel",2),c([x({type:Boolean})],$.prototype,"requireTagScan",2),c([x({type:Boolean})],$.prototype,"viaTagScan",2),c([h()],$.prototype,"_open",2),c([h()],$.prototype,"_notes",2),c([h()],$.prototype,"_cost",2),c([h()],$.prototype,"_duration",2),c([h()],$.prototype,"_loading",2),c([h()],$.prototype,"_error",2),c([h()],$.prototype,"_checklistState",2),c([h()],$.prototype,"_feedback",2),c([h()],$.prototype,"_photoDocId",2),c([h()],$.prototype,"_photoPreview",2),c([h()],$.prototype,"_photoUploading",2),c([h()],$.prototype,"_readingValue",2),c([h()],$.prototype,"_restockQty",2),c([h()],$.prototype,"_completedAt",2),c([h()],$.prototype,"_usedParts",2),c([x({attribute:!1})],$.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",$)});var U,Ri=w(()=>{"use strict";P();W();j();de();U=class extends A{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal=""}get _lang(){return D(this.hass)}openEdit(e){this._draft={...e},this._originalSnapshot={...e},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._loadPartOptions()}async _loadPartOptions(){let e=this._draft;if(e)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),i=[];for(let o of t.parts||[]){let p=o.entry_id===e.entry_id,d=o.consumers.some(u=>u.entry_id===e.entry_id&&u.task_id===e.task_id);!p&&!d||i.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!p,object_name:o.object_name})}for(let o of e.used_parts||[]){let p=o.entry_id||e.entry_id;i.some(d=>d.part_id===o.part_id&&d.entry_id===p)||i.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:p,foreign:p!==e.entry_id,object_name:null})}let n={};for(let o of e.used_parts||[])n[`${o.entry_id||e.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=i,this._partQty=n,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,e])=>e>0).sort(([e],[t])=>e.localeCompare(t)))}close(){this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null}_set(e,t){this._draft&&(this._draft={...this._draft,[e]:t})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let e={type:"maintenance_supporter/task/history/update",entry_id:this._draft.entry_id,task_id:this._draft.task_id,original_timestamp:this._originalSnapshot.original_timestamp};if(this._draft.timestamp!==this._originalSnapshot.timestamp&&(e.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(e.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(e.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(e.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(e.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(e.used_parts=(this._partOptions||[]).filter(i=>(this._partQty[`${i.entry_id}:${i.part_id}`]||0)>0).map(i=>({part_id:i.part_id,quantity:this._partQty[`${i.entry_id}:${i.part_id}`],...i.foreign?{entry_id:i.entry_id}:{}}))),Object.keys(e).filter(i=>!["type","entry_id","task_id","original_timestamp"].includes(i)).length===0){this.close();return}await this.hass.connection.sendMessagePromise(e),this.dispatchEvent(new CustomEvent("history-entry-saved",{detail:{entry_id:this._draft.entry_id,task_id:this._draft.task_id,new_timestamp:this._draft.timestamp},bubbles:!0,composed:!0})),this.close()}catch(e){this._error=N(e,this._lang)}finally{this._saving=!1}}}render(){if(!this._open||!this._draft)return _;let e=this._lang,t=this._draft;return l`
|
||||
<div class="backdrop" @click=${this.close}></div>
|
||||
<div class="dialog" role="dialog" aria-modal="true">
|
||||
<h2>${a("history_edit_title",e)||"Edit history entry"}</h2>
|
||||
@@ -2975,7 +3061,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`}};z.styles=S`
|
||||
`}};U.styles=S`
|
||||
:host { display: contents; }
|
||||
.backdrop {
|
||||
position: fixed; inset: 0;
|
||||
@@ -3051,7 +3137,7 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
.part-row-edit input[type="checkbox"] { width: auto; }
|
||||
.part-label { flex: 1; color: var(--primary-text-color); }
|
||||
.part-qty { width: 76px; }
|
||||
`,c([x({attribute:!1})],z.prototype,"hass",2),c([h()],z.prototype,"_open",2),c([h()],z.prototype,"_saving",2),c([h()],z.prototype,"_error",2),c([h()],z.prototype,"_draft",2),c([h()],z.prototype,"_partOptions",2),c([h()],z.prototype,"_partQty",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",z)});function Ii(r,s,e){let t=new Blob([r],{type:e}),i=URL.createObjectURL(t),n=document.createElement("a");n.href=i,n.download=s,n.target="_blank",n.rel="noopener",n.style.display="none",document.body.appendChild(n),n.dispatchEvent(new MouseEvent("click")),document.body.removeChild(n),setTimeout(()=>URL.revokeObjectURL(i),6e4)}var Pi=w(()=>{"use strict"});function me(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function Li(r){return!r.startsWith("data:image/svg+xml,")&&!r.startsWith("data:image/png;base64,")?"":me(r)}function Ws(r){return r.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var M,Ri=w(()=>{"use strict";L();U();R();Pi();M=class extends A{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,t){this._entryId=e,this._taskId=null,this._objectName=t,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,t,i,n){this._entryId=e,this._taskId=t,this._objectName=i,this._taskName=n,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let t={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(t.task_id=this._taskId);let i=[this.hass.connection.sendMessagePromise({...t,action:"view"})];this._taskId&&i.push(this.hass.connection.sendMessagePromise({...t,action:"complete"}));let n=await Promise.all(i);if(e!==this._generateSeq)return;this._viewResult=n[0],n.length>1&&(this._completeResult=n[1])}catch(t){if(e!==this._generateSeq)return;let i=t?.code,n=t?.message;this._error=i==="no_url"||typeof n=="string"&&n.includes("No Home Assistant URL")?a("qr_error_no_url",this.lang):a("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,t=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,i=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),n=window.open("","_blank","width=600,height=500");if(!n)return;let o=this.lang||"en",p=me(t),d=me(i),u=!!this._completeResult,m=me(a("qr_action_view",o)),f=me(a("qr_action_complete",o));n.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
`,c([x({attribute:!1})],U.prototype,"hass",2),c([h()],U.prototype,"_open",2),c([h()],U.prototype,"_saving",2),c([h()],U.prototype,"_error",2),c([h()],U.prototype,"_draft",2),c([h()],U.prototype,"_partOptions",2),c([h()],U.prototype,"_partQty",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",U)});function ji(r,s,e){let t=new Blob([r],{type:e}),i=URL.createObjectURL(t),n=document.createElement("a");n.href=i,n.download=s,n.target="_blank",n.rel="noopener",n.style.display="none",document.body.appendChild(n),n.dispatchEvent(new MouseEvent("click")),document.body.removeChild(n),setTimeout(()=>URL.revokeObjectURL(i),6e4)}var Ni=w(()=>{"use strict"});function fe(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function Hi(r){return!r.startsWith("data:image/svg+xml,")&&!r.startsWith("data:image/png;base64,")?"":fe(r)}function Zs(r){return r.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var F,Di=w(()=>{"use strict";P();W();j();Ni();F=class extends A{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,t){this._entryId=e,this._taskId=null,this._objectName=t,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,t,i,n){this._entryId=e,this._taskId=t,this._objectName=i,this._taskName=n,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let t={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(t.task_id=this._taskId);let i=[this.hass.connection.sendMessagePromise({...t,action:"view"})];this._taskId&&i.push(this.hass.connection.sendMessagePromise({...t,action:"complete"}));let n=await Promise.all(i);if(e!==this._generateSeq)return;this._viewResult=n[0],n.length>1&&(this._completeResult=n[1])}catch(t){if(e!==this._generateSeq)return;let i=t?.code,n=t?.message;this._error=i==="no_url"||typeof n=="string"&&n.includes("No Home Assistant URL")?a("qr_error_no_url",this.lang):a("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,t=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,i=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),n=window.open("","_blank","width=600,height=500");if(!n)return;let o=this.lang||"en",p=fe(t),d=fe(i),u=!!this._completeResult,m=fe(a("qr_action_view",o)),f=fe(a("qr_action_complete",o));n.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>${p}</title>
|
||||
<style>
|
||||
@@ -3073,17 +3159,17 @@ Test pressure`,checklist_help:"One step per line. Max 100 items.",err_too_long:"
|
||||
${d?`<div class="sub">${d}</div>`:""}
|
||||
<div class="qr-row">
|
||||
<div class="qr-col">
|
||||
<img src="${Li(this._viewResult.svg_data_uri)}" alt="QR Info" />
|
||||
<img src="${Hi(this._viewResult.svg_data_uri)}" alt="QR Info" />
|
||||
<div class="qr-label">${m}</div>
|
||||
</div>
|
||||
${u?`<div class="qr-col">
|
||||
<img src="${Li(this._completeResult.svg_data_uri)}" alt="QR Complete" />
|
||||
<img src="${Hi(this._completeResult.svg_data_uri)}" alt="QR Complete" />
|
||||
<div class="qr-label">${f}</div>
|
||||
</div>`:""}
|
||||
</div>
|
||||
<div class="url">${me(this._viewResult.url)}</div>
|
||||
<div class="url">${fe(this._viewResult.url)}</div>
|
||||
<script>setTimeout(()=>window.print(),300)<\/script>
|
||||
</body></html>`),n.document.close()}_downloadSvg(e,t){let i=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),n=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;Ii(i,`qr-${Ws(n)}-${t}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en",t=this._taskName?`${a("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${a("qr_code",e)}: ${this._objectName}`,i=!!this._viewResult;return l`
|
||||
</body></html>`),n.document.close()}_downloadSvg(e,t){let i=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),n=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;ji(i,`qr-${Zs(n)}-${t}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return l``;let e=this.lang||this.hass?.language||"en",t=this._taskName?`${a("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${a("qr_code",e)}: ${this._objectName}`,i=!!this._viewResult;return l`
|
||||
<ha-dialog open @closed=${this._close}>
|
||||
<div class="dialog-title">${t}</div>
|
||||
<div class="content">
|
||||
@@ -3144,7 +3230,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`}};M.styles=S`
|
||||
`}};F.styles=S`
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
@@ -3262,51 +3348,51 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
color: var(--text-primary-color, #fff);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
`,c([x({attribute:!1})],M.prototype,"hass",2),c([x()],M.prototype,"lang",2),c([h()],M.prototype,"_open",2),c([h()],M.prototype,"_loading",2),c([h()],M.prototype,"_error",2),c([h()],M.prototype,"_viewResult",2),c([h()],M.prototype,"_completeResult",2),c([h()],M.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",M)});function Vs(r,s){if(s<=0)return 0;let e=typeof r=="number"&&Number.isFinite(r)?Math.trunc(r):0;return e<0?0:e%s}function Ks(r){return!!(r?.phases&&r.phase_sequence&&r.phase_sequence.length>0)}function bt(r){if(!r||!Ks(r))return null;let s=r.phase_sequence,e=Vs(r.phase_cursor,s.length),t=s[e],i=r.phases?.[t];return i?{id:t,name:i.name,index:e,count:s.length,notes:i.notes,checklist:i.checklist!==void 0?i.checklist:r.checklist??[],consumesParts:i.consumes_parts!==void 0?i.consumes_parts:r.consumes_parts??[],requiredFields:i.required_completion_fields!==void 0?i.required_completion_fields:r.required_completion_fields??[]}:null}function Qe(r){let s=bt(r);return s?`${s.index+1}/${s.count} \xB7 ${s.name}`:""}var qi=w(()=>{"use strict"});function ji(r,s){let e=r.interval_analysis,t=e?.weibull_beta,i=e?.weibull_eta;if(t==null||i==null||i<=0)return _;let n=r.interval_days??0,o=r.suggested_interval??n;return l`
|
||||
`,c([x({attribute:!1})],F.prototype,"hass",2),c([x()],F.prototype,"lang",2),c([h()],F.prototype,"_open",2),c([h()],F.prototype,"_loading",2),c([h()],F.prototype,"_error",2),c([h()],F.prototype,"_viewResult",2),c([h()],F.prototype,"_completeResult",2),c([h()],F.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",F)});function Xs(r,s){if(s<=0)return 0;let e=typeof r=="number"&&Number.isFinite(r)?Math.trunc(r):0;return e<0?0:e%s}function er(r){return!!(r?.phases&&r.phase_sequence&&r.phase_sequence.length>0)}function xt(r){if(!r||!er(r))return null;let s=r.phase_sequence,e=Xs(r.phase_cursor,s.length),t=s[e],i=r.phases?.[t];return i?{id:t,name:i.name,index:e,count:s.length,notes:i.notes,checklist:i.checklist!==void 0?i.checklist:r.checklist??[],consumesParts:i.consumes_parts!==void 0?i.consumes_parts:r.consumes_parts??[],requiredFields:i.required_completion_fields!==void 0?i.required_completion_fields:r.required_completion_fields??[]}:null}function He(r){let s=xt(r);return s?`${s.index+1}/${s.count} \xB7 ${s.name}`:""}var wt=w(()=>{"use strict"});function Mi(r){let s=r.task??null,e=s?xt(s):null,t=e?e.consumesParts:s?.consumes_parts||[],i=!!s?.part_ref,n=r.objects.find(d=>d.entry_id===r.entryId)?.parts||[],o=i?n.find(d=>d.id===s.part_ref.part_id):void 0,p=r.checklistsEnabled??!0;return{entry_id:r.entryId,task_id:r.taskId,task_name:r.taskName,checklist:e?p?e.checklist:[]:r.checklist??[],adaptive_enabled:!!r.adaptiveEnabled,required_completion_fields:e?e.requiredFields:s?.required_completion_fields||[],task_type:s?.type||"",reading_unit:s?.reading_unit||"",parts:i?[]:Ai({consumes_parts:t},r.entryId,r.objects,r.lang),consumes_parts:i?[]:t,phase_label:e?He(s):"",require_tag_scan:!!s?.require_tag_scan,restock_default:i?o?.restock_quantity??1:null,restock_unit_cost:i?o?.cost??null:null,currency_symbol:r.currencySymbol??"",consumes_info:t.map(d=>Si(d,r.entryId,r.objects,r.lang)),checklist_prefill:s?.checklist_progress||{},via_tag_scan:!!r.viaTagScan}}function Oi(r,s,e){r.entryId=s.entry_id,r.taskId=s.task_id,r.taskName=s.task_name,r.lang=e,r.checklist=s.checklist??[],r.adaptiveEnabled=!!s.adaptive_enabled,r.requiredFields=s.required_completion_fields??[],r.taskType=s.task_type??"",r.readingUnit=s.reading_unit??"",r.parts=s.parts??[],r.consumesParts=s.consumes_parts??[],r.phaseLabel=s.phase_label??"",r.requireTagScan=!!s.require_tag_scan,r.restockDefault=s.restock_default??null,r.restockUnitCost=s.restock_unit_cost??null,r.currencySymbol=s.currency_symbol??"",r.consumesInfo=s.consumes_info??[],r.checklistPrefill=s.checklist_prefill??{},r.viaTagScan=!!s.via_tag_scan,r.open({viaTagScan:!!s.via_tag_scan})}var $t=w(()=>{"use strict";Qe();wt()});function Fi(r,s){let e=r.interval_analysis,t=e?.weibull_beta,i=e?.weibull_eta;if(t==null||i==null||i<=0)return _;let n=r.interval_days??0,o=r.suggested_interval??n;return l`
|
||||
<div class="weibull-section">
|
||||
<div class="weibull-title">
|
||||
<ha-svg-icon aria-hidden="true" path="M3,14L3.5,14.07L8.07,9.5C7.89,8.85 8.06,8.11 8.59,7.59C9.37,6.8 10.63,6.8 11.41,7.59C11.94,8.11 12.11,8.85 11.93,9.5L14.5,12.07L15,12C15.18,12 15.35,12 15.5,12.07L19.07,8.5C19,8.35 19,8.18 19,8A2,2 0 0,1 21,6A2,2 0 0,1 23,8A2,2 0 0,1 21,10C20.82,10 20.65,10 20.5,9.93L16.93,13.5C17,13.65 17,13.82 17,14A2,2 0 0,1 15,16A2,2 0 0,1 13,14L13.07,13.5L10.5,10.93C10.18,11 9.82,11 9.5,10.93L4.93,15.5L5,16A2,2 0 0,1 3,18A2,2 0 0,1 1,16A2,2 0 0,1 3,14Z"></ha-svg-icon>
|
||||
${a("weibull_reliability_curve",s)}
|
||||
${Gs(t,s)}
|
||||
${tr(t,s)}
|
||||
</div>
|
||||
${Ys(t,i,n,o,s)}
|
||||
${Qs(e,s)}
|
||||
${e?.confidence_interval_low!=null?Js(e,r,s):_}
|
||||
${ir(t,i,n,o,s)}
|
||||
${sr(e,s)}
|
||||
${e?.confidence_interval_low!=null?rr(e,r,s):_}
|
||||
</div>
|
||||
`}function Gs(r,s){let e,t,i;return r<.8?(e="early_failures",t="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",i="beta_early_failures"):r<=1.2?(e="random_failures",t="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",i="beta_random_failures"):r<=3.5?(e="wear_out",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",i="beta_wear_out"):(e="highly_predictable",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",i="beta_highly_predictable"),l`
|
||||
`}function tr(r,s){let e,t,i;return r<.8?(e="early_failures",t="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",i="beta_early_failures"):r<=1.2?(e="random_failures",t="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",i="beta_random_failures"):r<=3.5?(e="wear_out",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",i="beta_wear_out"):(e="highly_predictable",t="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",i="beta_highly_predictable"),l`
|
||||
<span class="beta-badge ${e}">
|
||||
<ha-svg-icon path="${t}"></ha-svg-icon>
|
||||
${a(i,s)} (\u03B2=${r.toFixed(2)})
|
||||
</span>
|
||||
`}function Ys(r,s,e,t,i){let b=Math.max(e,t,s,1)*1.3,k=50,E=[];for(let I=0;I<=k;I++){let P=I/k*b,te=1-Math.exp(-Math.pow(P/s,r)),ve=32+P/b*260,ye=136-te*128;E.push([ve,ye])}let O=E.map(([I,P])=>`${I.toFixed(1)},${P.toFixed(1)}`).join(" "),K="M32,136 "+E.map(([I,P])=>`L${I.toFixed(1)},${P.toFixed(1)}`).join(" ")+` L${E[k][0].toFixed(1)},136 Z`,D=32+e/b*260,ce=1-Math.exp(-Math.pow(e/s,r)),de=136-ce*128,y=((1-ce)*100).toFixed(0),F=32+t/b*260,pe=[0,.25,.5,.75,1];return l`
|
||||
`}function ir(r,s,e,t,i){let y=Math.max(e,t,s,1)*1.3,k=50,E=[];for(let C=0;C<=k;C++){let R=C/k*y,se=1-Math.exp(-Math.pow(R/s,r)),ye=32+R/y*260,be=136-se*128;E.push([ye,be])}let H=E.map(([C,R])=>`${C.toFixed(1)},${R.toFixed(1)}`).join(" "),M="M32,136 "+E.map(([C,R])=>`L${C.toFixed(1)},${R.toFixed(1)}`).join(" ")+` L${E[k][0].toFixed(1)},136 Z`,q=32+e/y*260,z=1-Math.exp(-Math.pow(e/s,r)),te=136-z*128,b=((1-z)*100).toFixed(0),B=32+t/y*260,pe=[0,.25,.5,.75,1];return l`
|
||||
<div class="weibull-chart">
|
||||
<svg viewBox="0 0 ${300} ${160}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${a("chart_weibull",i)}">
|
||||
${pe.map(I=>{let P=136-I*128;return _e`
|
||||
<line x1="${32}" y1="${P.toFixed(1)}" x2="${292}" y2="${P.toFixed(1)}"
|
||||
stroke="var(--divider-color)" stroke-width="0.5" stroke-dasharray="${I===.5?"4,3":_}" />
|
||||
<text x="${28}" y="${(P+3).toFixed(1)}" fill="var(--secondary-text-color)"
|
||||
font-size="8" text-anchor="end">${(I*100).toFixed(0)}%</text>
|
||||
${pe.map(C=>{let R=136-C*128;return _e`
|
||||
<line x1="${32}" y1="${R.toFixed(1)}" x2="${292}" y2="${R.toFixed(1)}"
|
||||
stroke="var(--divider-color)" stroke-width="0.5" stroke-dasharray="${C===.5?"4,3":_}" />
|
||||
<text x="${28}" y="${(R+3).toFixed(1)}" fill="var(--secondary-text-color)"
|
||||
font-size="8" text-anchor="end">${(C*100).toFixed(0)}%</text>
|
||||
`})}
|
||||
|
||||
<text x="${32}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">0</text>
|
||||
<text x="${324/2}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(b/2)}</text>
|
||||
<text x="${292}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(b)}</text>
|
||||
<text x="${324/2}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(y/2)}</text>
|
||||
<text x="${292}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(y)}</text>
|
||||
|
||||
<path d="${K}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
|
||||
<polyline points="${O}" fill="none"
|
||||
<path d="${M}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
|
||||
<polyline points="${H}" fill="none"
|
||||
stroke="var(--primary-color, #03a9f4)" stroke-width="2" />
|
||||
|
||||
${e>0?_e`
|
||||
<line x1="${D.toFixed(1)}" y1="${8}" x2="${D.toFixed(1)}" y2="${136 .toFixed(1)}"
|
||||
<line x1="${q.toFixed(1)}" y1="${8}" x2="${q.toFixed(1)}" y2="${136 .toFixed(1)}"
|
||||
stroke="var(--primary-color, #03a9f4)" stroke-width="1.5" stroke-dasharray="4,3" />
|
||||
<circle cx="${D.toFixed(1)}" cy="${de.toFixed(1)}" r="3"
|
||||
<circle cx="${q.toFixed(1)}" cy="${te.toFixed(1)}" r="3"
|
||||
fill="var(--primary-color, #03a9f4)" />
|
||||
<text x="${(D+4).toFixed(1)}" y="${(de-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)"
|
||||
font-size="9" font-weight="600">R=${y}%</text>
|
||||
<text x="${(q+4).toFixed(1)}" y="${(te-6).toFixed(1)}" fill="var(--primary-color, #03a9f4)"
|
||||
font-size="9" font-weight="600">R=${b}%</text>
|
||||
`:_}
|
||||
|
||||
${t>0&&t!==e?_e`
|
||||
<line x1="${F.toFixed(1)}" y1="${8}" x2="${F.toFixed(1)}" y2="${136 .toFixed(1)}"
|
||||
<line x1="${B.toFixed(1)}" y1="${8}" x2="${B.toFixed(1)}" y2="${136 .toFixed(1)}"
|
||||
stroke="var(--success-color, #4caf50)" stroke-width="1.5" stroke-dasharray="4,3" />
|
||||
`:_}
|
||||
|
||||
@@ -3321,7 +3407,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
${e>0?l`<span class="legend-item"><span class="legend-swatch" style="background:var(--primary-color, #03a9f4); opacity:0.5"></span> ${a("current_interval_marker",i)}</span>`:_}
|
||||
${t>0&&t!==e?l`<span class="legend-item"><span class="legend-swatch" style="background:var(--success-color, #4caf50)"></span> ${a("recommended_marker",i)}</span>`:_}
|
||||
</div>
|
||||
`}function Qs(r,s){return l`
|
||||
`}function sr(r,s){return l`
|
||||
<div class="weibull-info-row">
|
||||
<div class="weibull-info-item">
|
||||
<span>${a("characteristic_life",s)}</span>
|
||||
@@ -3334,14 +3420,14 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
</div>
|
||||
`:_}
|
||||
</div>
|
||||
`}function Js(r,s,e){let t=r.confidence_interval_low,i=r.confidence_interval_high,n=s.suggested_interval??s.interval_days??0,o=s.interval_days??0,p=Math.max(0,t-5),u=i+5-p,m=(t-p)/u*100,f=(i-t)/u*100,v=(n-p)/u*100,b=o>0?(o-p)/u*100:-1;return l`
|
||||
`}function rr(r,s,e){let t=r.confidence_interval_low,i=r.confidence_interval_high,n=s.suggested_interval??s.interval_days??0,o=s.interval_days??0,p=Math.max(0,t-5),u=i+5-p,m=(t-p)/u*100,f=(i-t)/u*100,v=(n-p)/u*100,y=o>0?(o-p)/u*100:-1;return l`
|
||||
<div class="confidence-range">
|
||||
<div class="confidence-range-title">
|
||||
${a("confidence_interval",e)}: ${n} ${a("days",e)} (${t}\u2013${i})
|
||||
</div>
|
||||
<div class="confidence-bar">
|
||||
<div class="confidence-fill" style="left:${m.toFixed(1)}%;width:${f.toFixed(1)}%"></div>
|
||||
${b>=0?l`<div class="confidence-marker current" style="left:${b.toFixed(1)}%"></div>`:_}
|
||||
${y>=0?l`<div class="confidence-marker current" style="left:${y.toFixed(1)}%"></div>`:_}
|
||||
<div class="confidence-marker recommended" style="left:${v.toFixed(1)}%"></div>
|
||||
</div>
|
||||
<div class="confidence-labels">
|
||||
@@ -3349,7 +3435,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<span class="confidence-text high">${a("confidence_aggressive",e)} (${i}${a("days",e).charAt(0)})</span>
|
||||
</div>
|
||||
</div>
|
||||
`}var Ni=w(()=>{"use strict";L();R()});function Hi(r,s,e){let t=r.degradation_trend!=null&&r.degradation_trend!=="insufficient_data",i=r.days_until_threshold!=null,n=r.environmental_factor!=null&&r.environmental_factor!==1;if(!t&&!i&&!n)return _;let o=r.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":r.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return l`
|
||||
`}var zi=w(()=>{"use strict";P();j()});function Ui(r,s,e){let t=r.degradation_trend!=null&&r.degradation_trend!=="insufficient_data",i=r.days_until_threshold!=null,n=r.environmental_factor!=null&&r.environmental_factor!==1;if(!t&&!i&&!n)return _;let o=r.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":r.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return l`
|
||||
<div class="prediction-section">
|
||||
${r.sensor_prediction_urgency?l`
|
||||
<div class="prediction-urgency-banner">
|
||||
@@ -3375,8 +3461,9 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<ha-svg-icon path="M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M12.5,8H11V14L15.75,16.85L16.5,15.62L12.5,13.25V8M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72Z"></ha-svg-icon>
|
||||
<span class="prediction-label">${a("days_until_threshold",s)}</span>
|
||||
<span class="prediction-value prediction-days${r.days_until_threshold===0?" exceeded":r.sensor_prediction_urgency?" urgent":""}">${r.days_until_threshold===0?a("threshold_exceeded",s):"~"+Math.round(r.days_until_threshold)+" "+a("days",s)}</span>
|
||||
${r.threshold_prediction_date?l`<span class="prediction-date">${Y(r.threshold_prediction_date,s)}</span>`:_}
|
||||
${r.threshold_prediction_date?l`<span class="prediction-date">${Q(r.threshold_prediction_date,s)}</span>`:_}
|
||||
${r.threshold_prediction_confidence?l`<span class="confidence-dot ${r.threshold_prediction_confidence}"></span>`:_}
|
||||
${(r.prediction_cycles??0)>0?l`<span class="prediction-cycles">${a("prediction_cycles",s)}: ${r.prediction_cycles}</span>`:_}
|
||||
</div>
|
||||
`:_}
|
||||
${n&&e.environmental?l`
|
||||
@@ -3384,12 +3471,12 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<ha-svg-icon path="M15,13V5A3,3 0 0,0 12,2A3,3 0 0,0 9,5V13A5,5 0 0,0 7,17A5,5 0 0,0 12,22A5,5 0 0,0 17,17A5,5 0 0,0 15,13M12,4A1,1 0 0,1 13,5V8H11V5A1,1 0 0,1 12,4Z"></ha-svg-icon>
|
||||
<span class="prediction-label">${a("environmental_adjustment",s)}</span>
|
||||
<span class="prediction-value">${r.environmental_factor.toFixed(2)}x</span>
|
||||
${r.environmental_entity?l`<span class="prediction-entity entity-link" @click=${p=>mi(p,r.environmental_entity)}>${r.environmental_entity}</span>`:_}
|
||||
${r.environmental_entity?l`<span class="prediction-entity entity-link" @click=${p=>bi(p,r.environmental_entity)}>${r.environmental_entity}</span>`:_}
|
||||
</div>
|
||||
`:_}
|
||||
</div>
|
||||
</div>
|
||||
`}var Di=w(()=>{"use strict";L();R()});function Mi(r,s,e,t){let i=Math.max(r||1,s);return l`
|
||||
`}var Bi=w(()=>{"use strict";P();j()});function Wi(r,s,e,t){let i=Math.max(r||1,s);return l`
|
||||
<div class="interval-comparison">
|
||||
<div class="interval-bar">
|
||||
<div class="interval-label">
|
||||
@@ -3407,7 +3494,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
style="width: ${Math.min(s/i*100,100)}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
`}var Oi=w(()=>{"use strict";L();R()});function Fi(r,s,e){if(!e.seasonal||!r.seasonal_factor||r.seasonal_factor===1)return _;let t=zi.map(p=>a(p,s)),i=new Date().getMonth(),n=r.seasonal_factors||r.interval_analysis?.seasonal_factors||null,o=n&&n.length===12?n:t.map((p,d)=>{let u=r.seasonal_factor||1,m=Math.sin((d-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,u+m))});return l`
|
||||
`}var Vi=w(()=>{"use strict";P();j()});function Gi(r,s,e){if(!e.seasonal||!r.seasonal_factor||r.seasonal_factor===1)return _;let t=Ki.map(p=>a(p,s)),i=new Date().getMonth(),n=r.seasonal_factors||r.interval_analysis?.seasonal_factors||null,o=n&&n.length===12?n:t.map((p,d)=>{let u=r.seasonal_factor||1,m=Math.sin((d-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,u+m))});return l`
|
||||
<div class="seasonal-card-compact">
|
||||
<h4>${a("seasonal_awareness",s)}</h4>
|
||||
<div class="seasonal-mini-chart">
|
||||
@@ -3424,7 +3511,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<span class="legend-item"><span class="dot high"></span> ${a("longer",s)||"L\xE4nger"}</span>
|
||||
</div>
|
||||
</div>
|
||||
`}function Ui(r,s){return Zs(r,s)}function Zs(r,s){let e=r.seasonal_factors??r.interval_analysis?.seasonal_factors;if(!e||e.length!==12)return _;let t=r.interval_analysis?.seasonal_reason,i=new Date().getMonth(),n=300,o=100,p=8,u=o-p-4,m=Math.max(...e,1.5),f=n/12,v=f*.65,b=p+u-1/m*u;return l`
|
||||
`}function Yi(r,s){return ar(r,s)}function ar(r,s){let e=r.seasonal_factors??r.interval_analysis?.seasonal_factors;if(!e||e.length!==12)return _;let t=r.interval_analysis?.seasonal_reason,i=new Date().getMonth(),n=300,o=100,p=8,u=o-p-4,m=Math.max(...e,1.5),f=n/12,v=f*.65,y=p+u-1/m*u;return l`
|
||||
<div class="seasonal-chart">
|
||||
<div class="seasonal-chart-title">
|
||||
<ha-svg-icon aria-hidden="true" path="M17.75 4.09L15.22 6.03L16.13 9.09L13.5 7.28L10.87 9.09L11.78 6.03L9.25 4.09L12.44 4L13.5 1L14.56 4L17.75 4.09M21.25 11L19.61 12.25L20.2 14.23L18.5 13.06L16.8 14.23L17.39 12.25L15.75 11L17.81 10.95L18.5 9L19.19 10.95L21.25 11M18.97 15.95C19.8 15.87 20.69 17.05 20.16 17.8C19.84 18.25 19.5 18.67 19.08 19.07C15.17 23 8.84 23 4.94 19.07C1.03 15.17 1.03 8.83 4.94 4.93C5.34 4.53 5.76 4.17 6.21 3.85C6.96 3.32 8.14 4.21 8.06 5.04C7.79 7.9 8.75 10.87 10.95 13.06C13.14 15.26 16.1 16.22 18.97 15.95Z"></ha-svg-icon>
|
||||
@@ -3432,22 +3519,22 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
${t?l`<span class="source-tag">${t==="learned"?a("seasonal_learned",s):a("seasonal_manual",s)}</span>`:_}
|
||||
</div>
|
||||
<svg viewBox="0 0 ${n} ${o}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${a("chart_seasonal",s)}">
|
||||
<line x1="0" y1="${b.toFixed(1)}" x2="${n}" y2="${b.toFixed(1)}"
|
||||
<line x1="0" y1="${y.toFixed(1)}" x2="${n}" y2="${y.toFixed(1)}"
|
||||
stroke="var(--divider-color)" stroke-width="1" stroke-dasharray="4,3" />
|
||||
${e.map((k,E)=>{let O=k/m*u,K=E*f+(f-v)/2,D=p+u-O,ce=E===i,de=k<1?"var(--success-color, #4caf50)":k>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return _e`
|
||||
<rect x="${K.toFixed(1)}" y="${D.toFixed(1)}"
|
||||
width="${v.toFixed(1)}" height="${O.toFixed(1)}"
|
||||
fill="${de}" opacity="${ce?1:.5}" rx="2" />
|
||||
${e.map((k,E)=>{let H=k/m*u,M=E*f+(f-v)/2,q=p+u-H,z=E===i,te=k<1?"var(--success-color, #4caf50)":k>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return _e`
|
||||
<rect x="${M.toFixed(1)}" y="${q.toFixed(1)}"
|
||||
width="${v.toFixed(1)}" height="${H.toFixed(1)}"
|
||||
fill="${te}" opacity="${z?1:.5}" rx="2" />
|
||||
`})}
|
||||
</svg>
|
||||
<div class="seasonal-labels">
|
||||
${zi.map((k,E)=>l`<span class="seasonal-label ${E===i?"active-month":""}">${a(k,s)}</span>`)}
|
||||
${Ki.map((k,E)=>l`<span class="seasonal-label ${E===i?"active-month":""}">${a(k,s)}</span>`)}
|
||||
</div>
|
||||
</div>
|
||||
`}var zi,Bi=w(()=>{"use strict";L();R();zi=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"]});var C,Wi=w(()=>{"use strict";L();U();R();le();ht();Ge();qi();Ni();Di();Oi();Bi();C=class extends A{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1}get _lang(){return j(this.hass)}async openFor(e,t){this._entryId=e,this._taskId=t,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=ne(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});e?.features&&(this._features={...this._features,...e.features}),this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=e.object?.name||"";let t=(e.tasks||[]).find(i=>i.id===this._taskId);this._task=t??null}catch(e){this._error=q(e,this._lang)}}async _runWs(e){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(e),this._busy=!1,!0}catch(t){return this._error=q(t,this._lang),this._busy=!1,!1}}_notifyChanged(e){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:e},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(W(),J)).then(async({openCompleteDialog:e})=>{let t=this._task,i=!!t.part_ref,n=bt(t),o=n?n.consumesParts:t.consumes_parts||[],p=[];if(!i)try{let u=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0});p=xi({consumes_parts:o},this._entryId,u.objects||[],this._lang)}catch{}e({entry_id:this._entryId,task_id:this._taskId,task_name:t.name,checklist:n?n.checklist:t.checklist||[],adaptive_enabled:!!t.adaptive_config?.enabled,required_completion_fields:n?n.requiredFields:t.required_completion_fields||[],task_type:t.type||"",reading_unit:t.reading_unit||"",parts:p,consumes_parts:i?[]:o,phase_label:Qe(t)})&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||Promise.resolve().then(()=>(W(),J)).then(({openEditTaskDialog:e})=>{e(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(W(),J)).then(({openQrDialog:e})=>{e({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let e=a("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(e))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let e=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",e),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=a("suggestion_applied",this._lang)||"Applied",this._notifyChanged("app
Line truncated
|
||||
`}var Ki,Qi=w(()=>{"use strict";P();j();Ki=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"]});var I,Ji=w(()=>{"use strict";P();W();j();de();ht();$t();wt();zi();Bi();Vi();Qi();I=class extends A{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1;this._currencySymbol=""}get _lang(){return D(this.hass)}async openFor(e,t){this._entryId=e,this._taskId=t,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=le(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});e?.features&&(this._features={...this._features,...e.features}),this._currencySymbol=e?.budget?.currency_symbol||"",this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=e.object?.name||"";let t=(e.tasks||[]).find(i=>i.id===this._taskId);this._task=t??null}catch(e){this._error=N(e,this._lang)}}async _runWs(e){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(e),this._busy=!1,!0}catch(t){return this._error=N(t,this._lang),this._busy=!1,!1}}_notifyChanged(e){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:e},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(K(),Z)).then(async({openCompleteDialog:e})=>{let t=this._task,i=[];try{i=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0})).objects||[]}catch{}e(Mi({entryId:this._entryId,taskId:this._taskId,taskName:t.name,task:t,objects:i,lang:this._lang,checklist:t.checklist||[],adaptiveEnabled:!!t.adaptive_config?.enabled,currencySymbol:this._currencySymbol}))&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||Promise.resolve().then(()=>(K(),Z)).then(({openEditTaskDialog:e})=>{e(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||Promise.resolve().then(()=>(K(),Z)).then(({openQrDialog:e})=>{e({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let e=a("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(e))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let e=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",e),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=a("suggestion_applied",this._lang)||"Applied",this._notifyChanged("apply_suggestion"),await this._loadTask(),setTimeout(()=>{this._toast=""},2500))}async _reanalyzeInterval(){if(!(!this._entryId||!this._taskId)){this._busy=!0,this._error="";tr
Line truncated
|
||||
<div class="recommendation-card">
|
||||
<h4>${a("suggested_interval",t)}</h4>
|
||||
${Mi(e.interval_days,e.suggested_interval,e.interval_confidence||"medium",t)}
|
||||
${Wi(e.interval_days,e.suggested_interval,e.interval_confidence||"medium",t)}
|
||||
<div class="recommendation-actions">
|
||||
<button class="btn primary"
|
||||
@click=${this._applySuggestion} ?disabled=${this._busy}>
|
||||
@@ -3467,11 +3554,11 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<div class="adaptive-stack">
|
||||
${this._toast?l`<div class="toast">${this._toast}</div>`:_}
|
||||
${i?this._renderRecommendation(e):_}
|
||||
${n?Hi(e,t,this._features):_}
|
||||
${o?ji(e,t):_}
|
||||
${n?Ui(e,t,this._features):_}
|
||||
${o?Fi(e,t):_}
|
||||
${p?l`
|
||||
${Fi(e,t,this._features)}
|
||||
${e.seasonal_factors?.length===12||e.interval_analysis?.seasonal_factors?.length===12?Ui(e,t):_}
|
||||
${Gi(e,t,this._features)}
|
||||
${e.seasonal_factors?.length===12||e.interval_analysis?.seasonal_factors?.length===12?Yi(e,t):_}
|
||||
`:_}
|
||||
</div>
|
||||
`}_renderDetails(e){let t=this._lang,i=e.history||[],n=i.filter(d=>d.type==="completed"),o=n.reduce((d,u)=>d+(typeof u.cost=="number"?u.cost:0),0),p=(()=>{let d=n.map(u=>typeof u.duration=="number"?u.duration:null).filter(u=>u!=null);return d.length?Math.round(d.reduce((u,m)=>u+m,0)/d.length):null})();return l`
|
||||
@@ -3500,7 +3587,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<div class="history-entry">
|
||||
<div class="history-line">
|
||||
<span class="history-type type-${d.type}">${a(d.type,t)}</span>
|
||||
<span class="history-date">${hi(d.timestamp,t)}</span>
|
||||
<span class="history-date">${fi(d.timestamp,t)}</span>
|
||||
${u?l`<button class="history-edit"
|
||||
title="${a("history_edit_button",t)||"Edit"}"
|
||||
@click=${()=>this._onEditHistoryEntry(d)}>
|
||||
@@ -3528,13 +3615,13 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<span class="task-name">${t.name}</span>
|
||||
</div>
|
||||
<div class="object">
|
||||
<button class="link-inline" @click=${()=>{this._entryId&&Promise.resolve().then(()=>(W(),J)).then(({openObjectQuickActions:n})=>{n(this._entryId),this.close()})}}>${this._objectName}</button>
|
||||
<button class="link-inline" @click=${()=>{this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openObjectQuickActions:n})=>{n(this._entryId),this.close()})}}>${this._objectName}</button>
|
||||
</div>
|
||||
<div class="quick-info">
|
||||
${t.next_due?l`<span><strong>${a("next_due",e)||"Next due"}:</strong> ${Y(t.next_due,e)}</span>`:_}
|
||||
${t.last_performed?l`<span><strong>${a("last_performed",e)||"Last"}:</strong> ${Y(t.last_performed,e)}</span>`:_}
|
||||
${t.schedule?.kind&&!["manual","one_time"].includes(t.schedule.kind)||t.interval_days!=null?l`<span><strong>${a("interval",e)||"Interval"}:</strong> ${gi(t,e)}</span>`:_}
|
||||
${Qe(t)?l`<span><strong>${a("phase_current",e)}:</strong> ${Qe(t)}</span>`:_}
|
||||
${t.next_due?l`<span><strong>${a("next_due",e)||"Next due"}:</strong> ${Q(t.next_due,e)}</span>`:_}
|
||||
${t.last_performed?l`<span><strong>${a("last_performed",e)||"Last"}:</strong> ${Q(t.last_performed,e)}</span>`:_}
|
||||
${t.schedule?.kind&&!["manual","one_time"].includes(t.schedule.kind)||t.interval_days!=null?l`<span><strong>${a("interval",e)||"Interval"}:</strong> ${yi(t,e)}</span>`:_}
|
||||
${He(t)?l`<span><strong>${a("phase_current",e)}:</strong> ${He(t)}</span>`:_}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3626,7 +3713,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
`}
|
||||
`:l`<div class="loading">${a("loading",e)||"Loading\u2026"}</div>`}
|
||||
</div>
|
||||
`}};C.styles=[Ve,S`
|
||||
`}};I.styles=[Ge,S`
|
||||
:host { display: contents; }
|
||||
.backdrop {
|
||||
position: fixed; inset: 0; z-index: 100;
|
||||
@@ -3806,7 +3893,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
.weibull-section, .seasonal-card-compact { max-width: 100%; }
|
||||
.weibull-chart svg { max-width: 100%; height: auto; }
|
||||
.details-toggle { gap: 12px; flex-wrap: wrap; }
|
||||
`],c([x({attribute:!1})],C.prototype,"hass",2),c([h()],C.prototype,"_open",2),c([h()],C.prototype,"_entryId",2),c([h()],C.prototype,"_taskId",2),c([h()],C.prototype,"_task",2),c([h()],C.prototype,"_objectName",2),c([h()],C.prototype,"_busy",2),c([h()],C.prototype,"_error",2),c([h()],C.prototype,"_showSkip",2),c([h()],C.prototype,"_showReset",2),c([h()],C.prototype,"_showDetails",2),c([h()],C.prototype,"_showAdaptive",2),c([h()],C.prototype,"_skipReason",2),c([h()],C.prototype,"_resetDate",2),c([h()],C.prototype,"_features",2),c([h()],C.prototype,"_toast",2);customElements.get("maintenance-task-quick-actions-dialog")||customElements.define("maintenance-task-quick-actions-dialog",C)});function Vi(r){return!!r&&/^https?:\/\//i.test(r)}var Ki=w(()=>{"use strict"});var V,Gi=w(()=>{"use strict";L();Ki();U();R();le();V=class extends A{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._data=null;this._busy=!1;this._error=""}get _lang(){return j(this.hass)}async openFor(e){this._entryId=e,this._error="",this._open=!0,await this._load()}close(){this._open=!1,this._data=null,this._error=""}async _load(){if(this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._data=e}catch(e){this._error=q(e,this._lang)}}_onEditObject(){!this._entryId||!this._data||Promise.resolve().then(()=>(W(),J)).then(({openEditObjectDialog:e})=>{e(this._entryId,this._data.object),this.close()})}_onAddTask(){this._entryId&&Promise.resolve().then(()=>(W(),J)).then(({openCreateTaskDialog:e})=>{e(this._entryId),this.close()})}async _onDelete(){if(!this._entryId||!this._data)return;let e=a("delete_object_confirm",this._lang)||`Delete "${this._data.object.name}" and all its tasks?`;if(window.confirm(e)){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/delete",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-deleted",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=q(t,this._lang)}finally{this._busy=!1}}}async _onArchiveObject(){if(!this._entryId||!this._data)return;let e=!!this._data.object.archived;if(!e){let t=a("confirm_archive_object",this._lang)||"Archive this object and its tasks?";if(!window.confirm(t))return}this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:e?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-changed",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=q(t,this._lang)}finally{this._busy=!1}}_onTaskClick(e){this._entryId&&Promise.resolve().then(()=>(W(),J)).then(({openTaskQuickActions:t})=>{t(this._entryId,e)})}render(){if(!this._open)return _;let e=this._lang,t=this._data,i=t?.object,n=t?.tasks||[],o=this.hass?.user?.is_admin??!0;return l`
|
||||
`],c([x({attribute:!1})],I.prototype,"hass",2),c([h()],I.prototype,"_open",2),c([h()],I.prototype,"_entryId",2),c([h()],I.prototype,"_taskId",2),c([h()],I.prototype,"_task",2),c([h()],I.prototype,"_objectName",2),c([h()],I.prototype,"_busy",2),c([h()],I.prototype,"_error",2),c([h()],I.prototype,"_showSkip",2),c([h()],I.prototype,"_showReset",2),c([h()],I.prototype,"_showDetails",2),c([h()],I.prototype,"_showAdaptive",2),c([h()],I.prototype,"_skipReason",2),c([h()],I.prototype,"_resetDate",2),c([h()],I.prototype,"_features",2),c([h()],I.prototype,"_toast",2);customElements.get("maintenance-task-quick-actions-dialog")||customElements.define("maintenance-task-quick-actions-dialog",I)});function Zi(r){return!!r&&/^https?:\/\//i.test(r)}var Xi=w(()=>{"use strict"});function es(r){return r?customElements.get("ha-markdown")?l`<ha-markdown class="notes-md" .content=${r} breaks></ha-markdown>`:l`${r}`:_}var ts=w(()=>{"use strict";P()});var G,is=w(()=>{"use strict";P();Xi();ts();W();j();de();G=class extends A{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._data=null;this._busy=!1;this._error=""}get _lang(){return D(this.hass)}async openFor(e){this._entryId=e,this._error="",this._open=!0,await this._load()}close(){this._open=!1,this._data=null,this._error=""}async _load(){if(this._entryId)try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._data=e}catch(e){this._error=N(e,this._lang)}}_onEditObject(){!this._entryId||!this._data||Promise.resolve().then(()=>(K(),Z)).then(({openEditObjectDialog:e})=>{e(this._entryId,this._data.object),this.close()})}_onAddTask(){this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openCreateTaskDialog:e})=>{e(this._entryId),this.close()})}async _onDelete(){if(!this._entryId||!this._data)return;let e=a("delete_object_confirm",this._lang)||`Delete "${this._data.object.name}" and all its tasks?`;if(window.confirm(e)){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/delete",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-deleted",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=N(t,this._lang)}finally{this._busy=!1}}}async _onArchiveObject(){if(!this._entryId||!this._data)return;let e=!!this._data.object.archived;if(!e){let t=a("confirm_archive_object",this._lang)||"Archive this object and its tasks?";if(!window.confirm(t))return}this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:e?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-changed",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(t){this._error=N(t,this._lang)}finally{this._busy=!1}}_onTaskClick(e){this._entryId&&Promise.resolve().then(()=>(K(),Z)).then(({openTaskQuickActions:t})=>{t(this._entryId,e)})}render(){if(!this._open)return _;let e=this._lang,t=this._data,i=t?.object,n=t?.tasks||[],o=this.hass?.user?.is_admin??!0;return l`
|
||||
<div class="backdrop" @click=${this.close}></div>
|
||||
<div class="dialog" role="dialog" aria-modal="true">
|
||||
${t&&i?l`
|
||||
@@ -3838,7 +3925,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
${i.notes?l`
|
||||
<div class="notes-section">
|
||||
<strong>${a("object_notes_label",e)}</strong>
|
||||
<div class="notes-body">${i.notes}</div>
|
||||
<div class="notes-body">${es(i.notes)}</div>
|
||||
</div>
|
||||
`:_}
|
||||
|
||||
@@ -3869,11 +3956,11 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
${i.map(([n,o])=>l`
|
||||
<div class="meta-item">
|
||||
<span class="meta-label">${n}</span>
|
||||
<span class="meta-value">${Vi(o)?l`<a href="${o}" target="_blank" rel="noopener noreferrer">${o}</a>`:o}</span>
|
||||
<span class="meta-value">${Zi(o)?l`<a href="${o}" target="_blank" rel="noopener noreferrer">${o}</a>`:o}</span>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
`}};V.styles=S`
|
||||
`}};G.styles=S`
|
||||
:host { display: contents; }
|
||||
.backdrop {
|
||||
position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,0.5);
|
||||
@@ -3916,6 +4003,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
.task-name { flex: 1; font-size: 14px; }
|
||||
.task-status { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; }
|
||||
.notes-body { white-space: pre-wrap; font-size: 13px; padding: 8px; background: var(--secondary-background-color); border-radius: 6px; }
|
||||
.notes-body ha-markdown { white-space: normal; }
|
||||
.actions { display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid var(--divider-color); }
|
||||
.actions .btn { flex: 1; }
|
||||
.btn {
|
||||
@@ -3932,7 +4020,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
.btn ha-icon { --mdc-icon-size: 16px; }
|
||||
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); }
|
||||
.error { padding: 8px; border-radius: 6px; background: rgba(211,47,47,0.1); color: var(--error-color); font-size: 13px; }
|
||||
`,c([x({attribute:!1})],V.prototype,"hass",2),c([h()],V.prototype,"_open",2),c([h()],V.prototype,"_entryId",2),c([h()],V.prototype,"_data",2),c([h()],V.prototype,"_busy",2),c([h()],V.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",V)});var J={};is(J,{openCompleteDialog:()=>cr,openCreateObjectDialog:()=>ar,openCreateTaskDialog:()=>or,openEditObjectDialog:()=>nr,openEditTaskDialog:()=>lr,openHistoryEditDialog:()=>xt,openObjectQuickActions:()=>pr,openQrDialog:()=>dr,openTaskQuickActions:()=>wt});function Ze(){return document.querySelector("home-assistant")?.hass}function rr(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function Z(r){let s=rr(),e=s.querySelector(r)??document.body.querySelector(r);return e?e.parentNode!==s&&s.appendChild(e):(e=document.createElement(r),s.appendChild(e)),e}function X(r){let s=Ze();if(!s)return!1;r.hass=s;let e=j(s);return Re(e)||qe(e).then(()=>{r.requestUpdate?.()}),!0}function Zi(r){return Je||(Je=r.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(s=>({features:s.features??Yi.features,defaultWarningDays:s.general?.default_warning_days??7})).catch(()=>Yi),Je)}function ar(){let r=Z(Qi);return X(r)?(r.openCreate(),!0):!1}function nr(r,s){let e=Z(Qi);return X(e)?(e.openEdit(r,s),!0):!1}function or(r="",s){let e=Z(Ji);if(!X(e))return!1;let t=Ze();return t?((async()=>{let i=await Zi(t),n=e;n.checklistsEnabled=i.features.checklists,n.scheduleTimeEnabled=i.features.schedule_time,n.completionActionsEnabled=i.features.completion_actions,n.defaultWarningDays=i.defaultWarningDays,n.openCreate(r,s)})(),!0):!1}function lr(r,s){let e=Z(Ji);if(!X(e))return!1;let t=Ze();return t?((async()=>{try{let[i,n]=await Promise.all([t.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:r}),Zi(t)]),o=(i.tasks||[]).find(d=>d.id===s);if(!o){console.warn(`openEditTaskDialog: task ${s} not found in entry ${r}`);return}let p=e;p.checklistsEnabled=n.features.checklists,p.scheduleTimeEnabled=n.features.schedule_time,p.completionActionsEnabled=n.features.completion_actions,p.defaultWarningDays=n.defaultWarningDays,await p.openEdit(r,o)}catch(i){console.warn("openEditTaskDialog: failed to load task/features",i)}})(),!0):!1}function xt(r){let s=Z(Xs);return X(s)?(s.openEdit(r),!0):!1}function cr(r){let s=Z(er);return X(s)?(s.entryId=r.entry_id,s.taskId=r.task_id,s.taskName=r.task_name,s.checklist=r.checklist??[],s.adaptiveEnabled=!!r.adaptive_enabled,s.requiredFields=r.required_completion_fields??[],s.taskType=r.task_type??"",s.readingUnit=r.reading_unit??"",s.parts=r.parts??[],s.consumesParts=r.consumes_parts??[],s.phaseLabel=r.phase_label??"",s.lang=Ze()?.language||"en",s.open(),!0):!1}function dr(r){let s=Z(tr);return X(s)?(s.openForTask(r.entry_id,r.task_id,r.object_name,r.task_name),!0):!1}function wt(r,s){let e=Z(ir);return X(e)?(e.openFor(r,s),!0):!1}function pr(r){let s=Z(sr);return X(s)?(s.openFor(r),!0):!1}var Qi,Ji,Xs,er,tr,ir,sr,Yi,Je,W=w(()=>{"use strict";yi();Ai();Ti();Ci();Ri();Wi();Gi();R();Qi="maintenance-object-dialog",Ji="maintenance-task-dialog",Xs="maintenance-history-edit-dialog",er="maintenance-complete-dialog",tr="maintenance-qr-dialog",ir="maintenance-task-quick-actions-dialog",sr="maintenance-object-quick-actions-dialog";Yi={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7},Je=null});L();U();ht();L();var si=S`
|
||||
`,c([x({attribute:!1})],G.prototype,"hass",2),c([h()],G.prototype,"_open",2),c([h()],G.prototype,"_entryId",2),c([h()],G.prototype,"_data",2),c([h()],G.prototype,"_busy",2),c([h()],G.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",G)});var Z={};ds(Z,{openCompleteDialog:()=>mr,openCreateObjectDialog:()=>ur,openCreateTaskDialog:()=>_r,openEditObjectDialog:()=>hr,openEditTaskDialog:()=>gr,openHistoryEditDialog:()=>kt,openObjectQuickActions:()=>vr,openQrDialog:()=>fr,openTaskQuickActions:()=>Et});function Ze(){return document.querySelector("home-assistant")?.hass}function pr(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function X(r){let s=pr(),e=s.querySelector(r)??document.body.querySelector(r);return e?e.parentNode!==s&&s.appendChild(e):(e=document.createElement(r),s.appendChild(e)),e}function ee(r){let s=Ze();if(!s)return!1;r.hass=s;let e=D(s);return Re(e)||je(e).then(()=>{r.requestUpdate?.()}),!0}function ns(r){return Je||(Je=r.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(s=>({features:s.features??ss.features,defaultWarningDays:s.general?.default_warning_days??7})).catch(()=>ss),Je)}function ur(){let r=X(rs);return ee(r)?(r.openCreate(),!0):!1}function hr(r,s){let e=X(rs);return ee(e)?(e.openEdit(r,s),!0):!1}function _r(r="",s){let e=X(as);if(!ee(e))return!1;let t=Ze();return t?((async()=>{let i=await ns(t),n=e;n.checklistsEnabled=i.features.checklists,n.scheduleTimeEnabled=i.features.schedule_time,n.completionActionsEnabled=i.features.completion_actions,n.defaultWarningDays=i.defaultWarningDays,n.openCreate(r,s)})(),!0):!1}function gr(r,s){let e=X(as);if(!ee(e))return!1;let t=Ze();return t?((async()=>{try{let[i,n]=await Promise.all([t.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:r}),ns(t)]),o=(i.tasks||[]).find(d=>d.id===s);if(!o){console.warn(`openEditTaskDialog: task ${s} not found in entry ${r}`);return}let p=e;p.checklistsEnabled=n.features.checklists,p.scheduleTimeEnabled=n.features.schedule_time,p.completionActionsEnabled=n.features.completion_actions,p.defaultWarningDays=n.defaultWarningDays,await p.openEdit(r,o)}catch(i){console.warn("openEditTaskDialog: failed to load task/features",i)}})(),!0):!1}function kt(r){let s=X(nr);return ee(s)?(s.openEdit(r),!0):!1}function mr(r){let s=X(or);return ee(s)?(Oi(s,r,Ze()?.language||"en"),!0):!1}function fr(r){let s=X(lr);return ee(s)?(s.openForTask(r.entry_id,r.task_id,r.object_name,r.task_name),!0):!1}function Et(r,s){let e=X(cr);return ee(e)?(e.openFor(r,s),!0):!1}function vr(r){let s=X(dr);return ee(s)?(s.openFor(r),!0):!1}var rs,as,nr,or,lr,cr,dr,ss,Je,K=w(()=>{"use strict";$i();Li();qi();Ri();Di();Ji();is();j();$t();rs="maintenance-object-dialog",as="maintenance-task-dialog",nr="maintenance-history-edit-dialog",or="maintenance-complete-dialog",lr="maintenance-qr-dialog",cr="maintenance-task-quick-actions-dialog",dr="maintenance-object-quick-actions-dialog";ss={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7},Je=null});P();W();ht();P();var ni=S`
|
||||
.cal-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -4129,35 +4217,35 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
.cal-pill-day { font-size: 17px; }
|
||||
.cal-user-filter { margin-left: 0; width: 100%; }
|
||||
}
|
||||
`;R();function vi(r){let s=window;s.customCards=s.customCards||[],s.customCards.some(e=>e.type===r.type)||s.customCards.push(r)}W();var H=class extends A{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"};this._objects=[];this._stats=null;this._windowDays=30;this._pastDays=0;this._userFilter="";this._objectFilter="";this._configuredObjects=[];this._unsub=null;this._dataLoaded=!1;this._lastConnection=null}static getConfigElement(){return document.createElement("maintenance-supporter-calendar-card-editor")}static getStubConfig(){return{type:"custom:maintenance-supporter-calendar-card",window_days:30,show_window_chips:!0,show_user_filter:!0}}setConfig(e){if(this._config={...e},e.past_days&&[30,90].includes(e.past_days)?this._pastDays=e.past_days:e.window_days&&[7,14,30,365].includes(e.window_days)&&(this._windowDays=e.window_days,this._pastDays=0),typeof e.user_filter=="string"&&(this._userFilter=e.user_filter),typeof e.object_filter=="string")this._objectFilter=e.object_filter,this._configuredObjects=[];else if(Array.isArray(e.object_filter)){let t=e.object_filter.filter(i=>typeof i=="string"&&i!=="");this._objectFilter=t.length===1?t[0]:"",this._configuredObjects=t.length>1?t:[]}}getCardSize(){return 6}get _lang(){return j(this.hass)}disconnectedCallback(){if(super.disconnectedCallback(),this._unsub){try{this._unsub()}catch{}this._unsub=null}this._dataLoaded=!1,this._lastConnection=null}updated(e){if(super.updated(e),pi(this,e),e.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}}}async _loadData(){try{let[e,t]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"})]);this._objects=e.objects,this._stats=t}catch{}}async _subscribe(){try{let e=await this.hass.connection.subscribeMessage(t=>{let i=t;this._objects=i.objects},{type:"maintenance_supporter/subscribe"});if(!this.isConnected){e();return}this._unsub=e}catch{}}_onEventClick(e){if(e.history_timestamp){this._openHistoryEntry(e);return}wt(e.entry_id,e.task_id)||this.dispatchEvent(new CustomEvent("ll-custom",{detail:{type:"maintenance-supporter:open-task",entry_id:e.entry_id,task_id:e.task_id},bubbles:!0,composed:!0}))}async _openHistoryEntry(e){try{let i=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:e.entry_id})).tasks?.find(o=>o.id===e.task_id)?.history?.find(o=>o.timestamp===e.history_timestamp);if(!i||xt({entry_id:e.entry_id,task_id:e.task_id,original_timestamp:e.history_timestamp,type:i.type||"completed",timestamp:i.timestamp||e.history_timestamp,notes:i.notes??null,cost:i.cost??null,duration:i.duration??null,completed_by:i.completed_by??null,used_parts:i.used_parts??null}))return}catch{}this.dispatchEvent(new CustomEvent("ll-custom",{detail:{type:"maintenance-supporter:edit-history",entry_id:e.entry_id,task_id:e.task_id,original_timestamp:e.history_timestamp},bubbles:!0,composed:!0}))}render(){if(!this.hass)return _;let e=this._lang,t=this._config.show_window_chips!==!1,i=this._config.show_user_filter!==!1,n=this._config.title,o=null;this._userFilter&&(o=this._userFilter==="current_user"?this.hass?.user?.id??null:this._userFilter);let p=y=>{let F=y.toLowerCase();return this._objects.find(I=>I.entry_id===y||I.object.name.toLowerCase()===F)?.entry_id??null},d=new Set(this._configuredObjects.map(p).filter(y=>y!==null)),u=d.size?this._objects.filter(y=>d.has(y.entry_id)):this._objects,m=this._config.show_object_filter!==!1&&u.length>1,f=this._objectFilter?p(this._objectFilter):null,v=f&&u.some(y=>y.entry_id===f)?u.filter(y=>y.entry_id===f):u,b=new Date;b.setHours(0,0,0,0);let k=this._pastDays>0,E=k?ii(v,b,this._pastDays,o):ti(v,b,this._windowDays,o),O=ne(b),K=this._windowDays===365||k,D=K?E.filter(y=>y.events.length>0):E,ce=y=>{let F=`cal-status-${y.status}`,pe=y.projected?"cal-event-projected":"",I=y.status==="overdue"&&y.days_until_due!=null?` (${_i(y.days_until_due,e)})`:"",P=y.projected&&y.interval_days?l`<span class="cal-event-recur">${y.interval_unit&&y.interval_unit!=="days"?`${y.interval_days} ${a("unit_"+y.interval_unit,e)}`:a("cal_every_n_days",e).replace("{n}",String(y.interval_days))}</span>`:_,te=y.schedule_type==="sensor_based",ve=te?l`<ha-icon class="cal-event-icon cal-source-sensor"
|
||||
`;j();function wi(r){let s=window;s.customCards=s.customCards||[],s.customCards.some(e=>e.type===r.type)||s.customCards.push(r)}K();var O=class extends A{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"};this._objects=[];this._stats=null;this._windowDays=30;this._pastDays=0;this._userFilter="";this._objectFilter="";this._configuredObjects=[];this._unsub=null;this._dataLoaded=!1;this._lastConnection=null}static getConfigElement(){return document.createElement("maintenance-supporter-calendar-card-editor")}static getStubConfig(){return{type:"custom:maintenance-supporter-calendar-card",window_days:30,show_window_chips:!0,show_user_filter:!0}}setConfig(e){if(this._config={...e},e.past_days&&[30,90].includes(e.past_days)?this._pastDays=e.past_days:e.window_days&&[7,14,30,365].includes(e.window_days)&&(this._windowDays=e.window_days,this._pastDays=0),typeof e.user_filter=="string"&&(this._userFilter=e.user_filter),typeof e.object_filter=="string")this._objectFilter=e.object_filter,this._configuredObjects=[];else if(Array.isArray(e.object_filter)){let t=e.object_filter.filter(i=>typeof i=="string"&&i!=="");this._objectFilter=t.length===1?t[0]:"",this._configuredObjects=t.length>1?t:[]}}getCardSize(){return 6}get _lang(){return D(this.hass)}disconnectedCallback(){if(super.disconnectedCallback(),this._unsub){try{this._unsub()}catch{}this._unsub=null}this._dataLoaded=!1,this._lastConnection=null}updated(e){if(super.updated(e),_i(this,e),e.has("hass")&&this.hass){if(!this._dataLoaded)this._dataLoaded=!0,this._lastConnection=this.hass.connection,this._loadData(),this._subscribe();else if(this.hass.connection!==this._lastConnection){if(this._lastConnection=this.hass.connection,this._unsub){try{this._unsub()}catch{}this._unsub=null}this._subscribe(),this._loadData()}}}async _loadData(){try{let[e,t]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"}),this.hass.connection.sendMessagePromise({type:"maintenance_supporter/statistics"})]);this._objects=e.objects,this._stats=t}catch{}}async _subscribe(){try{let e=await this.hass.connection.subscribeMessage(t=>{let i=t;this._objects=i.objects},{type:"maintenance_supporter/subscribe"});if(!this.isConnected){e();return}this._unsub=e}catch{}}_onEventClick(e){if(e.history_timestamp){this._openHistoryEntry(e);return}Et(e.entry_id,e.task_id)||this.dispatchEvent(new CustomEvent("ll-custom",{detail:{type:"maintenance-supporter:open-task",entry_id:e.entry_id,task_id:e.task_id},bubbles:!0,composed:!0}))}async _openHistoryEntry(e){try{let i=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:e.entry_id})).tasks?.find(o=>o.id===e.task_id)?.history?.find(o=>o.timestamp===e.history_timestamp);if(!i||kt({entry_id:e.entry_id,task_id:e.task_id,original_timestamp:e.history_timestamp,type:i.type||"completed",timestamp:i.timestamp||e.history_timestamp,notes:i.notes??null,cost:i.cost??null,duration:i.duration??null,completed_by:i.completed_by??null,used_parts:i.used_parts??null}))return}catch{}this.dispatchEvent(new CustomEvent("ll-custom",{detail:{type:"maintenance-supporter:edit-history",entry_id:e.entry_id,task_id:e.task_id,original_timestamp:e.history_timestamp},bubbles:!0,composed:!0}))}render(){if(!this.hass)return _;let e=this._lang,t=this._config.show_window_chips!==!1,i=this._config.show_user_filter!==!1,n=this._config.title,o=null;this._userFilter&&(o=this._userFilter==="current_user"?this.hass?.user?.id??null:this._userFilter);let p=b=>{let B=b.toLowerCase();return this._objects.find(C=>C.entry_id===b||C.object.name.toLowerCase()===B)?.entry_id??null},d=new Set(this._configuredObjects.map(p).filter(b=>b!==null)),u=d.size?this._objects.filter(b=>d.has(b.entry_id)):this._objects,m=this._config.show_object_filter!==!1&&u.length>1,f=this._objectFilter?p(this._objectFilter):null,v=f&&u.some(b=>b.entry_id===f)?u.filter(b=>b.entry_id===f):u,y=new Date;y.setHours(0,0,0,0);let k=this._pastDays>0,E=k?ai(v,y,this._pastDays,o):ri(v,y,this._windowDays,o),H=le(y),M=this._windowDays===365||k,q=M?E.filter(b=>b.events.length>0):E,z=b=>{let B=`cal-status-${b.status}`,pe=b.projected?"cal-event-projected":"",C=b.status==="overdue"&&b.days_until_due!=null?` (${vi(b.days_until_due,e)})`:"",R=b.projected&&b.interval_days?l`<span class="cal-event-recur">${b.interval_unit&&b.interval_unit!=="days"?`${b.interval_days} ${a("unit_"+b.interval_unit,e)}`:a("cal_every_n_days",e).replace("{n}",String(b.interval_days))}</span>`:_,se=b.schedule_type==="sensor_based",ye=se?l`<ha-icon class="cal-event-icon cal-source-sensor"
|
||||
title="${a("cal_source_sensor",e)}" icon="mdi:trending-up"></ha-icon>`:l`<ha-icon class="cal-event-icon cal-source-time"
|
||||
title="${y.adaptive_enabled?a("cal_source_time_adaptive",e):a("cal_source_time",e)}"
|
||||
icon="${y.adaptive_enabled?"mdi:clock-time-four-outline":"mdi:clock-outline"}"></ha-icon>`,ye=te&&y.prediction_confidence&&y.status!=="triggered"&&!y.projected?l`<span class="cal-event-prediction cal-conf-${y.prediction_confidence}">
|
||||
${a("cal_predicted",e)} · ${a(`cal_confidence_${y.prediction_confidence}`,e)}
|
||||
</span>`:_,Xi=this._stats?.budget?.currency_symbol||ci,es=y.history_type?a(y.history_type,e):a(y.status,e);return l`
|
||||
title="${b.adaptive_enabled?a("cal_source_time_adaptive",e):a("cal_source_time",e)}"
|
||||
icon="${b.adaptive_enabled?"mdi:clock-time-four-outline":"mdi:clock-outline"}"></ha-icon>`,be=se&&b.prediction_confidence&&b.status!=="triggered"&&!b.projected?l`<span class="cal-event-prediction cal-conf-${b.prediction_confidence}">
|
||||
${a("cal_predicted",e)} · ${a(`cal_confidence_${b.prediction_confidence}`,e)}
|
||||
</span>`:_,os=this._stats?.budget?.currency_symbol||ui,ls=b.history_type?a(b.history_type,e):a(b.status,e);return l`
|
||||
<div class="cal-event ${pe}"
|
||||
@click=${()=>this._onEventClick(y)}>
|
||||
${ve}
|
||||
<span class="cal-status-pill ${F}">${es}</span>
|
||||
@click=${()=>this._onEventClick(b)}>
|
||||
${ye}
|
||||
<span class="cal-status-pill ${B}">${ls}</span>
|
||||
<div class="cal-event-body">
|
||||
<div class="cal-event-title">${y.object_name} · ${y.task_name}${I}</div>
|
||||
${ye}
|
||||
${P}
|
||||
<div class="cal-event-title">${b.object_name} · ${b.task_name}${C}</div>
|
||||
${be}
|
||||
${R}
|
||||
</div>
|
||||
${y.avg_cost!=null&&y.avg_cost>0?l`<span class="cal-event-cost">${y.avg_cost.toFixed(0)} ${Xi}</span>`:_}
|
||||
${b.avg_cost!=null&&b.avg_cost>0?l`<span class="cal-event-cost">${b.avg_cost.toFixed(0)} ${os}</span>`:_}
|
||||
</div>
|
||||
`},de=y=>{let[F,pe,I]=y.date.split("-").map(Number),P=new Date(F,pe-1,I),te=y.date===O,ve=P.toLocaleDateString(e,{weekday:"short"}),ye=P.toLocaleDateString(e,{month:"long"});return l`
|
||||
`},te=b=>{let[B,pe,C]=b.date.split("-").map(Number),R=new Date(B,pe-1,C),se=b.date===H,ye=R.toLocaleDateString(e,{weekday:"short"}),be=R.toLocaleDateString(e,{month:"long"});return l`
|
||||
<div class="cal-day-row">
|
||||
<div class="cal-day-pill ${te?"cal-today":""}">
|
||||
<span class="cal-pill-weekday">${ve}</span>
|
||||
<span class="cal-pill-day">${P.getDate()}</span>
|
||||
<div class="cal-day-pill ${se?"cal-today":""}">
|
||||
<span class="cal-pill-weekday">${ye}</span>
|
||||
<span class="cal-pill-day">${R.getDate()}</span>
|
||||
</div>
|
||||
<div class="cal-day-content">
|
||||
<div class="cal-day-header">
|
||||
<span class="cal-day-month">${ye}</span>
|
||||
${te?l`<span class="cal-day-today-badge">${a("today",e)}</span>`:_}
|
||||
<span class="cal-day-month">${be}</span>
|
||||
${se?l`<span class="cal-day-today-badge">${a("today",e)}</span>`:_}
|
||||
</div>
|
||||
${y.events.length===0?l`<div class="cal-empty">${a("cal_no_events",e)}</div>`:y.events.map(ce)}
|
||||
${b.events.length===0?l`<div class="cal-empty">${a("cal_no_events",e)}</div>`:b.events.map(z)}
|
||||
</div>
|
||||
</div>
|
||||
`};return l`
|
||||
@@ -4166,19 +4254,19 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
<div class="cal-controls">
|
||||
${t?l`
|
||||
<div class="cal-window-chips cal-past-chips" title="${a("cal_past_windows",e)||"Past windows"}">
|
||||
${[30,90].map(y=>l`
|
||||
<button class="cal-window-chip cal-past-chip ${this._pastDays===y?"active":""}"
|
||||
@click=${()=>{this._pastDays=y}}>
|
||||
−${y}d
|
||||
${[30,90].map(b=>l`
|
||||
<button class="cal-window-chip cal-past-chip ${this._pastDays===b?"active":""}"
|
||||
@click=${()=>{this._pastDays=b}}>
|
||||
−${b}d
|
||||
</button>
|
||||
`)}
|
||||
</div>
|
||||
<span class="cal-chip-separator" aria-hidden="true">●</span>
|
||||
<div class="cal-window-chips" title="${a("cal_forward_windows",e)||"Forward windows"}">
|
||||
${[7,14,30,365].map(y=>l`
|
||||
<button class="cal-window-chip ${this._pastDays===0&&this._windowDays===y?"active":""}"
|
||||
@click=${()=>{this._windowDays=y,this._pastDays=0}}>
|
||||
${y===365?"+1y":`+${y}d`}
|
||||
${[7,14,30,365].map(b=>l`
|
||||
<button class="cal-window-chip ${this._pastDays===0&&this._windowDays===b?"active":""}"
|
||||
@click=${()=>{this._windowDays=b,this._pastDays=0}}>
|
||||
${b===365?"+1y":`+${b}d`}
|
||||
</button>
|
||||
`)}
|
||||
</div>
|
||||
@@ -4186,7 +4274,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
${i?l`
|
||||
<select class="cal-user-filter"
|
||||
.value=${this._userFilter}
|
||||
@change=${y=>{this._userFilter=y.target.value}}>
|
||||
@change=${b=>{this._userFilter=b.target.value}}>
|
||||
<option value="">${a("all_users",e)}</option>
|
||||
<option value="current_user">${a("my_tasks",e)}</option>
|
||||
</select>
|
||||
@@ -4194,21 +4282,21 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
${m?l`
|
||||
<select class="cal-user-filter"
|
||||
.value=${f??""}
|
||||
@change=${y=>{this._objectFilter=y.target.value}}>
|
||||
@change=${b=>{this._objectFilter=b.target.value}}>
|
||||
<option value="">${a("all_objects",e)}</option>
|
||||
${[...u].sort((y,F)=>y.object.name.localeCompare(F.object.name)).map(y=>l`<option value=${y.entry_id} ?selected=${y.entry_id===f}>${y.object.name}</option>`)}
|
||||
${[...u].sort((b,B)=>b.object.name.localeCompare(B.object.name)).map(b=>l`<option value=${b.entry_id} ?selected=${b.entry_id===f}>${b.object.name}</option>`)}
|
||||
</select>
|
||||
`:_}
|
||||
</div>
|
||||
`:_}
|
||||
<div class="cal-rolling">
|
||||
${D.length===0&&K?l`<div class="cal-empty">${a("cal_no_events",e)}</div>`:D.map(de)}
|
||||
${q.length===0&&M?l`<div class="cal-empty">${a("cal_no_events",e)}</div>`:q.map(te)}
|
||||
</div>
|
||||
</ha-card>
|
||||
`}};H.styles=[Ve,si,S`
|
||||
`}};O.styles=[Ge,ni,S`
|
||||
:host { display: block; }
|
||||
ha-card { padding: 0; overflow: hidden; }
|
||||
`],c([x({attribute:!1})],H.prototype,"hass",2),c([h()],H.prototype,"_config",2),c([h()],H.prototype,"_objects",2),c([h()],H.prototype,"_stats",2),c([h()],H.prototype,"_windowDays",2),c([h()],H.prototype,"_pastDays",2),c([h()],H.prototype,"_userFilter",2),c([h()],H.prototype,"_objectFilter",2),c([h()],H.prototype,"_unsub",2);var ur=[{value:7,key:"cal_editor_window_week"},{value:14,key:"cal_editor_window_fortnight"},{value:30,key:"cal_editor_window_month"},{value:365,key:"cal_editor_window_year"}],fe=class extends A{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"}}get _lang(){return j(this.hass)}setConfig(e){this._config={...e}}updated(){let e=this._lang;e&&!Re(e)&&qe(e).then(()=>this.requestUpdate())}_valueChanged(e,t){let i={...this._config,[e]:t};e==="show_window_chips"&&t===!0&&delete i.show_window_chips,e==="show_user_filter"&&t===!0&&delete i.show_user_filter,e==="show_object_filter"&&t===!0&&delete i.show_object_filter,e==="title"&&(!t||typeof t=="string"&&t.trim()==="")&&delete i.title,e==="user_filter"&&t===""&&delete i.user_filter,this._config=i,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:i},bubbles:!0,composed:!0}))}render(){let e=this._lang,t=this._config.window_days??30,i=this._config.show_window_chips!==!1,n=this._config.show_user_filter!==!1,o=this._config.user_filter??"",p=this._config.title??"";return l`
|
||||
`],c([x({attribute:!1})],O.prototype,"hass",2),c([h()],O.prototype,"_config",2),c([h()],O.prototype,"_objects",2),c([h()],O.prototype,"_stats",2),c([h()],O.prototype,"_windowDays",2),c([h()],O.prototype,"_pastDays",2),c([h()],O.prototype,"_userFilter",2),c([h()],O.prototype,"_objectFilter",2),c([h()],O.prototype,"_unsub",2);var yr=[{value:7,key:"cal_editor_window_week"},{value:14,key:"cal_editor_window_fortnight"},{value:30,key:"cal_editor_window_month"},{value:365,key:"cal_editor_window_year"}],ve=class extends A{constructor(){super(...arguments);this._config={type:"custom:maintenance-supporter-calendar-card"}}get _lang(){return D(this.hass)}setConfig(e){this._config={...e}}updated(){let e=this._lang;e&&!Re(e)&&je(e).then(()=>this.requestUpdate())}_valueChanged(e,t){let i={...this._config,[e]:t};e==="show_window_chips"&&t===!0&&delete i.show_window_chips,e==="show_user_filter"&&t===!0&&delete i.show_user_filter,e==="show_object_filter"&&t===!0&&delete i.show_object_filter,e==="title"&&(!t||typeof t=="string"&&t.trim()==="")&&delete i.title,e==="user_filter"&&t===""&&delete i.user_filter,this._config=i,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:i},bubbles:!0,composed:!0}))}render(){let e=this._lang,t=this._config.window_days??30,i=this._config.show_window_chips!==!1,n=this._config.show_user_filter!==!1,o=this._config.user_filter??"",p=this._config.title??"";return l`
|
||||
<div class="editor">
|
||||
<div class="row">
|
||||
<label for="title">${a("card_title",e)}</label>
|
||||
@@ -4225,7 +4313,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
id="window"
|
||||
@change=${d=>this._valueChanged("window_days",Number(d.target.value))}
|
||||
>
|
||||
${ur.map(d=>l`<option value="${d.value}" ?selected=${d.value===t}>${a(d.key,e)}</option>`)}
|
||||
${yr.map(d=>l`<option value="${d.value}" ?selected=${d.value===t}>${a(d.key,e)}</option>`)}
|
||||
</select>
|
||||
</div>
|
||||
<div class="row toggle">
|
||||
@@ -4270,7 +4358,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
</div>
|
||||
<div class="hint">${a("cal_editor_object_hint",e)}</div>
|
||||
</div>
|
||||
`}};fe.styles=S`
|
||||
`}};ve.styles=S`
|
||||
:host { display: block; padding: 8px 0; }
|
||||
.editor { display: flex; flex-direction: column; gap: 12px; }
|
||||
.row { display: flex; flex-direction: column; gap: 4px; }
|
||||
@@ -4294,7 +4382,7 @@ ${d?`<div class="sub">${d}</div>`:""}
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color, #666);
|
||||
}
|
||||
`,c([x({attribute:!1})],fe.prototype,"hass",2),c([h()],fe.prototype,"_config",2);customElements.get("maintenance-supporter-calendar-card")||customElements.define("maintenance-supporter-calendar-card",H);customElements.get("maintenance-supporter-calendar-card-editor")||customElements.define("maintenance-supporter-calendar-card-editor",fe);vi({type:"maintenance-supporter-calendar-card",name:"Maintenance Supporter \u2014 Calendar",description:"Rolling calendar of maintenance tasks with 7/14/30/365 day windows, source icons, and prediction-confidence pills.",preview:!0});export{H as MaintenanceCalendarCard};
|
||||
`,c([x({attribute:!1})],ve.prototype,"hass",2),c([h()],ve.prototype,"_config",2);customElements.get("maintenance-supporter-calendar-card")||customElements.define("maintenance-supporter-calendar-card",O);customElements.get("maintenance-supporter-calendar-card-editor")||customElements.define("maintenance-supporter-calendar-card-editor",ve);wi({type:"maintenance-supporter-calendar-card",name:"Maintenance Supporter \u2014 Calendar",description:"Rolling calendar of maintenance tasks with 7/14/30/365 day windows, source icons, and prediction-confidence pills.",preview:!0});export{O as MaintenanceCalendarCard};
|
||||
/*! Bundled license information:
|
||||
|
||||
@lit/reactive-element/css-tag.js:
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.65.0 */
|
||||
var S="2.65.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{}})();
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
var S="2.68.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{}})();
|
||||
Loaded 100 of 566 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user