184 files
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import date as date_cls
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
|
||||
@@ -18,7 +20,7 @@ from homeassistant.core import (
|
||||
SupportsResponse,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
)
|
||||
@@ -34,6 +36,7 @@ from homeassistant.helpers.event import (
|
||||
)
|
||||
from homeassistant.helpers.start import async_at_started
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
BATTERY_FLEET_OBJECT_FLAG,
|
||||
@@ -131,7 +134,7 @@ type MaintenanceSupporterConfigEntry = ConfigEntry[MaintenanceSupporterData]
|
||||
# --- Service Schemas ---
|
||||
SERVICE_COMPLETE_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional("notes"): vol.All(cv.string, vol.Length(max=2000)),
|
||||
vol.Optional("cost"): vol.All(vol.Coerce(float), vol.Range(min=0, max=MAX_COST)),
|
||||
vol.Optional("duration"): vol.All(vol.Coerce(int), vol.Range(min=0, max=MAX_DURATION_MINUTES)),
|
||||
@@ -149,14 +152,14 @@ SERVICE_COMPLETE_SCHEMA = vol.Schema(
|
||||
|
||||
SERVICE_RESET_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional("date"): cv.date,
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_SKIP_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
|
||||
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional("reason"): vol.All(cv.string, vol.Length(max=2000)),
|
||||
}
|
||||
)
|
||||
@@ -451,9 +454,8 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
)
|
||||
return str(user_id)
|
||||
|
||||
async def _handle_complete(call: ServiceCall) -> None:
|
||||
"""Handle the complete service call."""
|
||||
entity_id = call.data[ATTR_ENTITY_ID]
|
||||
def _resolve_task_target(entity_id: str) -> tuple[MaintenanceCoordinator, str]:
|
||||
"""entity_id -> (coordinator, task_id), with the classic errors."""
|
||||
coordinator = _get_coordinator_for_entity(hass, entity_id)
|
||||
if coordinator is None:
|
||||
raise ServiceValidationError(
|
||||
@@ -468,68 +470,94 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
translation_key="no_task_for_entity",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
)
|
||||
return coordinator, task_id
|
||||
|
||||
async def _run_per_task(
|
||||
action: Callable[[MaintenanceCoordinator, str], Awaitable[None]],
|
||||
entity_ids: list[str],
|
||||
label: str,
|
||||
) -> None:
|
||||
"""#158: one or many task sensors. A single entity keeps today's
|
||||
exact error shape; a list runs sequentially best-effort — the
|
||||
choke-point refusals (too_early, tag_scan_required,
|
||||
completion_details_required, the skip lock, ...) hit per task, are
|
||||
collected, and never block the other tasks. Partial success stands
|
||||
and the summary error names what was refused."""
|
||||
ids = list(dict.fromkeys(entity_ids))
|
||||
if len(ids) == 1:
|
||||
coordinator, task_id = _resolve_task_target(ids[0])
|
||||
await action(coordinator, task_id)
|
||||
return
|
||||
done = 0
|
||||
errors: list[str] = []
|
||||
for eid in ids:
|
||||
try:
|
||||
coordinator, task_id = _resolve_task_target(eid)
|
||||
await action(coordinator, task_id)
|
||||
done += 1
|
||||
except HomeAssistantError as err:
|
||||
errors.append(f"{eid}: {err}")
|
||||
if errors:
|
||||
raise ServiceValidationError(
|
||||
f"{label}: {done} of {len(ids)} succeeded; " + " | ".join(errors)
|
||||
)
|
||||
|
||||
async def _handle_complete(call: ServiceCall) -> None:
|
||||
"""Handle the complete service call (one or many task sensors, #158)."""
|
||||
entity_ids = call.data[ATTR_ENTITY_ID]
|
||||
if len(dict.fromkeys(entity_ids)) > 1:
|
||||
# Per-task values must not fan out: a reading belongs to ONE
|
||||
# meter, cost/duration would multiply into the budget and the
|
||||
# averages, and one physical scan proves exactly one tag.
|
||||
for field in ("reading_value", "cost", "duration", "via_tag_scan"):
|
||||
if call.data.get(field) is not None:
|
||||
raise ServiceValidationError(
|
||||
f"'{field}' can only be used with a single entity_id"
|
||||
)
|
||||
# #128: explicit person beats the call context; the context covers the
|
||||
# common case for free (a dashboard tap propagates the tapping user).
|
||||
if call.data.get("completed_by"):
|
||||
completed_by: str | None = _resolve_person_user_id(call.data["completed_by"])
|
||||
else:
|
||||
completed_by = call.context.user_id if call.context else None
|
||||
await coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
notes=call.data.get("notes"),
|
||||
cost=call.data.get("cost"),
|
||||
duration=call.data.get("duration"),
|
||||
reading_value=call.data.get("reading_value"),
|
||||
completed_by=completed_by,
|
||||
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 _one(coordinator: MaintenanceCoordinator, task_id: str) -> None:
|
||||
await coordinator.complete_maintenance(
|
||||
task_id=task_id,
|
||||
notes=call.data.get("notes"),
|
||||
cost=call.data.get("cost"),
|
||||
duration=call.data.get("duration"),
|
||||
reading_value=call.data.get("reading_value"),
|
||||
completed_by=completed_by,
|
||||
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")),
|
||||
)
|
||||
|
||||
await _run_per_task(_one, entity_ids, "complete")
|
||||
|
||||
async def _handle_reset(call: ServiceCall) -> None:
|
||||
"""Handle the reset service call."""
|
||||
entity_id = call.data[ATTR_ENTITY_ID]
|
||||
coordinator = _get_coordinator_for_entity(hass, entity_id)
|
||||
if coordinator is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_coordinator_for_entity",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
"""Handle the reset service call (one or many task sensors, #158)."""
|
||||
|
||||
async def _one(coordinator: MaintenanceCoordinator, task_id: str) -> None:
|
||||
await coordinator.reset_maintenance(
|
||||
task_id=task_id,
|
||||
date=call.data.get("date"),
|
||||
)
|
||||
task_id = _get_task_id_for_entity(hass, entity_id)
|
||||
if task_id is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_task_for_entity",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
)
|
||||
await coordinator.reset_maintenance(
|
||||
task_id=task_id,
|
||||
date=call.data.get("date"),
|
||||
)
|
||||
|
||||
await _run_per_task(_one, call.data[ATTR_ENTITY_ID], "reset")
|
||||
|
||||
async def _handle_skip(call: ServiceCall) -> None:
|
||||
"""Handle the skip service call."""
|
||||
entity_id = call.data[ATTR_ENTITY_ID]
|
||||
coordinator = _get_coordinator_for_entity(hass, entity_id)
|
||||
if coordinator is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_coordinator_for_entity",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
"""Handle the skip service call (one or many task sensors, #158)."""
|
||||
|
||||
async def _one(coordinator: MaintenanceCoordinator, task_id: str) -> None:
|
||||
await coordinator.skip_maintenance(
|
||||
task_id=task_id,
|
||||
reason=call.data.get("reason"),
|
||||
)
|
||||
task_id = _get_task_id_for_entity(hass, entity_id)
|
||||
if task_id is None:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="no_task_for_entity",
|
||||
translation_placeholders={"entity_id": entity_id},
|
||||
)
|
||||
await coordinator.skip_maintenance(
|
||||
task_id=task_id,
|
||||
reason=call.data.get("reason"),
|
||||
)
|
||||
|
||||
await _run_per_task(_one, call.data[ATTR_ENTITY_ID], "skip")
|
||||
|
||||
async def _handle_export(call: ServiceCall) -> None:
|
||||
"""Handle the export_data service call."""
|
||||
@@ -689,6 +717,21 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
continue
|
||||
if wanted_status and status != wanted_status:
|
||||
continue
|
||||
# #151: last completion + live trigger reading, so
|
||||
# notification automations don't need extra lookups.
|
||||
lp = task.get("last_performed")
|
||||
days_since: int | None = None
|
||||
if lp:
|
||||
try:
|
||||
days_since = (dt_util.now().date() - date_cls.fromisoformat(str(lp)[:10])).days
|
||||
except ValueError:
|
||||
days_since = None
|
||||
tc = task.get("trigger_config") or {}
|
||||
trigger_unit: str | None = None
|
||||
if tc.get("entity_id"):
|
||||
st = hass.states.get(tc["entity_id"])
|
||||
if st:
|
||||
trigger_unit = st.attributes.get("unit_of_measurement")
|
||||
tasks.append(
|
||||
{
|
||||
"entry_id": ce.entry_id,
|
||||
@@ -700,6 +743,10 @@ async def _async_setup_shared(hass: HomeAssistant) -> bool:
|
||||
"days_until_due": task.get("_days_until_due"),
|
||||
"type": task.get("type"),
|
||||
"priority": task.get("priority", "normal"),
|
||||
"last_performed": lp,
|
||||
"days_since_last_completed": days_since,
|
||||
"trigger_current_value": task.get("_trigger_current_value"),
|
||||
"trigger_unit": trigger_unit,
|
||||
}
|
||||
)
|
||||
return {"tasks": tasks, "count": len(tasks)}
|
||||
@@ -1484,12 +1531,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: MaintenanceSupporterConf
|
||||
except Exception:
|
||||
_LOGGER.exception("Battery-fleet part reconcile at start failed")
|
||||
return
|
||||
if result["added"] or result["pruned"]:
|
||||
if result["added"] or result["pruned"] or result["orphans_removed"]:
|
||||
_LOGGER.info(
|
||||
"Battery-fleet parts reconciled at start: added %s, pruned legacy UNKNOWN: %s",
|
||||
"Battery-fleet parts reconciled at start: added %s, pruned legacy UNKNOWN: %s, orphaned stock sensors removed: %s",
|
||||
result["added"] or "none",
|
||||
result["pruned"],
|
||||
result["orphans_removed"],
|
||||
)
|
||||
if result.get("trigger_healed"):
|
||||
# #156: the recovery flag only reaches the LIVE trigger
|
||||
# after a reload — do it once; the next start finds it set.
|
||||
_LOGGER.info("Battery-fleet trigger healed (auto_complete_on_recovery) — reloading %s", entry.title)
|
||||
hass.async_create_task(hass.config_entries.async_reload(entry.entry_id))
|
||||
|
||||
entry.async_on_unload(async_at_started(hass, _fleet_parts_at_start))
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Řekni mi, o kolik ho mám odložit — třeba o pět dní.",
|
||||
"postpone_past": "Tím by '{task}' spadl do minulosti.",
|
||||
"skipped": "Tento cyklus úkolu '{task}' u zařízení {object} je přeskočený. Další termín je {date}.",
|
||||
"skip_disabled": "Přeskakování je pro '{task}' vypnuto.",
|
||||
"not_found": "Nenašel jsem úkol údržby, který by odpovídal '{name}'.",
|
||||
"ambiguous": "Tomu odpovídá víc úkolů: {candidates}. Upřesni to, prosím.",
|
||||
"too_early": "Úkol '{task}' se dá dokončit až blíž k termínu.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Sig, hvor langt jeg skal udskyde den — for eksempel fem dage.",
|
||||
"postpone_past": "Så ville '{task}' ligge i fortiden.",
|
||||
"skipped": "Jeg har sprunget denne omgang af '{task}' på {object} over. Næste omgang skal laves den {date}.",
|
||||
"skip_disabled": "Spring over er slået fra for '{task}'.",
|
||||
"not_found": "Jeg kunne ikke finde en vedligeholdelsesopgave, der passer på '{name}'.",
|
||||
"ambiguous": "Det passer på flere opgaver: {candidates}. Vær lige lidt mere præcis.",
|
||||
"too_early": "'{task}' kan først markeres som udført tættere på forfaldsdatoen.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Sag mir, um wie lange ich verschieben soll — zum Beispiel um fünf Tage.",
|
||||
"postpone_past": "Damit läge '{task}' in der Vergangenheit.",
|
||||
"skipped": "Dieser Durchgang von '{task}' an {object} wurde übersprungen. Der nächste ist am {date} fällig.",
|
||||
"skip_disabled": "Überspringen ist für '{task}' ausgeschaltet.",
|
||||
"not_found": "Ich habe keine Wartungsaufgabe zu '{name}' gefunden.",
|
||||
"ambiguous": "Das passt auf mehrere Aufgaben: {candidates}. Bitte formuliere genauer.",
|
||||
"too_early": "'{task}' kann erst näher am Fälligkeitstermin erledigt werden.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Tell me how far to postpone it — for example, by five days.",
|
||||
"postpone_past": "That would put '{task}' in the past.",
|
||||
"skipped": "Skipped this cycle of '{task}' on {object}. The next one is due {date}.",
|
||||
"skip_disabled": "Skipping is turned off for '{task}'.",
|
||||
"not_found": "I couldn't find a maintenance task matching '{name}'.",
|
||||
"ambiguous": "That matches several tasks: {candidates}. Please be more specific.",
|
||||
"too_early": "'{task}' can only be completed closer to its due date.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Dime cuánto quieres aplazarlo — por ejemplo, cinco días.",
|
||||
"postpone_past": "Eso dejaría '{task}' en el pasado.",
|
||||
"skipped": "Me he saltado este ciclo de '{task}' en {object}. El siguiente toca el {date}.",
|
||||
"skip_disabled": "Omitir está desactivado para '{task}'.",
|
||||
"not_found": "No he encontrado ninguna tarea de mantenimiento que coincida con '{name}'.",
|
||||
"ambiguous": "Eso coincide con varias tareas: {candidates}. Concreta un poco más, por favor.",
|
||||
"too_early": "'{task}' solo se puede completar cuando se acerque su fecha prevista.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Kerro, kuinka paljon siirrän sitä — esimerkiksi viisi päivää.",
|
||||
"postpone_past": "Silloin tehtävä '{task}' menisi menneisyyteen.",
|
||||
"skipped": "Ohitin tehtävän '{task}' tämän kerran kohteessa {object}. Seuraava ajankohta: {date}.",
|
||||
"skip_disabled": "Ohittaminen on poistettu käytöstä kohteelle '{task}'.",
|
||||
"not_found": "En löytänyt huoltotehtävää, joka vastaisi hakua '{name}'.",
|
||||
"ambiguous": "Tämä vastaa useaa tehtävää: {candidates}. Tarkenna vielä hieman.",
|
||||
"too_early": "Tehtävän '{task}' voi kuitata tehdyksi vasta lähempänä sen ajankohtaa.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Dis-moi de combien je dois le reporter — par exemple, de cinq jours.",
|
||||
"postpone_past": "Ça mettrait '{task}' dans le passé.",
|
||||
"skipped": "J'ai sauté ce cycle de '{task}' sur {object}. Le prochain est à faire le {date}.",
|
||||
"skip_disabled": "Ignorer est désactivé pour '{task}'.",
|
||||
"not_found": "Je n'ai pas trouvé de tâche d'entretien qui corresponde à '{name}'.",
|
||||
"ambiguous": "Ça correspond à plusieurs tâches : {candidates}. Sois un peu plus précis, s'il te plaît.",
|
||||
"too_early": "Tu ne pourras valider '{task}' que plus près de sa date d'échéance.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "बताइए कितना टालना है — जैसे पाँच दिन।",
|
||||
"postpone_past": "इससे '{task}' की तारीख़ पिछली हो जाएगी।",
|
||||
"skipped": "{object} पर '{task}' का यह चक्र छोड़ दिया है। अगली बार {date} को करना है।",
|
||||
"skip_disabled": "'{task}' के लिए छोड़ना बंद है।",
|
||||
"not_found": "'{name}' से मिलता-जुलता कोई मेंटेनेंस काम नहीं मिला।",
|
||||
"ambiguous": "इससे कई काम मेल खाते हैं: {candidates}। थोड़ा और साफ़ बताइए।",
|
||||
"too_early": "'{task}' को तय तारीख़ के क़रीब आने पर ही पूरा किया जा सकता है।",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Mondd meg, mennyivel halasszam el — például öt nappal.",
|
||||
"postpone_past": "'{task}': ezzel a feladat a múltba kerülne.",
|
||||
"skipped": "Kihagytam ezt a fordulót: '{task}' – {object}. A következő időpont: {date}.",
|
||||
"skip_disabled": "A kihagyás ki van kapcsolva ehhez: '{task}'.",
|
||||
"not_found": "Nem találtam ehhez illő karbantartási feladatot: '{name}'.",
|
||||
"ambiguous": "Több feladatra is illik: {candidates}. Fogalmazz pontosabban.",
|
||||
"too_early": "'{task}': ezt a feladatot csak az esedékesség közelében lehet elvégezni.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Dimmi di quanto rimandarla — per esempio, di cinque giorni.",
|
||||
"postpone_past": "Così '{task}' finirebbe nel passato.",
|
||||
"skipped": "Ho saltato questo ciclo di '{task}' su {object}. Il prossimo è previsto per il {date}.",
|
||||
"skip_disabled": "Saltare è disattivato per '{task}'.",
|
||||
"not_found": "Non ho trovato nessuna attività di manutenzione che corrisponda a '{name}'.",
|
||||
"ambiguous": "Corrisponde a più attività: {candidates}. Puoi essere un po' più preciso?",
|
||||
"too_early": "Puoi completare '{task}' solo quando si avvicina la scadenza.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "どのくらい先に延ばすか教えてください。たとえば「5日」のように言ってください。",
|
||||
"postpone_past": "それだと「{task}」の日付が過去になってしまいます。",
|
||||
"skipped": "{object}の「{task}」の今回分をスキップしました。次回は{date}が期限です。",
|
||||
"skip_disabled": "'{task}'のスキップは無効になっています。",
|
||||
"not_found": "「{name}」に一致するメンテナンスタスクは見つかりませんでした。",
|
||||
"ambiguous": "いくつかのタスクが当てはまります:{candidates}。もう少し詳しく言ってください。",
|
||||
"too_early": "「{task}」は期限が近づいてからでないと完了にできません。",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "얼마나 미룰지 알려주세요. 예를 들어 '닷새 뒤'처럼요.",
|
||||
"postpone_past": "그러면 '{task}' 날짜가 과거가 돼요.",
|
||||
"skipped": "{object}의 '{task}', 이번 차례는 건너뛰었어요. 다음은 {date}에 예정돼 있어요.",
|
||||
"skip_disabled": "'{task}'의 건너뛰기가 꺼져 있습니다.",
|
||||
"not_found": "'{name}'에 맞는 정비 작업을 찾지 못했어요.",
|
||||
"ambiguous": "여러 작업이 해당돼요: {candidates}. 조금 더 구체적으로 말씀해 주세요.",
|
||||
"too_early": "'{task}' 작업은 예정일이 가까워져야 완료할 수 있어요.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Si hvor langt jeg skal utsette den — for eksempel fem dager.",
|
||||
"postpone_past": "Da ville '{task}' havne i fortiden.",
|
||||
"skipped": "Jeg hoppet over denne runden av '{task}' på {object}. Neste runde skal gjøres {date}.",
|
||||
"skip_disabled": "Hopp over er slått av for '{task}'.",
|
||||
"not_found": "Jeg fant ingen vedlikeholdsoppgave som passer med '{name}'.",
|
||||
"ambiguous": "Det passer med flere oppgaver: {candidates}. Vær litt mer presis.",
|
||||
"too_early": "'{task}' kan først krysses av nærmere forfallsdatoen.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Zeg even hoe lang ik het moet uitstellen — bijvoorbeeld vijf dagen.",
|
||||
"postpone_past": "Dan zou '{task}' in het verleden liggen.",
|
||||
"skipped": "Ik heb deze ronde van '{task}' bij {object} overgeslagen. De volgende staat op {date}.",
|
||||
"skip_disabled": "Overslaan is uitgeschakeld voor '{task}'.",
|
||||
"not_found": "Ik kon geen onderhoudstaak vinden die past bij '{name}'.",
|
||||
"ambiguous": "Dat past bij meerdere taken: {candidates}. Kun je het iets preciezer zeggen?",
|
||||
"too_early": "'{task}' kun je pas afvinken als de datum dichterbij is.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Powiedz mi, o ile mam przełożyć — na przykład o pięć dni.",
|
||||
"postpone_past": "Wtedy '{task}' wypadłoby w przeszłości.",
|
||||
"skipped": "Ten cykl zadania '{task}' na urządzeniu {object} został pominięty. Następny termin to {date}.",
|
||||
"skip_disabled": "Pomijanie jest wyłączone dla '{task}'.",
|
||||
"not_found": "Nie znalazłem zadania konserwacyjnego pasującego do '{name}'.",
|
||||
"ambiguous": "To pasuje do kilku zadań: {candidates}. Doprecyzuj, proszę.",
|
||||
"too_early": "Zadanie '{task}' można wykonać dopiero bliżej terminu.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Me diga por quanto tempo adiar — por exemplo, cinco dias.",
|
||||
"postpone_past": "Isso colocaria '{task}' no passado.",
|
||||
"skipped": "Pulei este ciclo de '{task}' em {object}. O próximo vence em {date}.",
|
||||
"skip_disabled": "Pular está desativado para '{task}'.",
|
||||
"not_found": "Não encontrei nenhuma tarefa de manutenção que corresponda a '{name}'.",
|
||||
"ambiguous": "Isso corresponde a várias tarefas: {candidates}. Seja mais específico, por favor.",
|
||||
"too_early": "'{task}' só pode ser concluída mais perto da data de vencimento.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Diz-me quanto tempo queres adiar — por exemplo, cinco dias.",
|
||||
"postpone_past": "Assim '{task}' ficaria no passado.",
|
||||
"skipped": "Saltei este ciclo de '{task}' em {object}. O próximo está previsto para {date}.",
|
||||
"skip_disabled": "Ignorar está desativado para '{task}'.",
|
||||
"not_found": "Não encontrei nenhuma tarefa de manutenção que corresponda a '{name}'.",
|
||||
"ambiguous": "Isso corresponde a várias tarefas: {candidates}. Podes ser um pouco mais específico?",
|
||||
"too_early": "Só podes concluir '{task}' mais perto da data prevista.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Скажи, на сколько перенести — например, на пять дней.",
|
||||
"postpone_past": "Тогда '{task}' окажется в прошлом.",
|
||||
"skipped": "Этот цикл задачи '{task}' у устройства {object} пропущен. Следующий срок — {date}.",
|
||||
"skip_disabled": "Пропуск отключён для '{task}'.",
|
||||
"not_found": "Я не нашёл задачу обслуживания, подходящую под '{name}'.",
|
||||
"ambiguous": "Под это подходит несколько задач: {candidates}. Уточни, пожалуйста.",
|
||||
"too_early": "Задачу '{task}' можно выполнить только ближе к сроку.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Säg hur långt jag ska skjuta upp den — till exempel fem dagar.",
|
||||
"postpone_past": "Då skulle '{task}' hamna i det förflutna.",
|
||||
"skipped": "Jag har hoppat över den här omgången av '{task}' på {object}. Nästa omgång ska göras den {date}.",
|
||||
"skip_disabled": "Hoppa över är avstängt för '{task}'.",
|
||||
"not_found": "Jag hittade ingen underhållsuppgift som matchar '{name}'.",
|
||||
"ambiguous": "Det matchar flera uppgifter: {candidates}. Var lite mer specifik.",
|
||||
"too_early": "'{task}' går att bocka av först närmare förfallodatumet.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Ne kadar erteleyeceğimi söyle — örneğin beş gün.",
|
||||
"postpone_past": "Bu durumda '{task}' görevi geçmişte kalır.",
|
||||
"skipped": "{object} için '{task}' görevinin bu turu atlandı. Sıradaki {date} tarihinde.",
|
||||
"skip_disabled": "'{task}' için atlama kapalı.",
|
||||
"not_found": "'{name}' ile eşleşen bir bakım görevi bulamadım.",
|
||||
"ambiguous": "Birden fazla göreve uyuyor: {candidates}. Biraz daha açık söyler misin?",
|
||||
"too_early": "'{task}' görevi ancak son tarihine yaklaşınca tamamlanabilir.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "Скажи, на скільки перенести — наприклад, на п’ять днів.",
|
||||
"postpone_past": "Тоді '{task}' опиниться в минулому.",
|
||||
"skipped": "Цей цикл завдання '{task}' на пристрої {object} пропущено. Наступний термін — {date}.",
|
||||
"skip_disabled": "Пропуск вимкнено для '{task}'.",
|
||||
"not_found": "Я не знайшов завдання з обслуговування, яке відповідає '{name}'.",
|
||||
"ambiguous": "Цьому відповідає кілька завдань: {candidates}. Уточни, будь ласка.",
|
||||
"too_early": "Завдання '{task}' можна виконати лише ближче до терміну.",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"postpone_needs_when": "告诉我要推迟多久,比如推迟五天。",
|
||||
"postpone_past": "那样的话,“{task}”的日期就会落在过去了。",
|
||||
"skipped": "已跳过{object}的“{task}”这一次。下一次在{date}到期。",
|
||||
"skip_disabled": "已为'{task}'关闭跳过。",
|
||||
"not_found": "我没有找到和“{name}”对得上的维护任务。",
|
||||
"ambiguous": "有好几项任务都对得上:{candidates}。请说得更具体一些。",
|
||||
"too_early": "“{task}”要等到接近到期日才能完成。",
|
||||
|
||||
@@ -353,6 +353,14 @@ class TriggerConfigMixin:
|
||||
if "trigger_baseline_value" in prev_tc
|
||||
else {}
|
||||
),
|
||||
# #149: the session cap is panel-managed (the flow grew no
|
||||
# field) — carry it so an options-flow trigger edit doesn't
|
||||
# silently drop it.
|
||||
**(
|
||||
{"trigger_runtime_max_session_seconds": prev_tc["trigger_runtime_max_session_seconds"]}
|
||||
if "trigger_runtime_max_session_seconds" in prev_tc
|
||||
else {}
|
||||
),
|
||||
}
|
||||
return await next_step()
|
||||
|
||||
|
||||
@@ -1436,6 +1436,13 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
||||
_LOGGER.error("Task %s not found in entry %s", task_id, self.entry.title)
|
||||
return
|
||||
|
||||
# #150: the per-task skip lock is enforced HERE — the choke point
|
||||
# every surface funnels through (WS, voice, vacation preview).
|
||||
if merged[task_id].get("allow_skip") is False:
|
||||
raise ServiceValidationError(
|
||||
f"Skipping is disabled for task {merged[task_id].get('name', task_id)!r}"
|
||||
)
|
||||
|
||||
task = MaintenanceTask.from_dict(merged[task_id])
|
||||
# Skipping an already-overdue task means it lapsed → record it as MISSED
|
||||
# (not a deliberate skip). An explicit as_missed=True always wins.
|
||||
|
||||
@@ -100,6 +100,10 @@ def _inject_per_entity_state(config: dict[str, Any], entity_state: dict[str, Any
|
||||
config["trigger_accumulated_seconds"] = entity_state["accumulated_seconds"]
|
||||
if "on_since" in entity_state:
|
||||
config["trigger_on_since"] = entity_state["on_since"]
|
||||
# #149: what the current session already booked — the per-session cap
|
||||
# must survive a restart, or a stuck sensor books the cap again.
|
||||
if "session_booked_seconds" in entity_state:
|
||||
config["trigger_session_booked_seconds"] = entity_state["session_booked_seconds"]
|
||||
elif trigger_type == TriggerType.STATE_CHANGE:
|
||||
if "change_count" in entity_state:
|
||||
config["trigger_change_count"] = entity_state["change_count"]
|
||||
|
||||
@@ -59,6 +59,18 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._target_hours: float = trigger_config.get("trigger_runtime_hours", 100.0)
|
||||
self._accumulated_seconds: float = trigger_config.get("trigger_accumulated_seconds", 0.0)
|
||||
|
||||
# #149: optional per-session cap (seconds). A session that exceeds it
|
||||
# contributes exactly the cap — protects against a sensor stuck ON
|
||||
# (lost connection, restart while "running").
|
||||
raw_cap = trigger_config.get("trigger_runtime_max_session_seconds")
|
||||
self._max_session: float | None = (
|
||||
float(raw_cap) if isinstance(raw_cap, (int, float)) and not isinstance(raw_cap, bool) and raw_cap > 0 else None
|
||||
)
|
||||
# Seconds the CURRENT session already booked. Persisted, because the
|
||||
# 5-minute periodic persist resets the on_since window — the cap is
|
||||
# per SESSION, not per window.
|
||||
self._session_booked: float = float(trigger_config.get("trigger_session_booked_seconds", 0.0) or 0.0)
|
||||
|
||||
# Restore on_since timestamp for restart recovery. parse_persisted_utc
|
||||
# coerces a naive legacy payload to UTC — dt_util.parse_datetime kept
|
||||
# it naive here, and `utcnow() - naive` raises TypeError in the
|
||||
@@ -107,6 +119,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
now = dt_util.utcnow()
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self._session_booked = 0.0
|
||||
await self._persist_runtime()
|
||||
_LOGGER.debug(
|
||||
"Runtime trigger: entity %s is ON, started tracking from now",
|
||||
@@ -119,6 +132,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulate_elapsed()
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
await self._persist_runtime()
|
||||
|
||||
# Register state change listener
|
||||
@@ -184,6 +198,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
now = dt_util.utcnow()
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
elif not self._is_on(new_val) and self._on_since_dt is not None:
|
||||
# Restored anchor but the device APPEARS off (deferred setup
|
||||
@@ -195,6 +210,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulate_elapsed()
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
self._update_evaluation()
|
||||
return
|
||||
@@ -207,6 +223,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulate_elapsed()
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
if not self._logged_unavailable:
|
||||
_LOGGER.warning(
|
||||
@@ -234,6 +251,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulate_elapsed()
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
_LOGGER.debug(
|
||||
"Runtime trigger: %s turned OFF (accumulated=%.2fh)",
|
||||
@@ -245,6 +263,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
now = dt_util.utcnow()
|
||||
self._on_since_dt = now
|
||||
self._on_since = now.isoformat()
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
_LOGGER.debug(
|
||||
"Runtime trigger: %s turned ON (tracking started)",
|
||||
@@ -258,6 +277,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
self._accumulate_elapsed()
|
||||
self._on_since_dt = None
|
||||
self._on_since = None
|
||||
self._session_booked = 0.0
|
||||
self.hass.async_create_task(self._persist_runtime())
|
||||
|
||||
self._update_evaluation()
|
||||
@@ -279,12 +299,23 @@ class RuntimeTrigger(BaseTrigger):
|
||||
return str(state.state)
|
||||
|
||||
def _accumulate_elapsed(self, now: datetime | None = None) -> None:
|
||||
"""Add elapsed time since _on_since to accumulated total."""
|
||||
"""Add elapsed time since _on_since to accumulated total.
|
||||
|
||||
With a per-session cap (#149) the whole session contributes at most
|
||||
the cap: ``_session_booked`` remembers what this session booked across
|
||||
the 5-minute persist windows, so a stuck-ON sensor cannot book capped
|
||||
slices window after window.
|
||||
"""
|
||||
if self._on_since_dt is None:
|
||||
return
|
||||
now = now or dt_util.utcnow()
|
||||
elapsed = (now - self._on_since_dt).total_seconds()
|
||||
if elapsed <= 0:
|
||||
return
|
||||
if self._max_session is not None:
|
||||
elapsed = min(elapsed, max(0.0, self._max_session - self._session_booked))
|
||||
if elapsed > 0:
|
||||
self._session_booked += elapsed
|
||||
self._accumulated_seconds += elapsed
|
||||
|
||||
def _get_current_runtime_hours(self) -> float:
|
||||
@@ -293,6 +324,8 @@ class RuntimeTrigger(BaseTrigger):
|
||||
if self._on_since_dt is not None:
|
||||
elapsed = (dt_util.utcnow() - self._on_since_dt).total_seconds()
|
||||
if elapsed > 0:
|
||||
if self._max_session is not None:
|
||||
elapsed = min(elapsed, max(0.0, self._max_session - self._session_booked))
|
||||
total_seconds += elapsed
|
||||
return total_seconds / 3600.0
|
||||
|
||||
@@ -326,6 +359,7 @@ class RuntimeTrigger(BaseTrigger):
|
||||
data: dict[str, Any] = {
|
||||
"accumulated_seconds": self._accumulated_seconds,
|
||||
"on_since": self._on_since,
|
||||
"session_booked_seconds": self._session_booked,
|
||||
}
|
||||
await self._coordinator.async_persist_trigger_runtime(
|
||||
self._task_id,
|
||||
|
||||
@@ -117,6 +117,8 @@ def _build_export_object(
|
||||
"custom_icon": tdata.get("custom_icon"),
|
||||
"nfc_tag_id": tdata.get("nfc_tag_id"),
|
||||
"require_tag_scan": tdata.get("require_tag_scan"),
|
||||
# #150: per-task skip lock.
|
||||
"allow_skip": tdata.get("allow_skip"),
|
||||
"responsible_user_id": tdata.get("responsible_user_id"),
|
||||
"entity_slug": tdata.get("entity_slug"),
|
||||
"adaptive_config": tdata.get("adaptive_config"),
|
||||
|
||||
@@ -227,6 +227,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _triggerToState = "";
|
||||
@state() private _triggerTargetChanges = "";
|
||||
@state() private _triggerRuntimeHours = "";
|
||||
@state() private _triggerRuntimeMaxSession = "";
|
||||
// Comma-separated "running" states for the runtime trigger (#103) —
|
||||
// empty = the backend default ["on"]. Must roundtrip on edit: adopted
|
||||
// tasks ship e.g. ["mowing"], and dropping it on save silently stops
|
||||
@@ -245,6 +246,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
@state() private _lastPerformed = "";
|
||||
@state() private _nfcTagId = "";
|
||||
@state() private _requireTagScan = false;
|
||||
@state() private _allowSkip = true;
|
||||
// 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,
|
||||
@@ -400,6 +402,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._lastPerformed = task.last_performed || "";
|
||||
this._nfcTagId = task.nfc_tag_id || "";
|
||||
this._requireTagScan = !!task.require_tag_scan;
|
||||
this._allowSkip = task.allow_skip !== false;
|
||||
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).
|
||||
@@ -500,6 +503,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._triggerToState = tc.trigger_to_state || "";
|
||||
this._triggerTargetChanges = tc.trigger_target_changes?.toString() || "";
|
||||
this._triggerRuntimeHours = tc.trigger_runtime_hours?.toString() || "";
|
||||
this._triggerRuntimeMaxSession = tc.trigger_runtime_max_session_seconds?.toString() || "";
|
||||
this._triggerOnStates = (tc.trigger_on_states || []).join(", ");
|
||||
if (tc.type === "compound") {
|
||||
this._compoundLogic = tc.compound_logic === "OR" ? "OR" : "AND";
|
||||
@@ -551,6 +555,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._lastPerformed = "";
|
||||
this._nfcTagId = "";
|
||||
this._requireTagScan = false;
|
||||
this._allowSkip = true;
|
||||
this._readingUnit = "";
|
||||
this._consumesParts = {};
|
||||
this._responsibleUserId = null;
|
||||
@@ -610,6 +615,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
this._triggerToState = "";
|
||||
this._triggerTargetChanges = "";
|
||||
this._triggerRuntimeHours = "";
|
||||
this._triggerRuntimeMaxSession = "";
|
||||
this._triggerOnStates = "";
|
||||
this._compoundLogic = "AND";
|
||||
this._compoundConditions = [];
|
||||
@@ -1253,6 +1259,7 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
data.last_performed = this._lastPerformed || null;
|
||||
data.nfc_tag_id = this._nfcTagId || null;
|
||||
data.require_tag_scan = this._requireTagScan;
|
||||
data.allow_skip = this._allowSkip;
|
||||
data.reading_unit = this._readingUnit.trim() || null;
|
||||
// Task phases (#139): always sent — null clears a removed cycle.
|
||||
{
|
||||
@@ -1366,6 +1373,8 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
if (this._triggerForMinutes) { const v = parseInt(this._triggerForMinutes, 10); if (!isNaN(v)) triggerConfig.trigger_for_minutes = v; }
|
||||
} else if (this._triggerType === "runtime") {
|
||||
if (this._triggerRuntimeHours) { const v = parseFloat(this._triggerRuntimeHours); if (!isNaN(v)) triggerConfig.trigger_runtime_hours = v; }
|
||||
// #149: per-session cap (seconds) against sensors stuck ON.
|
||||
if (this._triggerRuntimeMaxSession) { const v = parseInt(this._triggerRuntimeMaxSession, 10); if (!isNaN(v) && v > 0) triggerConfig.trigger_runtime_max_session_seconds = v; }
|
||||
const onStates = this._triggerOnStates.split(",").map((s) => s.trim()).filter(Boolean);
|
||||
if (onStates.length > 0) triggerConfig.trigger_on_states = onStates;
|
||||
}
|
||||
@@ -2480,6 +2489,14 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
.value=${this._triggerRuntimeHours}
|
||||
@input=${(e: Event) => (this._triggerRuntimeHours = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
<ms-textfield
|
||||
label="${t("runtime_max_session", L)}"
|
||||
type="number"
|
||||
step="1"
|
||||
.value=${this._triggerRuntimeMaxSession}
|
||||
@input=${(e: Event) => (this._triggerRuntimeMaxSession = (e.target as HTMLInputElement).value)}
|
||||
></ms-textfield>
|
||||
<div class="field-help">${t("runtime_max_session_help", L)}</div>
|
||||
${this._renderOnStatesField({
|
||||
value: this._triggerOnStates,
|
||||
entityId: this._triggerEntityId,
|
||||
@@ -2831,6 +2848,15 @@ export class MaintenanceTaskDialog extends LitElement {
|
||||
<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="req-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${!this._allowSkip}
|
||||
@change=${(e: Event) => (this._allowSkip = !(e.target as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span>${t("disallow_skip", L)}</span>
|
||||
</label>
|
||||
${!this._allowSkip ? html`<div class="field-help">${t("disallow_skip_help", L)}</div>` : nothing}
|
||||
<label class="toggle-row">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
+9
-5
@@ -600,11 +600,15 @@ export class MaintenanceTaskQuickActionsDialog extends LitElement {
|
||||
<ha-icon slot="start" icon="mdi:check"></ha-icon>
|
||||
${t("complete", L) || "Complete"}
|
||||
</ha-button>
|
||||
<ha-button appearance="plain" variant="neutral" @click=${() => { this._showSkip = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>
|
||||
${t("skip", L) || "Skip"}
|
||||
</ha-button>
|
||||
<ha-button appearance="plain" variant="neutral" @click=${() => { this._showReset = true; }} .disabled=${this._busy}>
|
||||
${task.allow_skip !== false
|
||||
? html`
|
||||
<ha-button appearance="outlined" variant="neutral" @click=${() => { this._showSkip = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>
|
||||
${t("skip", L) || "Skip"}
|
||||
</ha-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-button appearance="outlined" variant="neutral" @click=${() => { this._showReset = true; }} .disabled=${this._busy}>
|
||||
<ha-icon slot="start" icon="mdi:restart"></ha-icon>
|
||||
${t("reset", L) || "Reset"}
|
||||
</ha-button>
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Podle oblasti",
|
||||
"groupby_group": "Podle skupiny",
|
||||
"groupby_user": "Podle uživatele",
|
||||
"groupby_object": "Podle objektu",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Uživatel",
|
||||
"photo_label": "Fotografie",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Aktuálně platná počáteční hodnota: {value}",
|
||||
"runtime_on_states": "Aktivní stavy",
|
||||
"runtime_on_states_help": "Stavy počítané jako doba běhu — výchozí: on. Např. mowing, cleaning, printing. Při zvoleném atributu se porovnávají jeho hodnoty.",
|
||||
"runtime_max_session": "Max. doba běhu relace (sekundy)",
|
||||
"runtime_max_session_help": "Jeden běh se počítá nejvýše na tolik sekund — chrání před senzorem zaseknutým na ZAP (ztráta spojení, restart). Prázdné = bez limitu.",
|
||||
"trend_approaching": "Blíží se k prahu",
|
||||
"trend_easing": "Vzdaluje se od prahu",
|
||||
"split_select_hint": "Vyberte úkol a zde se zobrazí podrobnosti",
|
||||
"setups_target_new": "Vytvořit nový: {name}",
|
||||
"schedule_preview_title": "Nejbližší termíny",
|
||||
"schedule_preview_ontime": "Za předpokladu včasného dokončení.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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'.",
|
||||
"disallow_skip": "Nepovolit přeskočení",
|
||||
"disallow_skip_help": "Akce Přeskočit zmizí ze všech ploch a server odmítne přeskočení i z automatizací a hlasu.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Efter område",
|
||||
"groupby_group": "Efter gruppe",
|
||||
"groupby_user": "Efter bruger",
|
||||
"groupby_object": "Efter objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruger",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Aktuelt gældende startværdi: {value}",
|
||||
"runtime_on_states": "Aktive tilstande",
|
||||
"runtime_on_states_help": "Tilstande der tæller som driftstid — standard: on. F.eks. mowing, cleaning, printing. Med en valgt attribut sammenlignes dens værdier i stedet.",
|
||||
"runtime_max_session": "Maks. sessionstid (sekunder)",
|
||||
"runtime_max_session_help": "En enkelt kørsel tæller højst så mange sekunder — beskytter mod en sensor, der hænger på TÆNDT (tabt forbindelse, genstart). Tom = ingen grænse.",
|
||||
"trend_approaching": "Nærmer sig tærsklen",
|
||||
"trend_easing": "Bevæger sig væk fra tærsklen",
|
||||
"split_select_hint": "Vælg en opgave for at se detaljerne her",
|
||||
"setups_target_new": "Opret ny: {name}",
|
||||
"schedule_preview_title": "Kommende datoer",
|
||||
"schedule_preview_ontime": "Forudsat rettidig fuldførelse.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Tillad ikke at springe over",
|
||||
"disallow_skip_help": "Handlingen Spring over forsvinder fra alle flader, og serveren afviser også spring fra automatiseringer og stemme.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Nach Bereich",
|
||||
"groupby_group": "Nach Gruppe",
|
||||
"groupby_user": "Nach Verantwortlichem",
|
||||
"groupby_object": "Nach Objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Benutzer",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Aktuell wirksamer Startwert: {value}",
|
||||
"runtime_on_states": "Aktive Zustände",
|
||||
"runtime_on_states_help": "Zustände, die als Laufzeit zählen — Standard: on. Z. B. mowing, cleaning, printing. Bei gewähltem Attribut werden dessen Werte verglichen.",
|
||||
"runtime_max_session": "Maximale Sitzungslaufzeit (Sekunden)",
|
||||
"runtime_max_session_help": "Ein einzelner Lauf zählt höchstens so viele Sekunden — schützt vor Sensoren, die auf AN hängen bleiben (Verbindungsabriss, Neustart). Leer = keine Begrenzung.",
|
||||
"trend_approaching": "Läuft auf die Schwelle zu",
|
||||
"trend_easing": "Entfernt sich von der Schwelle",
|
||||
"split_select_hint": "Wähle eine Aufgabe, um hier die Details zu sehen",
|
||||
"setups_target_new": "Neu anlegen: {name}",
|
||||
"schedule_preview_title": "Nächste Termine",
|
||||
"schedule_preview_ontime": "Bei pünktlichem Abschluss.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Überspringen nicht erlauben",
|
||||
"disallow_skip_help": "Die Aktion Überspringen verschwindet von allen Oberflächen; auch Automationen und Sprachbefehle werden abgewiesen.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "By area",
|
||||
"groupby_group": "By group",
|
||||
"groupby_user": "By user",
|
||||
"groupby_object": "By object",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "User",
|
||||
"photo_label": "Photo",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Currently effective start value: {value}",
|
||||
"runtime_on_states": "Active states",
|
||||
"runtime_on_states_help": "States that count as running — default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",
|
||||
"runtime_max_session": "Max session runtime (seconds)",
|
||||
"runtime_max_session_help": "A single run counts at most this many seconds — protects against a sensor stuck ON (lost connection, restart). Empty = no cap.",
|
||||
"trend_approaching": "Heading toward the threshold",
|
||||
"trend_easing": "Easing away from the threshold",
|
||||
"split_select_hint": "Select a task to see its details here",
|
||||
"setups_target_new": "Create new: {name}",
|
||||
"schedule_preview_title": "Next dates",
|
||||
"schedule_preview_ontime": "Assuming on-time completion.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Don't allow skipping",
|
||||
"disallow_skip_help": "The Skip action disappears from every surface, and the server refuses a skip from automations and voice too.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Por área",
|
||||
"groupby_group": "Por grupo",
|
||||
"groupby_user": "Por usuario",
|
||||
"groupby_object": "Por objeto",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuario",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valor inicial vigente: {value}",
|
||||
"runtime_on_states": "Estados activos",
|
||||
"runtime_on_states_help": "Estados que cuentan como en funcionamiento — predeterminado: on. P. ej. mowing, cleaning, printing. Con un atributo seleccionado se comparan sus valores.",
|
||||
"runtime_max_session": "Duración máxima por sesión (segundos)",
|
||||
"runtime_max_session_help": "Una sola ejecución cuenta como máximo estos segundos — protege contra un sensor atascado en ON (conexión perdida, reinicio). Vacío = sin límite.",
|
||||
"trend_approaching": "Se acerca al umbral",
|
||||
"trend_easing": "Se aleja del umbral",
|
||||
"split_select_hint": "Selecciona una tarea para ver sus detalles aquí",
|
||||
"setups_target_new": "Crear nuevo: {name}",
|
||||
"schedule_preview_title": "Próximas fechas",
|
||||
"schedule_preview_ontime": "Suponiendo finalización puntual.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "No permitir omitir",
|
||||
"disallow_skip_help": "La acción Omitir desaparece de todas las superficies y el servidor también rechaza omisiones desde automatizaciones y voz.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Alueen mukaan",
|
||||
"groupby_group": "Ryhmän mukaan",
|
||||
"groupby_user": "Käyttäjän mukaan",
|
||||
"groupby_object": "Kohteen mukaan",
|
||||
"filter_label": "Suodata",
|
||||
"user_label": "Käyttäjä",
|
||||
"photo_label": "Valokuva",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Nyt voimassa oleva aloitusarvo: {value}",
|
||||
"runtime_on_states": "Aktiiviset tilat",
|
||||
"runtime_on_states_help": "Tilat, jotka lasketaan käyntiajaksi — oletus: on. Esim. mowing, cleaning, printing. Jos attribuutti on valittu, verrataan sen arvoja.",
|
||||
"runtime_max_session": "Istunnon enimmäiskesto (sekuntia)",
|
||||
"runtime_max_session_help": "Yksi ajo lasketaan enintään näin moneksi sekunniksi — suojaa PÄÄLLÄ-tilaan jumittuneelta anturilta (katkennut yhteys, uudelleenkäynnistys). Tyhjä = ei rajaa.",
|
||||
"trend_approaching": "Lähestyy raja-arvoa",
|
||||
"trend_easing": "Loittonee raja-arvosta",
|
||||
"split_select_hint": "Valitse tehtävä nähdäksesi sen tiedot tässä",
|
||||
"setups_target_new": "Luo uusi: {name}",
|
||||
"schedule_preview_title": "Seuraavat päivämäärät",
|
||||
"schedule_preview_ontime": "Olettaen, että valmistuminen on ajallaan.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Estä ohittaminen",
|
||||
"disallow_skip_help": "Ohita-toiminto katoaa kaikista näkymistä, ja palvelin hylkää ohituksen myös automaatioista ja puheesta.",
|
||||
"require_tag_scan_hint": "Tämä tehtävä valmistuu vain skannaamalla sen NFC-tunniste tai QR-koodi itse laitteesta — tallennus tässä estetään."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Par zone",
|
||||
"groupby_group": "Par groupe",
|
||||
"groupby_user": "Par utilisateur",
|
||||
"groupby_object": "Par objet",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Utilisateur",
|
||||
"photo_label": "Photo",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valeur de départ actuellement en vigueur : {value}",
|
||||
"runtime_on_states": "États actifs",
|
||||
"runtime_on_states_help": "États comptés comme en fonctionnement — par défaut : on. P. ex. mowing, cleaning, printing. Si un attribut est sélectionné, ce sont ses valeurs qui sont comparées.",
|
||||
"runtime_max_session": "Durée max. par session (secondes)",
|
||||
"runtime_max_session_help": "Une seule exécution compte au plus ce nombre de secondes — protège contre un capteur bloqué sur ON (connexion perdue, redémarrage). Vide = pas de limite.",
|
||||
"trend_approaching": "Se rapproche du seuil",
|
||||
"trend_easing": "S'éloigne du seuil",
|
||||
"split_select_hint": "Sélectionnez une tâche pour voir ses détails ici",
|
||||
"setups_target_new": "Créer : {name}",
|
||||
"schedule_preview_title": "Prochaines échéances",
|
||||
"schedule_preview_ontime": "En supposant un achèvement ponctuel.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Ne pas autoriser à ignorer",
|
||||
"disallow_skip_help": "L'action Ignorer disparaît de toutes les surfaces et le serveur refuse aussi un saut via les automatisations et la voix.",
|
||||
"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é."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "क्षेत्र के अनुसार",
|
||||
"groupby_group": "समूह के अनुसार",
|
||||
"groupby_user": "उपयोगकर्ता के अनुसार",
|
||||
"groupby_object": "वस्तु के अनुसार",
|
||||
"filter_label": "फ़िल्टर",
|
||||
"user_label": "उपयोगकर्ता",
|
||||
"photo_label": "फ़ोटो",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "वर्तमान में प्रभावी प्रारंभिक मान: {value}",
|
||||
"runtime_on_states": "सक्रिय अवस्थाएँ",
|
||||
"runtime_on_states_help": "वे अवस्थाएँ जो चालू समय में गिनी जाती हैं — डिफ़ॉल्ट: on। जैसे mowing, cleaning, printing। विशेषता चुनी होने पर उसकी मानों की तुलना होती है।",
|
||||
"runtime_max_session": "प्रति सत्र अधिकतम रनटाइम (सेकंड)",
|
||||
"runtime_max_session_help": "एक रन अधिकतम इतने सेकंड गिना जाता है — ON पर अटके सेंसर (कनेक्शन टूटना, रीस्टार्ट) से बचाता है। खाली = कोई सीमा नहीं।",
|
||||
"trend_approaching": "सीमा की ओर बढ़ रहा है",
|
||||
"trend_easing": "सीमा से दूर जा रहा है",
|
||||
"split_select_hint": "विवरण यहाँ देखने के लिए कोई कार्य चुनें",
|
||||
"setups_target_new": "नया बनाएँ: {name}",
|
||||
"schedule_preview_title": "अगली तिथियाँ",
|
||||
"schedule_preview_ontime": "समय पर पूर्णता मानते हुए।",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "पिछली पूर्णता जोड़ें",
|
||||
"require_tag_scan": "केवल टैग स्कैन से पूर्ण करें",
|
||||
"require_tag_scan_help": "उपस्थिति प्रमाण: जब तक वस्तु पर लगा NFC टैग या QR कोड स्कैन न हो, हर जगह “पूर्ण” अवरुद्ध रहता है। ऑटोमेशन complete सेवा को 'via_tag_scan' दे सकते हैं।",
|
||||
"disallow_skip": "छोड़ने की अनुमति न दें",
|
||||
"disallow_skip_help": "छोड़ें क्रिया सभी सतहों से हट जाती है, और सर्वर ऑटोमेशन व वॉयस से छोड़ने को भी अस्वीकार करता है।",
|
||||
"require_tag_scan_hint": "यह कार्य केवल वस्तु पर लगे NFC टैग या QR कोड को स्कैन करके ही पूर्ण होता है — यहाँ सहेजना अस्वीकार होगा।"
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "Terület szerint",
|
||||
"groupby_group": "Csoport szerint",
|
||||
"groupby_user": "Felhasználó szerint",
|
||||
"groupby_object": "Objektum szerint",
|
||||
"filter_label": "Szűrő",
|
||||
"user_label": "Felhasználó",
|
||||
"photo_label": "Fénykép",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Jelenleg érvényes kezdőérték: {value}",
|
||||
"runtime_on_states": "Aktív állapotok",
|
||||
"runtime_on_states_help": "Működésként számító állapotok — alapértelmezés: on. Pl. mowing, cleaning, printing. Attribútum kiválasztásakor annak értékei számítanak.",
|
||||
"runtime_max_session": "Max. munkamenet-futásidő (másodperc)",
|
||||
"runtime_max_session_help": "Egy futás legfeljebb ennyi másodpercet számít — véd a BE állapotban beragadt érzékelő ellen (kapcsolatvesztés, újraindítás). Üres = nincs korlát.",
|
||||
"trend_approaching": "Közeledik a küszöbhöz",
|
||||
"trend_easing": "Távolodik a küszöbtől",
|
||||
"split_select_hint": "Válassz egy feladatot, és itt jelennek meg a részletei",
|
||||
"setups_target_new": "Új létrehozása: {name}",
|
||||
"schedule_preview_title": "Következő dátumok",
|
||||
"schedule_preview_ontime": "Időben történő elvégzést feltételezve.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Kihagyás tiltása",
|
||||
"disallow_skip_help": "A Kihagyás művelet minden felületről eltűnik, és a szerver az automatizálásokból és hangból érkező kihagyást is elutasítja.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Per area",
|
||||
"groupby_group": "Per gruppo",
|
||||
"groupby_user": "Per utente",
|
||||
"groupby_object": "Per oggetto",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Utente",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valore iniziale attualmente in vigore: {value}",
|
||||
"runtime_on_states": "Stati attivi",
|
||||
"runtime_on_states_help": "Stati che contano come in funzione — predefinito: on. Ad es. mowing, cleaning, printing. Con un attributo selezionato vengono confrontati i suoi valori.",
|
||||
"runtime_max_session": "Durata massima per sessione (secondi)",
|
||||
"runtime_max_session_help": "Una singola esecuzione conta al massimo questi secondi — protegge da un sensore bloccato su ON (connessione persa, riavvio). Vuoto = nessun limite.",
|
||||
"trend_approaching": "Si avvicina alla soglia",
|
||||
"trend_easing": "Si allontana dalla soglia",
|
||||
"split_select_hint": "Seleziona un'attività per vederne i dettagli qui",
|
||||
"setups_target_new": "Crea nuovo: {name}",
|
||||
"schedule_preview_title": "Prossime date",
|
||||
"schedule_preview_ontime": "Presumendo un completamento puntuale.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Non consentire di saltare",
|
||||
"disallow_skip_help": "L'azione Salta scompare da tutte le superfici e il server rifiuta anche i salti da automazioni e voce.",
|
||||
"require_tag_scan_hint": "Questa attività si completa solo scansionando il suo tag NFC o codice QR sull'oggetto — il salvataggio qui verrà rifiutato."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "エリア別",
|
||||
"groupby_group": "グループ別",
|
||||
"groupby_user": "ユーザー別",
|
||||
"groupby_object": "対象別",
|
||||
"filter_label": "絞り込み",
|
||||
"user_label": "ユーザー",
|
||||
"photo_label": "写真",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "現在有効な開始値:{value}",
|
||||
"runtime_on_states": "稼働中とみなす状態",
|
||||
"runtime_on_states_help": "稼働時間として数える状態 — 既定値:on。例:mowing, cleaning, printing。属性を選択している場合はその値が比較されます。",
|
||||
"runtime_max_session": "1回の最大稼働時間(秒)",
|
||||
"runtime_max_session_help": "1回の稼働は最大この秒数までカウント——ONのまま固まったセンサー(接続切れ・再起動)への保護です。空欄=制限なし。",
|
||||
"trend_approaching": "しきい値に近づいています",
|
||||
"trend_easing": "しきい値から遠ざかっています",
|
||||
"split_select_hint": "タスクを選択すると、ここに詳細が表示されます",
|
||||
"setups_target_new": "新規作成:{name}",
|
||||
"schedule_preview_title": "次回の予定日",
|
||||
"schedule_preview_ontime": "期日どおりに完了した場合。",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "過去の完了を追加",
|
||||
"require_tag_scan": "タグのスキャンでのみ完了可能",
|
||||
"require_tag_scan_help": "在席証明:対象物のNFCタグまたはQRコードをスキャンするまで、どの画面でも「完了」はブロックされます。オートメーションはcompleteサービスに 'via_tag_scan' を渡せます。",
|
||||
"disallow_skip": "スキップを許可しない",
|
||||
"disallow_skip_help": "スキップ操作はすべての画面から消え、オートメーションや音声からのスキップもサーバーが拒否します。",
|
||||
"require_tag_scan_hint": "このタスクは対象物のNFCタグまたはQRコードをスキャンしないと完了できません。ここでの保存は拒否されます。"
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "구역별",
|
||||
"groupby_group": "그룹별",
|
||||
"groupby_user": "사용자별",
|
||||
"groupby_object": "대상별",
|
||||
"filter_label": "필터",
|
||||
"user_label": "사용자",
|
||||
"photo_label": "사진",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "현재 적용 중인 시작 값: {value}",
|
||||
"runtime_on_states": "활성 상태",
|
||||
"runtime_on_states_help": "가동 중으로 계산할 상태입니다 — 기본값: on. 예: mowing, cleaning, printing. 속성을 선택한 경우 해당 속성 값과 비교합니다.",
|
||||
"runtime_max_session": "세션당 최대 가동 시간(초)",
|
||||
"runtime_max_session_help": "한 번의 가동은 최대 이 초까지만 집계됩니다 — ON에 멈춘 센서(연결 끊김, 재시작)로부터 보호합니다. 비워두면 제한 없음.",
|
||||
"trend_approaching": "임계값에 가까워지는 중",
|
||||
"trend_easing": "임계값에서 멀어지는 중",
|
||||
"split_select_hint": "작업을 선택하면 여기에 세부 정보가 표시됩니다",
|
||||
"setups_target_new": "새로 만들기: {name}",
|
||||
"schedule_preview_title": "다음 예정일",
|
||||
"schedule_preview_ontime": "제때 완료한다고 가정한 일정입니다.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "지난 완료 추가",
|
||||
"require_tag_scan": "태그 스캔으로만 완료 가능",
|
||||
"require_tag_scan_help": "현장 확인: 기기의 NFC 태그나 QR 코드를 스캔할 때까지 모든 화면에서 '완료'가 차단됩니다. 자동화는 complete 서비스에 'via_tag_scan'을 전달할 수 있습니다.",
|
||||
"disallow_skip": "건너뛰기 허용 안 함",
|
||||
"disallow_skip_help": "건너뛰기 동작이 모든 화면에서 사라지며, 서버는 자동화와 음성의 건너뛰기도 거부합니다.",
|
||||
"require_tag_scan_hint": "이 작업은 기기의 NFC 태그나 QR 코드를 스캔해야만 완료됩니다. 여기서 저장하면 거부됩니다."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Etter område",
|
||||
"groupby_group": "Etter gruppe",
|
||||
"groupby_user": "Etter bruker",
|
||||
"groupby_object": "Etter objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruker",
|
||||
"photo_label": "Bilde",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Gjeldende startverdi: {value}",
|
||||
"runtime_on_states": "Aktive tilstander",
|
||||
"runtime_on_states_help": "Tilstander som teller som driftstid — standard: on. F.eks. mowing, cleaning, printing. Med et valgt attributt sammenlignes verdiene dets i stedet.",
|
||||
"runtime_max_session": "Maks økt-varighet (sekunder)",
|
||||
"runtime_max_session_help": "En enkelt kjøring teller høyst så mange sekunder — beskytter mot en sensor som henger på PÅ (mistet forbindelse, omstart). Tom = ingen grense.",
|
||||
"trend_approaching": "Nærmer seg terskelen",
|
||||
"trend_easing": "Beveger seg bort fra terskelen",
|
||||
"split_select_hint": "Velg en oppgave for å se detaljene her",
|
||||
"setups_target_new": "Opprett ny: {name}",
|
||||
"schedule_preview_title": "Kommende datoer",
|
||||
"schedule_preview_ontime": "Forutsatt fullføring i tide.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Ikke tillat å hoppe over",
|
||||
"disallow_skip_help": "Handlingen Hopp over forsvinner fra alle flater, og serveren avviser også hopp fra automatiseringer og tale.",
|
||||
"require_tag_scan_hint": "Denne oppgaven fullføres bare ved å skanne NFC-taggen eller QR-koden på selve tingen — lagring her vil bli avvist."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Per gebied",
|
||||
"groupby_group": "Per groep",
|
||||
"groupby_user": "Per gebruiker",
|
||||
"groupby_object": "Per object",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Gebruiker",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Momenteel geldende startwaarde: {value}",
|
||||
"runtime_on_states": "Actieve statussen",
|
||||
"runtime_on_states_help": "Statussen die als draaitijd tellen — standaard: on. Bijv. mowing, cleaning, printing. Met een geselecteerd attribuut worden de waarden daarvan vergeleken.",
|
||||
"runtime_max_session": "Maximale sessieduur (seconden)",
|
||||
"runtime_max_session_help": "Eén run telt maximaal dit aantal seconden — beschermt tegen sensoren die op AAN blijven hangen (verbroken verbinding, herstart). Leeg = geen limiet.",
|
||||
"trend_approaching": "Beweegt richting de drempel",
|
||||
"trend_easing": "Beweegt weg van de drempel",
|
||||
"split_select_hint": "Selecteer een taak om hier de details te zien",
|
||||
"setups_target_new": "Nieuw aanmaken: {name}",
|
||||
"schedule_preview_title": "Volgende data",
|
||||
"schedule_preview_ontime": "Uitgaande van tijdige afronding.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Overslaan niet toestaan",
|
||||
"disallow_skip_help": "De actie Overslaan verdwijnt overal, en de server weigert ook een skip via automatiseringen en spraak.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Wg obszaru",
|
||||
"groupby_group": "Wg grupy",
|
||||
"groupby_user": "Wg użytkownika",
|
||||
"groupby_object": "Według obiektu",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Użytkownik",
|
||||
"photo_label": "Zdjęcie",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Obecnie obowiązująca wartość początkowa: {value}",
|
||||
"runtime_on_states": "Stany aktywne",
|
||||
"runtime_on_states_help": "Stany liczone jako czas pracy — domyślnie: on. Np. mowing, cleaning, printing. Przy wybranym atrybucie porównywane są jego wartości.",
|
||||
"runtime_max_session": "Maks. czas sesji (sekundy)",
|
||||
"runtime_max_session_help": "Pojedynczy przebieg liczy się najwyżej tyle sekund — chroni przed czujnikiem zawieszonym na WŁ. (utrata połączenia, restart). Puste = bez limitu.",
|
||||
"trend_approaching": "Zbliża się do progu",
|
||||
"trend_easing": "Oddala się od progu",
|
||||
"split_select_hint": "Wybierz zadanie, aby zobaczyć tutaj szczegóły",
|
||||
"setups_target_new": "Utwórz nowy: {name}",
|
||||
"schedule_preview_title": "Najbliższe terminy",
|
||||
"schedule_preview_ontime": "Przy założeniu terminowego ukończenia.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Nie zezwalaj na pomijanie",
|
||||
"disallow_skip_help": "Akcja Pomiń znika ze wszystkich widoków, a serwer odrzuca pominięcie także z automatyzacji i głosu.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "Por área",
|
||||
"groupby_group": "Por grupo",
|
||||
"groupby_user": "Por usuário",
|
||||
"groupby_object": "Por objeto",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuário",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valor inicial efetivo atual: {value}",
|
||||
"runtime_on_states": "Estados ativos",
|
||||
"runtime_on_states_help": "Estados que contam como em funcionamento — padrão: on. Ex.: mowing, cleaning, printing. Com um atributo selecionado, os valores dele são comparados.",
|
||||
"runtime_max_session": "Duração máxima por sessão (segundos)",
|
||||
"runtime_max_session_help": "Uma única execução conta no máximo esses segundos — protege contra um sensor travado em ON (conexão perdida, reinício). Vazio = sem limite.",
|
||||
"trend_approaching": "Aproximando-se do limite",
|
||||
"trend_easing": "Afastando-se do limite",
|
||||
"split_select_hint": "Selecione uma tarefa para ver os detalhes aqui",
|
||||
"setups_target_new": "Criar novo: {name}",
|
||||
"schedule_preview_title": "Próximas datas",
|
||||
"schedule_preview_ontime": "Supondo conclusão em dia.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Não permitir pular",
|
||||
"disallow_skip_help": "A ação Pular desaparece de todas as telas e o servidor também recusa pular via automações e voz.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -573,6 +573,7 @@
|
||||
"groupby_area": "Por área",
|
||||
"groupby_group": "Por grupo",
|
||||
"groupby_user": "Por usuário",
|
||||
"groupby_object": "Por objeto",
|
||||
"unassigned": "Não atribuído",
|
||||
"no_area": "Sem área",
|
||||
"has_overdue": "Tarefas em atraso",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valor inicial atualmente em vigor: {value}",
|
||||
"runtime_on_states": "Estados ativos",
|
||||
"runtime_on_states_help": "Estados que contam como em funcionamento — padrão: on. P. ex. mowing, cleaning, printing. Com um atributo selecionado, são comparados os valores dele.",
|
||||
"runtime_max_session": "Duração máxima por sessão (segundos)",
|
||||
"runtime_max_session_help": "Uma única execução conta no máximo estes segundos — protege contra um sensor preso em ON (ligação perdida, reinício). Vazio = sem limite.",
|
||||
"trend_approaching": "A aproximar-se do limiar",
|
||||
"trend_easing": "A afastar-se do limiar",
|
||||
"split_select_hint": "Selecione uma tarefa para ver os detalhes aqui",
|
||||
"setups_target_new": "Criar novo: {name}",
|
||||
"schedule_preview_title": "Próximas datas",
|
||||
"schedule_preview_ontime": "Assumindo conclusão pontual.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Não permitir ignorar",
|
||||
"disallow_skip_help": "A ação Ignorar desaparece de todas as superfícies e o servidor também recusa ignorar via automações e voz.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "По области",
|
||||
"groupby_group": "По группе",
|
||||
"groupby_user": "По пользователю",
|
||||
"groupby_object": "По объекту",
|
||||
"filter_label": "Фильтр",
|
||||
"user_label": "Пользователь",
|
||||
"photo_label": "Фото",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Действующее начальное значение: {value}",
|
||||
"runtime_on_states": "Активные состояния",
|
||||
"runtime_on_states_help": "Состояния, засчитываемые как работа — по умолчанию: on. Напр. mowing, cleaning, printing. Если выбран атрибут, сравниваются его значения.",
|
||||
"runtime_max_session": "Макс. время сеанса (секунды)",
|
||||
"runtime_max_session_help": "Один запуск засчитывается не более чем на столько секунд — защита от датчика, зависшего во включённом состоянии (обрыв связи, перезапуск). Пусто = без ограничения.",
|
||||
"trend_approaching": "Приближается к порогу",
|
||||
"trend_easing": "Отдаляется от порога",
|
||||
"split_select_hint": "Выберите задачу, чтобы увидеть здесь её детали",
|
||||
"setups_target_new": "Создать новый: {name}",
|
||||
"schedule_preview_title": "Ближайшие даты",
|
||||
"schedule_preview_ontime": "При условии своевременного выполнения.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "Добавить прошлое выполнение",
|
||||
"require_tag_scan": "Завершение только по скану метки",
|
||||
"require_tag_scan_help": "Подтверждение присутствия: «Готово» заблокировано везде, пока не отсканирована NFC-метка или QR-код на самом устройстве. Автоматизации могут передавать 'via_tag_scan' в службу complete.",
|
||||
"disallow_skip": "Запретить пропуск",
|
||||
"disallow_skip_help": "Действие «Пропустить» исчезает со всех экранов, а сервер отклоняет пропуск из автоматизаций и голосовых команд.",
|
||||
"require_tag_scan_hint": "Эта задача завершается только сканированием её NFC-метки или QR-кода на самом устройстве — сохранение здесь будет отклонено."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Per område",
|
||||
"groupby_group": "Per grupp",
|
||||
"groupby_user": "Per användare",
|
||||
"groupby_object": "Efter objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Användare",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Nu gällande startvärde: {value}",
|
||||
"runtime_on_states": "Aktiva tillstånd",
|
||||
"runtime_on_states_help": "Tillstånd som räknas som drifttid — standard: on. T.ex. mowing, cleaning, printing. Med ett valt attribut jämförs dess värden i stället.",
|
||||
"runtime_max_session": "Max körtid per session (sekunder)",
|
||||
"runtime_max_session_help": "En enskild körning räknas som högst så här många sekunder — skyddar mot en sensor som fastnat på PÅ (tappad anslutning, omstart). Tomt = ingen gräns.",
|
||||
"trend_approaching": "Närmar sig tröskeln",
|
||||
"trend_easing": "Rör sig bort från tröskeln",
|
||||
"split_select_hint": "Välj en uppgift för att se detaljerna här",
|
||||
"setups_target_new": "Skapa ny: {name}",
|
||||
"schedule_preview_title": "Kommande datum",
|
||||
"schedule_preview_ontime": "Förutsatt slutförande i tid.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Tillåt inte att hoppa över",
|
||||
"disallow_skip_help": "Åtgärden Hoppa över försvinner från alla ytor och servern nekar även hopp från automationer och röst.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "Alana göre",
|
||||
"groupby_group": "Gruba göre",
|
||||
"groupby_user": "Kullanıcıya göre",
|
||||
"groupby_object": "Nesneye göre",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Kullanıcı",
|
||||
"photo_label": "Fotoğraf",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Şu anda geçerli başlangıç değeri: {value}",
|
||||
"runtime_on_states": "Etkin durumlar",
|
||||
"runtime_on_states_help": "Çalışıyor sayılan durumlar — varsayılan: on. Örn. mowing, cleaning, printing. Bir öznitelik seçiliyse onun değerleri eşleştirilir.",
|
||||
"runtime_max_session": "Oturum başına azami çalışma süresi (saniye)",
|
||||
"runtime_max_session_help": "Tek bir çalışma en fazla bu kadar saniye sayılır — AÇIK durumda takılı kalan sensöre (bağlantı kopması, yeniden başlatma) karşı korur. Boş = sınır yok.",
|
||||
"trend_approaching": "Eşiğe yaklaşıyor",
|
||||
"trend_easing": "Eşikten uzaklaşıyor",
|
||||
"split_select_hint": "Ayrıntıları burada görmek için bir görev seçin",
|
||||
"setups_target_new": "Yeni oluştur: {name}",
|
||||
"schedule_preview_title": "Sonraki tarihler",
|
||||
"schedule_preview_ontime": "Zamanında tamamlama varsayılarak.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Atlamaya izin verme",
|
||||
"disallow_skip_help": "Atla eylemi tüm yüzeylerden kaybolur; sunucu otomasyonlardan ve sesten gelen atlamayı da reddeder.",
|
||||
"require_tag_scan_hint": "Bu görev yalnızca cihazın üzerindeki NFC etiketi veya QR kodu taranarak tamamlanır — buradan kaydetme reddedilecektir."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "За зоною",
|
||||
"groupby_group": "За групою",
|
||||
"groupby_user": "За користувачем",
|
||||
"groupby_object": "За об'єктом",
|
||||
"filter_label": "Фільтр",
|
||||
"user_label": "Користувач",
|
||||
"photo_label": "Фото",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Чинне початкове значення: {value}",
|
||||
"runtime_on_states": "Активні стани",
|
||||
"runtime_on_states_help": "Стани, що зараховуються як робота — типово: on. Напр. mowing, cleaning, printing. Якщо вибрано атрибут, порівнюються його значення.",
|
||||
"runtime_max_session": "Макс. час сеансу (секунди)",
|
||||
"runtime_max_session_help": "Один запуск зараховується щонайбільше на стільки секунд — захист від датчика, що завис в увімкненому стані (втрата зв'язку, перезапуск). Порожньо = без обмеження.",
|
||||
"trend_approaching": "Наближається до порога",
|
||||
"trend_easing": "Віддаляється від порога",
|
||||
"split_select_hint": "Виберіть завдання, щоб побачити тут його деталі",
|
||||
"setups_target_new": "Створити новий: {name}",
|
||||
"schedule_preview_title": "Найближчі дати",
|
||||
"schedule_preview_ontime": "За умови вчасного виконання.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "Додати минуле виконання",
|
||||
"require_tag_scan": "Завершення лише скануванням мітки",
|
||||
"require_tag_scan_help": "Підтвердження присутності: «Готово» заблоковано всюди, доки не відскановано NFC-мітку або QR-код на самому пристрої. Автоматизації можуть передати 'via_tag_scan' службі complete.",
|
||||
"disallow_skip": "Заборонити пропуск",
|
||||
"disallow_skip_help": "Дія «Пропустити» зникає з усіх поверхонь, а сервер відхиляє пропуск з автоматизацій і голосу.",
|
||||
"require_tag_scan_hint": "Це завдання завершується лише скануванням його NFC-мітки або QR-коду на самому пристрої — збереження тут буде відхилено."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "按区域分组",
|
||||
"groupby_group": "按分组",
|
||||
"groupby_user": "按负责人",
|
||||
"groupby_object": "按对象",
|
||||
"filter_label": "过滤器",
|
||||
"user_label": "用户",
|
||||
"photo_label": "照片",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "当前生效的起始读数:{value}",
|
||||
"runtime_on_states": "活动状态",
|
||||
"runtime_on_states_help": "计入运行时间的状态 — 默认:on。例如 mowing、cleaning、printing。选择了属性时将比较该属性的值。",
|
||||
"runtime_max_session": "单次运行最长时间(秒)",
|
||||
"runtime_max_session_help": "单次运行最多计入这么多秒——防止传感器卡在开启状态(连接丢失、重启)。留空 = 不限制。",
|
||||
"trend_approaching": "正接近阈值",
|
||||
"trend_easing": "正远离阈值",
|
||||
"split_select_hint": "选择一个任务,在此查看详情",
|
||||
"setups_target_new": "新建:{name}",
|
||||
"schedule_preview_title": "下次日期",
|
||||
"schedule_preview_ontime": "假设按时完成。",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "补录过去的完成记录",
|
||||
"require_tag_scan": "仅可通过扫描标签完成",
|
||||
"require_tag_scan_help": "在场证明:在扫描设备上的 NFC 标签或二维码之前,所有界面的“完成”均被阻止。自动化可向 complete 服务传递 'via_tag_scan'。",
|
||||
"disallow_skip": "不允许跳过",
|
||||
"disallow_skip_help": "跳过操作将从所有界面消失,服务器也会拒绝来自自动化和语音的跳过。",
|
||||
"require_tag_scan_hint": "此任务只能通过扫描设备上的 NFC 标签或二维码来完成——在此保存将被拒绝。"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ import "./components/saved-views-dialog";
|
||||
import type { MaintenanceSavedViewsDialog } from "./components/saved-views-dialog";
|
||||
import { type SparklineContext } from "./renderers/sparkline";
|
||||
import { buildCalendarBuckets, isoDateLocal, type CalendarEvent } from "./helpers/calendar-bucket";
|
||||
import { renderTriggerProgress, renderMiniSparkline } from "./renderers/progress";
|
||||
import { renderTriggerProgress, renderMiniSparkline, computeTrend } from "./renderers/progress";
|
||||
import { type HistoryContext } from "./renderers/history";
|
||||
import { renderUserBadge, type TaskDetailContext } from "./renderers/task-detail";
|
||||
// The task-detail sub-view as a web component (light-DOM, panel styles apply).
|
||||
@@ -112,7 +112,7 @@ interface PartsOverviewRow {
|
||||
}
|
||||
type SortMode = "due_date" | "object" | "type" | "task_name" | "area" | "assigned_user" | "group";
|
||||
type ObjectSortMode = "alphabetical" | "due_soonest" | "task_count";
|
||||
type GroupByMode = "none" | "area" | "group" | "user";
|
||||
type GroupByMode = "none" | "area" | "group" | "user" | "object";
|
||||
|
||||
// Chart dimension constants for mini sparklines (overview)
|
||||
|
||||
@@ -125,6 +125,10 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
* The wide row grid then cannot hold labelled action buttons, so the
|
||||
* compact (icon-only) form applies here too. Reflected for the CSS. */
|
||||
@property({ type: Boolean, reflect: true }) public tight = false;
|
||||
/** ≥1500px panel width (2026-09-01): master-detail split — the dashboard
|
||||
* keeps the task list on the left and docks the task detail on the right
|
||||
* (a mail client's preview pane). Reflected for the CSS. */
|
||||
@property({ type: Boolean, reflect: true }) public split = false;
|
||||
private _tightObserver: ResizeObserver | null = null;
|
||||
@property({ attribute: false }) public panel: Record<string, unknown> = {};
|
||||
|
||||
@@ -340,6 +344,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
// fragments. Below ~1000 the narrow layout is the honest choice;
|
||||
// from there up the chips track keeps ≥150 px of slack.
|
||||
if (w > 0) this.tight = w < 1000;
|
||||
if (w > 0) this.split = w >= 1500;
|
||||
});
|
||||
this._tightObserver.observe(this);
|
||||
}
|
||||
@@ -358,7 +363,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._objectSortMode = savedObj as ObjectSortMode;
|
||||
}
|
||||
const savedGroup = lsGet(LS_KEYS.groupBy);
|
||||
if (savedGroup && ["none", "area", "group", "user"].includes(savedGroup)) {
|
||||
if (savedGroup && ["none", "area", "group", "user", "object"].includes(savedGroup)) {
|
||||
this._groupByMode = savedGroup as GroupByMode;
|
||||
}
|
||||
const savedView = lsGet(LS_KEYS.objectView);
|
||||
@@ -822,6 +827,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
entry_id: obj.entry_id,
|
||||
task_id: task.id,
|
||||
object_name: obj.object.name,
|
||||
allow_skip: task.allow_skip !== false,
|
||||
task_name: task.name,
|
||||
type: task.type,
|
||||
schedule_type: task.schedule_type,
|
||||
@@ -1023,7 +1029,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
if (["due_date", "object", "type", "task_name", "area", "assigned_user", "group"].includes(f.sort_mode)) {
|
||||
this._sortMode = f.sort_mode as SortMode;
|
||||
}
|
||||
if (["none", "area", "group", "user"].includes(f.group_by)) {
|
||||
if (["none", "area", "group", "user", "object"].includes(f.group_by)) {
|
||||
this._groupByMode = f.group_by as GroupByMode;
|
||||
}
|
||||
// Persist sort/group like the manual controls do, so they stick after reload.
|
||||
@@ -1077,7 +1083,28 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
this._scrollContentToTop();
|
||||
}
|
||||
|
||||
/** Master-detail split is active: wide panel, dashboard tab, no bulk. */
|
||||
private _splitActive(): boolean {
|
||||
return this.split && !this.narrow && !this.tight && this._view === "overview" && this._overviewTab === "dashboard" && !this._bulkMode;
|
||||
}
|
||||
|
||||
private _showTask(entryId: string, taskId: string): void {
|
||||
// Master-detail split (2026-09-01): same data prep, but no view switch
|
||||
// and no history entry — the detail docks on the right like a mail
|
||||
// client's preview pane. The detail's breadcrumb (showTaskView) still
|
||||
// opens the full page.
|
||||
if (this._splitActive()) {
|
||||
this._selectedEntryId = entryId;
|
||||
this._selectedTaskId = taskId;
|
||||
this._activeTab = "overview";
|
||||
this._historyFilter = null;
|
||||
this._fetchFullHistory(entryId, taskId);
|
||||
const task = this._getTask(entryId, taskId);
|
||||
if (task?.trigger_config?.entity_id) {
|
||||
this._fetchDetailStats(task.trigger_config.entity_id, this._isCounterEntity(task.trigger_config));
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._pushPanelState("task", entryId, taskId);
|
||||
this._view = "task";
|
||||
this._selectedEntryId = entryId;
|
||||
@@ -2496,6 +2523,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<option value="area" ?selected=${this._groupByMode === "area"}>${t("groupby_area", L)}</option>
|
||||
${this._features.groups ? html`<option value="group" ?selected=${this._groupByMode === "group"}>${t("groupby_group", L)}</option>` : nothing}
|
||||
<option value="user" ?selected=${this._groupByMode === "user"}>${t("groupby_user", L)}</option>
|
||||
<option value="object" ?selected=${this._groupByMode === "object"}>${t("groupby_object", L)}</option>
|
||||
</select>
|
||||
</label>
|
||||
${archivedCount > 0 ? html`
|
||||
@@ -2541,7 +2569,20 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
`
|
||||
: html`
|
||||
${this._bulkMode ? this._renderBulkBar(rows, L) : nothing}
|
||||
${this._groupByMode === "none"
|
||||
${this._splitActive()
|
||||
? html`
|
||||
<div class="split-layout">
|
||||
<div class="split-list">
|
||||
${this._groupByMode === "none" ? this._renderTaskTable(rows) : this._renderGroupedTasks(rows, L)}
|
||||
</div>
|
||||
<div class="split-pane">
|
||||
${this._selectedEntryId && this._selectedTaskId && this._getTask(this._selectedEntryId, this._selectedTaskId)
|
||||
? this._renderTaskDetail()
|
||||
: html`<div class="split-pane-empty"><ha-icon icon="mdi:cursor-default-click-outline"></ha-icon><p>${t("split_select_hint", L)}</p></div>`}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: this._groupByMode === "none"
|
||||
? this._renderTaskTable(rows)
|
||||
: this._renderGroupedTasks(rows, L)}
|
||||
`}
|
||||
@@ -2661,6 +2702,9 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
keys = [u || noneLabel];
|
||||
} else if (this._groupByMode === "group") {
|
||||
keys = row.group_names.length > 0 ? row.group_names : [noneLabel];
|
||||
} else if (this._groupByMode === "object") {
|
||||
// #150(3): group the dashboard by the owning object/device.
|
||||
keys = [row.object_name];
|
||||
}
|
||||
for (const k of keys) {
|
||||
if (!groups.has(k)) groups.set(k, []);
|
||||
@@ -2675,6 +2719,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
});
|
||||
const icon = this._groupByMode === "area" ? "mdi:map-marker-outline"
|
||||
: this._groupByMode === "group" ? "mdi:folder-outline"
|
||||
: this._groupByMode === "object" ? "mdi:cube-outline"
|
||||
: "mdi:account-outline";
|
||||
return html`
|
||||
${sorted.map(([key, taskRows]) => html`
|
||||
@@ -3220,7 +3265,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
|
||||
const bulkSelected = this._bulkMode && this._bulkSelected.has(this._bulkKey(row));
|
||||
return html`
|
||||
<div class="task-row${!row.enabled ? ' task-disabled' : ''}${bulkSelected ? ' bulk-selected' : ''}">
|
||||
<div class="task-row${!row.enabled ? ' task-disabled' : ''}${bulkSelected ? ' bulk-selected' : ''}${this._splitActive() && row.entry_id === this._selectedEntryId && row.task_id === this._selectedTaskId ? ' selected' : ''}">
|
||||
${this._bulkMode ? html`
|
||||
<label class="cell bulk-check" @click=${(e: Event) => e.stopPropagation()}>
|
||||
<input type="checkbox" .checked=${bulkSelected} @change=${() => this._toggleBulkRow(row)} />
|
||||
@@ -3260,13 +3305,13 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
? html`<div class="days-bar"><div class="days-bar-fill${daysOverflow ? " overflow" : ""}" style="width:${pct}%;background:${barColor}"></div></div>`
|
||||
: nothing}
|
||||
${row.trigger_config
|
||||
? renderTriggerProgress(row)
|
||||
? renderTriggerProgress(row, { trend: computeTrend(row, this._miniStatsData), lang: L })
|
||||
: !hasDaysBar && row.trigger_active
|
||||
? html`<span style="color:var(--maint-triggered-color);font-weight:600">⚡</span>`
|
||||
: nothing}
|
||||
${renderMiniSparkline(row, this._miniStatsData, this._lang)}
|
||||
</span>
|
||||
${this._renderRowActions(L, () => this._openCompleteDialogForRow(row), () => this._promptSkipTask(row.entry_id, row.task_id))}
|
||||
${this._renderRowActions(L, () => this._openCompleteDialogForRow(row), () => this._promptSkipTask(row.entry_id, row.task_id), row.allow_skip)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -3296,7 +3341,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
/** #145: row actions as HA buttons — labelled (accent/success + plain, the
|
||||
* task-detail pairing) or, for "buttons_compact" on narrow screens,
|
||||
* icon-only — or the classic icon pair when the household chose "icons". */
|
||||
private _renderRowActions(L: string, onComplete: () => void, onSkip: () => void): TemplateResult {
|
||||
private _renderRowActions(L: string, onComplete: () => void, onSkip: () => void, allowSkip = true): TemplateResult {
|
||||
const style = this._actionStyle();
|
||||
if (style === "buttons" || style === "buttons_compact") {
|
||||
const iconOnly = style === "buttons_compact" && (this.narrow || this.tight);
|
||||
@@ -3308,9 +3353,10 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<ha-button size="small" appearance="accent" variant="success" title="${t("complete", L)}" @click=${(e: Event) => { e.stopPropagation(); onComplete(); }}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
</ha-button>
|
||||
<ha-icon-button .label=${t("skip", L)} title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-icon icon="mdi:skip-next"></ha-icon>
|
||||
</ha-icon-button>
|
||||
${allowSkip ? html`
|
||||
<ha-icon-button .label=${t("skip", L)} title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-icon icon="mdi:skip-next"></ha-icon>
|
||||
</ha-icon-button>` : nothing}
|
||||
</span>`;
|
||||
}
|
||||
return html`
|
||||
@@ -3318,9 +3364,10 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<ha-button size="small" appearance="accent" variant="success" title="${t("complete", L)}" @click=${(e: Event) => { e.stopPropagation(); onComplete(); }}>
|
||||
<ha-icon slot="start" icon="mdi:check"></ha-icon>${t("complete", L)}
|
||||
</ha-button>
|
||||
<ha-button size="small" appearance="plain" variant="neutral" title="${t("skip", L)}" ?disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>${t("skip", L)}
|
||||
</ha-button>
|
||||
${allowSkip ? html`
|
||||
<ha-button size="small" appearance="outlined" variant="neutral" title="${t("skip", L)}" ?disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>${t("skip", L)}
|
||||
</ha-button>` : nothing}
|
||||
</span>`;
|
||||
}
|
||||
return html`
|
||||
@@ -3328,9 +3375,10 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<mwc-icon-button class="btn-complete" title="${t("complete", L)}" @click=${(e: Event) => { e.stopPropagation(); onComplete(); }}>
|
||||
<ha-icon icon="mdi:check"></ha-icon>
|
||||
</mwc-icon-button>
|
||||
<mwc-icon-button class="btn-skip" title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-icon icon="mdi:skip-next"></ha-icon>
|
||||
</mwc-icon-button>
|
||||
${allowSkip ? html`
|
||||
<mwc-icon-button class="btn-skip" title="${t("skip", L)}" .disabled=${this._actionLoading} @click=${(e: Event) => { e.stopPropagation(); onSkip(); }}>
|
||||
<ha-icon icon="mdi:skip-next"></ha-icon>
|
||||
</mwc-icon-button>` : nothing}
|
||||
</span>`;
|
||||
}
|
||||
|
||||
@@ -3470,7 +3518,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
<span class="due-cell" @click=${() => this._showTask(obj.entry_id, task.id)}>
|
||||
<span class="due-text">${formatDueDays(task.days_until_due, L)}</span>
|
||||
${task.trigger_config
|
||||
? renderTriggerProgress(task)
|
||||
? renderTriggerProgress(task, { trend: computeTrend(task, this._miniStatsData), lang: L })
|
||||
: nothing}
|
||||
${renderMiniSparkline(task, this._miniStatsData, this._lang)}
|
||||
</span>
|
||||
@@ -3478,6 +3526,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
||||
L,
|
||||
() => this._openCompleteDialog(obj.entry_id, task.id, task.name, this._features.checklists ? task.checklist : undefined, this._features.adaptive && !!task.adaptive_config?.enabled),
|
||||
() => this._promptSkipTask(obj.entry_id, task.id),
|
||||
task.allow_skip,
|
||||
)}
|
||||
</div>
|
||||
`)}</div>`}
|
||||
|
||||
+115
-115
@@ -18,7 +18,7 @@
|
||||
"@web/dev-server-esbuild": "^2.0.0",
|
||||
"@web/test-runner": "^1.0.0",
|
||||
"@web/test-runner-playwright": "^1.0.0",
|
||||
"esbuild": "^0.28.1",
|
||||
"esbuild": "^0.28.2",
|
||||
"lit": "^3.3.3",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
@@ -51,9 +51,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
|
||||
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -68,9 +68,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
|
||||
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -85,9 +85,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -102,9 +102,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -119,9 +119,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -136,9 +136,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -153,9 +153,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -170,9 +170,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -187,9 +187,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
|
||||
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
|
||||
"integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -204,9 +204,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -221,9 +221,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
|
||||
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -238,9 +238,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
|
||||
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
|
||||
"integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -255,9 +255,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
|
||||
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
|
||||
"integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -272,9 +272,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
|
||||
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
|
||||
"integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -289,9 +289,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
|
||||
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
|
||||
"integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -306,9 +306,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
|
||||
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
|
||||
"integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -323,9 +323,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -340,9 +340,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -357,9 +357,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -374,9 +374,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -391,9 +391,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -408,9 +408,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -425,9 +425,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -442,9 +442,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
|
||||
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
|
||||
"integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -459,9 +459,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
|
||||
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
|
||||
"integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -476,9 +476,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
|
||||
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
|
||||
"integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3204,9 +3204,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.28.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
|
||||
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
|
||||
"integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -3217,32 +3217,32 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.28.1",
|
||||
"@esbuild/android-arm": "0.28.1",
|
||||
"@esbuild/android-arm64": "0.28.1",
|
||||
"@esbuild/android-x64": "0.28.1",
|
||||
"@esbuild/darwin-arm64": "0.28.1",
|
||||
"@esbuild/darwin-x64": "0.28.1",
|
||||
"@esbuild/freebsd-arm64": "0.28.1",
|
||||
"@esbuild/freebsd-x64": "0.28.1",
|
||||
"@esbuild/linux-arm": "0.28.1",
|
||||
"@esbuild/linux-arm64": "0.28.1",
|
||||
"@esbuild/linux-ia32": "0.28.1",
|
||||
"@esbuild/linux-loong64": "0.28.1",
|
||||
"@esbuild/linux-mips64el": "0.28.1",
|
||||
"@esbuild/linux-ppc64": "0.28.1",
|
||||
"@esbuild/linux-riscv64": "0.28.1",
|
||||
"@esbuild/linux-s390x": "0.28.1",
|
||||
"@esbuild/linux-x64": "0.28.1",
|
||||
"@esbuild/netbsd-arm64": "0.28.1",
|
||||
"@esbuild/netbsd-x64": "0.28.1",
|
||||
"@esbuild/openbsd-arm64": "0.28.1",
|
||||
"@esbuild/openbsd-x64": "0.28.1",
|
||||
"@esbuild/openharmony-arm64": "0.28.1",
|
||||
"@esbuild/sunos-x64": "0.28.1",
|
||||
"@esbuild/win32-arm64": "0.28.1",
|
||||
"@esbuild/win32-ia32": "0.28.1",
|
||||
"@esbuild/win32-x64": "0.28.1"
|
||||
"@esbuild/aix-ppc64": "0.28.2",
|
||||
"@esbuild/android-arm": "0.28.2",
|
||||
"@esbuild/android-arm64": "0.28.2",
|
||||
"@esbuild/android-x64": "0.28.2",
|
||||
"@esbuild/darwin-arm64": "0.28.2",
|
||||
"@esbuild/darwin-x64": "0.28.2",
|
||||
"@esbuild/freebsd-arm64": "0.28.2",
|
||||
"@esbuild/freebsd-x64": "0.28.2",
|
||||
"@esbuild/linux-arm": "0.28.2",
|
||||
"@esbuild/linux-arm64": "0.28.2",
|
||||
"@esbuild/linux-ia32": "0.28.2",
|
||||
"@esbuild/linux-loong64": "0.28.2",
|
||||
"@esbuild/linux-mips64el": "0.28.2",
|
||||
"@esbuild/linux-ppc64": "0.28.2",
|
||||
"@esbuild/linux-riscv64": "0.28.2",
|
||||
"@esbuild/linux-s390x": "0.28.2",
|
||||
"@esbuild/linux-x64": "0.28.2",
|
||||
"@esbuild/netbsd-arm64": "0.28.2",
|
||||
"@esbuild/netbsd-x64": "0.28.2",
|
||||
"@esbuild/openbsd-arm64": "0.28.2",
|
||||
"@esbuild/openbsd-x64": "0.28.2",
|
||||
"@esbuild/openharmony-arm64": "0.28.2",
|
||||
"@esbuild/sunos-x64": "0.28.2",
|
||||
"@esbuild/win32-arm64": "0.28.2",
|
||||
"@esbuild/win32-ia32": "0.28.2",
|
||||
"@esbuild/win32-x64": "0.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@@ -4773,12 +4773,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.0.tgz",
|
||||
"integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==",
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
|
||||
"integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.62.0"
|
||||
"playwright-core": "1.62.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@@ -4791,9 +4791,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.0.tgz",
|
||||
"integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==",
|
||||
"version": "1.62.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
|
||||
"integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"@web/dev-server-esbuild": "^2.0.0",
|
||||
"@web/test-runner": "^1.0.0",
|
||||
"@web/test-runner-playwright": "^1.0.0",
|
||||
"esbuild": "^0.28.1",
|
||||
"esbuild": "^0.28.2",
|
||||
"lit": "^3.3.3",
|
||||
"react": "^19.2.8",
|
||||
"react-dom": "^19.2.8",
|
||||
|
||||
@@ -229,6 +229,51 @@ export const panelStyles = css`
|
||||
}
|
||||
}
|
||||
.bulk-check { display: flex; align-items: center; justify-content: center; cursor: pointer; }
|
||||
|
||||
/* Master-detail split (>=1500px panel, 2026-09-01): list left, docked task
|
||||
detail right - the width finally carries content instead of slack. Only
|
||||
the dashboard tab uses it; below the threshold everything is unchanged. */
|
||||
.split-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(430px, 41%);
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
.split-list { min-width: 0; }
|
||||
/* Inside the split the list is ~60% wide — the type and chips columns are
|
||||
redundant there (the docked detail names the type, assignee and labels),
|
||||
and squeezed chips read as clutter. Drop both; the task name gets the
|
||||
room and the list stays calm. */
|
||||
.split-list .task-table { grid-template-columns: auto minmax(100px, 180px) minmax(120px, 1fr) 150px auto; }
|
||||
.split-list .task-table.bulk { grid-template-columns: auto auto minmax(100px, 180px) minmax(120px, 1fr) 150px auto; }
|
||||
.split-list .cell.type { display: none; }
|
||||
.split-list .task-sub { display: none; }
|
||||
.split-pane {
|
||||
min-width: 0;
|
||||
position: sticky;
|
||||
top: 8px;
|
||||
max-height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 12px;
|
||||
padding: 4px 16px 16px;
|
||||
background: var(--card-background-color, #fff);
|
||||
}
|
||||
.split-pane-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 240px;
|
||||
gap: 8px;
|
||||
color: var(--secondary-text-color);
|
||||
text-align: center;
|
||||
}
|
||||
.split-pane-empty ha-icon { --mdc-icon-size: 40px; opacity: 0.5; }
|
||||
.task-row.selected {
|
||||
background: rgba(var(--rgb-primary-color, 3, 169, 244), 0.1);
|
||||
box-shadow: inset 3px 0 0 var(--primary-color);
|
||||
}
|
||||
.bulk-check input, .bulk-selectall input { width: 17px; height: 17px; cursor: pointer; accent-color: var(--primary-color); }
|
||||
.task-row.bulk-selected { background: color-mix(in srgb, var(--primary-color) 12%, transparent); }
|
||||
.bulk-bar {
|
||||
@@ -1457,7 +1502,16 @@ export const panelStyles = css`
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) .task-table { display: block; }
|
||||
/* Shared column tracks across rows (the wide layout's #66 lesson, applied
|
||||
to narrow 2026-09-01): with per-row grids the auto badge/actions columns
|
||||
sized independently, so the object-name column started at a different X
|
||||
in every row — an "OK" pill vs "Due Soon" + priority chevron made the
|
||||
list read jittery. The LIST owns the four tracks, rows subgrid them. */
|
||||
:host([narrow]) .task-table {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(76px, 1fr) 100px auto;
|
||||
column-gap: 8px;
|
||||
}
|
||||
|
||||
:host([narrow]) .task-row {
|
||||
/* Mobile: 4-column grid keeps due-cell + actions at deterministic
|
||||
@@ -1469,10 +1523,9 @@ export const panelStyles = css`
|
||||
Task-name spans the full top row (own row above), chips span the
|
||||
full bottom row. */
|
||||
display: grid;
|
||||
grid-column: auto;
|
||||
grid-template-columns: auto minmax(76px, 1fr) fit-content(100px) auto; /* name column keeps a word-sized floor, the due column hugs its content — at 400 px the object name used to be clipped mid-word (2026-08-30) */
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: auto auto auto;
|
||||
column-gap: 8px;
|
||||
row-gap: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
@@ -1486,6 +1539,13 @@ export const panelStyles = css`
|
||||
:host([narrow]) .cell-badges {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
/* Stack extra badges (priority chevron, NFC, disabled) under the status
|
||||
pill: in the shared-track table the widest badge ROW sized column 1
|
||||
for every row, and a pill+chevron combo pushed Complete/Skip off the
|
||||
right edge at phone widths (2026-09-01). */
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
:host([narrow]) .cell.object-name {
|
||||
grid-column: 2;
|
||||
@@ -1540,26 +1600,30 @@ export const panelStyles = css`
|
||||
the wide subgrid overflowed its last column at that width even before
|
||||
the buttons. Same rules as the :host([narrow]) block above / the
|
||||
max-width:768px media mirror below, keyed on the panel's own width. */
|
||||
:host([tight]:not([narrow])) .task-table { display: block; }
|
||||
:host([tight]:not([narrow])) .task-table {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(76px, 1fr) 100px auto;
|
||||
column-gap: 8px;
|
||||
}
|
||||
:host([tight]:not([narrow])) .task-row {
|
||||
display: grid;
|
||||
grid-column: auto;
|
||||
grid-template-columns: auto minmax(76px, 1fr) fit-content(100px) auto; /* name column keeps a word-sized floor, the due column hugs its content — at 400 px the object name used to be clipped mid-word (2026-08-30) */
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: auto auto auto;
|
||||
column-gap: 8px;
|
||||
row-gap: 4px;
|
||||
padding: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
:host([tight]:not([narrow])) .cell.type { display: none; }
|
||||
:host([tight]:not([narrow])) .cell.task-name { grid-column: 1 / -1; grid-row: 1; min-width: 0; }
|
||||
:host([tight]:not([narrow])) .cell-badges { grid-column: 1; grid-row: 2; }
|
||||
:host([tight]:not([narrow])) .cell-badges { grid-column: 1; grid-row: 2; flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
:host([tight]:not([narrow])) .cell.object-name { grid-column: 2; grid-row: 2; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.2; }
|
||||
:host([tight]:not([narrow])) .due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; }
|
||||
:host([tight]:not([narrow])) .row-actions { grid-column: 4; grid-row: 2; }
|
||||
:host([tight]:not([narrow])) .task-sub { grid-column: 1 / -1; grid-row: 3; font-size: 11px; gap: 6px; justify-content: flex-start; flex-wrap: wrap; }
|
||||
:host([tight]:not([narrow])) .task-sub-empty { display: none; }
|
||||
:host([tight]:not([narrow])) .mini-sparkline { width: 50px; }
|
||||
:host([tight]:not([narrow])) .mini-sparkline { display: none; }
|
||||
:host([tight]:not([narrow])) .trend-arrow { display: inline; }
|
||||
:host([narrow]) .task-sub {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 3;
|
||||
@@ -1569,7 +1633,22 @@ export const panelStyles = css`
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
:host([narrow]) .task-sub-empty { display: none; }
|
||||
:host([narrow]) .mini-sparkline { width: 50px; }
|
||||
:host([narrow]) .mini-sparkline { display: none; }
|
||||
:host([narrow]) .trend-arrow { display: inline; }
|
||||
|
||||
/* Phone band (≤429px): with SHARED tracks the widest badge sizes column 1
|
||||
for every row, and the fixed 100px due column + both action buttons no
|
||||
longer fit — Skip fell off the right edge. Tighter floors, slimmer row
|
||||
padding and a smaller Skip icon-button. Bars stay uniform per
|
||||
breakpoint (100px above, 84px here). */
|
||||
@media (max-width: 429px) {
|
||||
:host([narrow]) .task-table {
|
||||
grid-template-columns: auto minmax(64px, 1fr) 84px auto;
|
||||
column-gap: 6px;
|
||||
}
|
||||
:host([narrow]) .task-row { padding: 12px 8px; }
|
||||
:host([narrow]) .row-actions.compact ha-icon-button { --mdc-icon-button-size: 32px; --mdc-icon-size: 20px; }
|
||||
}
|
||||
|
||||
:host([narrow]) .detail-header {
|
||||
flex-direction: column;
|
||||
@@ -1691,24 +1770,31 @@ export const panelStyles = css`
|
||||
.task-header-actions { width: 100%; justify-content: flex-start; flex-wrap: wrap; }
|
||||
.filter-bar { flex-wrap: wrap; }
|
||||
.filter-bar select { flex: 1; min-width: 0; }
|
||||
/* Mirror the :host([narrow]) grid layout for narrow desktop windows */
|
||||
/* Mirror the :host([narrow]) grid layout for narrow desktop windows —
|
||||
incl. the shared-track table so rows align (2026-09-01). */
|
||||
.task-table {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(76px, 1fr) 100px auto;
|
||||
column-gap: 8px;
|
||||
}
|
||||
.task-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(76px, 1fr) fit-content(100px) auto; /* name column keeps a word-sized floor, the due column hugs its content — at 400 px the object name used to be clipped mid-word (2026-08-30) */
|
||||
grid-column: 1 / -1;
|
||||
grid-template-columns: subgrid;
|
||||
grid-template-rows: auto auto auto;
|
||||
column-gap: 8px;
|
||||
row-gap: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
.cell.type { display: none; }
|
||||
.cell.task-name { grid-column: 1 / -1; grid-row: 1; min-width: 0; }
|
||||
.cell-badges { grid-column: 1; grid-row: 2; }
|
||||
.cell-badges { grid-column: 1; grid-row: 2; flex-direction: column; align-items: flex-start; gap: 4px; }
|
||||
.cell.object-name { grid-column: 2; grid-row: 2; min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.2; }
|
||||
.due-cell { grid-column: 3; grid-row: 2; align-items: flex-end; min-width: 0; }
|
||||
.row-actions { grid-column: 4; grid-row: 2; }
|
||||
.task-sub { grid-column: 1 / -1; grid-row: 3; font-size: 11px; gap: 6px; justify-content: flex-start; flex-wrap: wrap; }
|
||||
.task-sub-empty { display: none; }
|
||||
.mini-sparkline { width: 50px; }
|
||||
.mini-sparkline { display: none; }
|
||||
.trend-arrow { display: inline; }
|
||||
.detail-header { flex-direction: column; align-items: flex-start; }
|
||||
.info-grid { grid-template-columns: 1fr; }
|
||||
.history-filters-new { flex-direction: column; }
|
||||
|
||||
@@ -20,7 +20,59 @@ const MINI_SPARKLINE_W = 60;
|
||||
const MINI_SPARKLINE_H = 20;
|
||||
const MAX_MINI_POINTS = 30;
|
||||
|
||||
export function renderTriggerProgress(row: TaskRow | MaintenanceTask) {
|
||||
export type TrendState = "approaching" | "stable" | "easing";
|
||||
const TREND_GLYPHS: Record<TrendState, string> = { approaching: "\u2197", stable: "\u2192", easing: "\u2198" };
|
||||
|
||||
/** #150 follow-up: on narrow/tight rows the squeezed sparkline is unreadable
|
||||
* — this reduces the same points to the one question the curve answered:
|
||||
* is the value heading TOWARD the threshold, holding, or easing away?
|
||||
* Direction is relative to the threshold (a dropping salt level and a
|
||||
* rising runtime are both "approaching"). null = no arrow (no direction,
|
||||
* compound, or fewer than two points). */
|
||||
export function computeTrend(
|
||||
row: TaskRow | MaintenanceTask,
|
||||
miniStatsData: Map<string, StatisticsPoint[]>,
|
||||
): TrendState | null {
|
||||
const tc = row.trigger_config;
|
||||
if (!tc?.entity_id) return null;
|
||||
let toward: 1 | -1;
|
||||
const type = tc.type || "threshold";
|
||||
if (type === "threshold") {
|
||||
if (tc.trigger_above != null) toward = 1;
|
||||
else if (tc.trigger_below != null) toward = -1;
|
||||
else return null; // discrete = / \u2260 levels have no gradient
|
||||
} else if (type === "counter" || type === "runtime" || type === "state_change") {
|
||||
toward = 1; // these accumulate toward their target
|
||||
} else {
|
||||
return null; // compound: conditions may point both ways
|
||||
}
|
||||
const statsPoints = miniStatsData.get(tc.entity_id) || [];
|
||||
let points: { ts: number; val: number }[] =
|
||||
statsPoints.length >= 2
|
||||
? statsPoints.map((p) => ({ ts: p.ts, val: p.val }))
|
||||
: (row.history || [])
|
||||
.filter((h) => h.trigger_value != null)
|
||||
.map((h) => ({ ts: new Date(h.timestamp).getTime(), val: h.trigger_value as number }));
|
||||
if (row.trigger_current_value != null) points = [...points, { ts: Date.now(), val: row.trigger_current_value }];
|
||||
if (points.length < 2) return null;
|
||||
points.sort((a, b) => a.ts - b.ts);
|
||||
const vals = points.map((p) => p.val);
|
||||
const range = Math.max(...vals) - Math.min(...vals);
|
||||
const delta = vals[vals.length - 1] - vals[0];
|
||||
// Significance is judged against the JOURNEY to the threshold, not the
|
||||
// series' own noise band: a sensor crawling 50.0 -> 50.15 under a
|
||||
// 60-threshold is flat, even though that move spans most of its range.
|
||||
const target =
|
||||
type === "threshold" ? (tc.trigger_above ?? tc.trigger_below)
|
||||
: type === "counter" ? tc.trigger_target_value
|
||||
: type === "runtime" ? tc.trigger_runtime_hours
|
||||
: tc.trigger_target_changes;
|
||||
const base = typeof target === "number" ? Math.max(Math.abs(target - vals[0]), range) : range;
|
||||
if (base === 0 || Math.abs(delta) < base * (typeof target === "number" ? 0.05 : 0.15)) return "stable";
|
||||
return Math.sign(delta) === toward ? "approaching" : "easing";
|
||||
}
|
||||
|
||||
export function renderTriggerProgress(row: TaskRow | MaintenanceTask, opts?: { trend?: TrendState | null; lang?: string }) {
|
||||
const tc = row.trigger_config ?? null;
|
||||
if (!tc) return nothing;
|
||||
|
||||
@@ -109,7 +161,9 @@ export function renderTriggerProgress(row: TaskRow | MaintenanceTask) {
|
||||
<div class="trigger-progress-bar">
|
||||
<div class="trigger-progress-fill${triggerOverflow ? " overflow" : ""}" style="width:${pct}%;background:${barColor}"></div>
|
||||
</div>
|
||||
<span class="trigger-progress-label">${label}</span>
|
||||
<span class="trigger-progress-label">${label}${opts?.trend
|
||||
? html` <i class="trend-arrow trend-${opts.trend}" title="${t(`trend_${opts.trend}`, opts.lang ?? "en")}" aria-label="${t(`trend_${opts.trend}`, opts.lang ?? "en")}">${TREND_GLYPHS[opts.trend]}</i>`
|
||||
: nothing}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,9 @@ function renderTaskHeader(task: MaintenanceTask, ctx: TaskDetailContext) {
|
||||
</div>
|
||||
<div class="task-header-actions">
|
||||
<ha-button appearance="filled" @click=${() => ctx.openComplete(task)}>${t("complete", L)}</ha-button>
|
||||
<ha-button appearance="plain" .disabled=${ctx.actionLoading} @click=${() => ctx.promptSkip()}>${t("skip", L)}</ha-button>
|
||||
${task.allow_skip !== false
|
||||
? html`<ha-button appearance="outlined" .disabled=${ctx.actionLoading} @click=${() => ctx.promptSkip()}>${t("skip", L)}</ha-button>`
|
||||
: nothing}
|
||||
<div class="more-menu-wrapper">
|
||||
<ha-icon-button .disabled=${ctx.actionLoading} .path=${"M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"} @click=${() => ctx.toggleMoreMenu()}></ha-icon-button>
|
||||
${ctx.moreMenuOpen ? html`
|
||||
|
||||
@@ -908,8 +908,15 @@ export const sharedStyles = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
/* #150 follow-up (2026-08-31, maisun's mobile screenshot): the box used
|
||||
to carry min-width: 90px — in the narrow grid the due column is
|
||||
fit-content(100px) and right-aligned, so on cells narrower than 90px
|
||||
the box overhung LEFT across the object name. It now tracks the cell
|
||||
with no floor, and the label ellipsizes inside. */
|
||||
align-self: stretch;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
min-width: 90px;
|
||||
}
|
||||
|
||||
.trigger-progress-bar {
|
||||
@@ -930,8 +937,25 @@ export const sharedStyles = css`
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
text-align: right;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* #150 follow-up: 3-state trend arrow — replaces the sparkline on
|
||||
narrow/tight rows (panel-styles toggles visibility per regime). */
|
||||
.trend-arrow {
|
||||
display: none;
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
margin-left: 2px;
|
||||
}
|
||||
.trend-approaching { color: var(--warning-color, #ff9800); }
|
||||
.trend-stable { color: var(--secondary-text-color); }
|
||||
.trend-easing { color: var(--success-color, #4caf50); }
|
||||
|
||||
|
||||
/* Days progress bar (detail view) */
|
||||
.days-progress {
|
||||
margin: 8px 0 16px;
|
||||
@@ -974,6 +998,11 @@ export const sharedStyles = css`
|
||||
/* Mini-sparkline in overview rows */
|
||||
.mini-sparkline {
|
||||
width: 60px;
|
||||
/* Same class of bug as the trigger-progress floor (#150 follow-up): in
|
||||
due cells narrower than the fixed width the right-aligned SVG used to
|
||||
overhang LEFT (phone-360 sweep). preserveAspectRatio="none" lets it
|
||||
just squeeze. */
|
||||
max-width: 100%;
|
||||
height: 20px;
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
|
||||
@@ -68,6 +68,8 @@ export interface TriggerConfig {
|
||||
trigger_to_state?: string | null;
|
||||
trigger_target_changes?: number;
|
||||
trigger_runtime_hours?: number;
|
||||
/** #149: a single session books at most this many seconds. */
|
||||
trigger_runtime_max_session_seconds?: number;
|
||||
/** States (or attribute values) that count as "running" — default ["on"]. */
|
||||
trigger_on_states?: string[];
|
||||
compound_logic?: "AND" | "OR";
|
||||
@@ -173,6 +175,8 @@ export interface MaintenanceTask {
|
||||
name: string;
|
||||
type: string; // "cleaning" | "inspection" | "replacement" | "calibration" | "service" | "reading" | "custom"
|
||||
enabled: boolean;
|
||||
/** #150: false = the skip lock — Skip hidden everywhere, server refuses. */
|
||||
allow_skip?: boolean;
|
||||
schedule_type: string; // "time_based" | "sensor_based" | "one_time" | "manual" | calendar kind
|
||||
interval_days?: number | null;
|
||||
interval_unit?: string; // "days" | "weeks" | "months" | "years"
|
||||
@@ -509,6 +513,8 @@ export interface TaskRow {
|
||||
archived: boolean;
|
||||
history: HistoryEntry[];
|
||||
enabled: boolean;
|
||||
/** #150: false = skip locked for this task. */
|
||||
allow_skip: boolean;
|
||||
nfc_tag_id: string | null;
|
||||
priority: string;
|
||||
labels: string[];
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Podle oblasti",
|
||||
"groupby_group": "Podle skupiny",
|
||||
"groupby_user": "Podle uživatele",
|
||||
"groupby_object": "Podle objektu",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Uživatel",
|
||||
"photo_label": "Fotografie",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Aktuálně platná počáteční hodnota: {value}",
|
||||
"runtime_on_states": "Aktivní stavy",
|
||||
"runtime_on_states_help": "Stavy počítané jako doba běhu — výchozí: on. Např. mowing, cleaning, printing. Při zvoleném atributu se porovnávají jeho hodnoty.",
|
||||
"runtime_max_session": "Max. doba běhu relace (sekundy)",
|
||||
"runtime_max_session_help": "Jeden běh se počítá nejvýše na tolik sekund — chrání před senzorem zaseknutým na ZAP (ztráta spojení, restart). Prázdné = bez limitu.",
|
||||
"trend_approaching": "Blíží se k prahu",
|
||||
"trend_easing": "Vzdaluje se od prahu",
|
||||
"split_select_hint": "Vyberte úkol a zde se zobrazí podrobnosti",
|
||||
"setups_target_new": "Vytvořit nový: {name}",
|
||||
"schedule_preview_title": "Nejbližší termíny",
|
||||
"schedule_preview_ontime": "Za předpokladu včasného dokončení.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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'.",
|
||||
"disallow_skip": "Nepovolit přeskočení",
|
||||
"disallow_skip_help": "Akce Přeskočit zmizí ze všech ploch a server odmítne přeskočení i z automatizací a hlasu.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Efter område",
|
||||
"groupby_group": "Efter gruppe",
|
||||
"groupby_user": "Efter bruger",
|
||||
"groupby_object": "Efter objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruger",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Aktuelt gældende startværdi: {value}",
|
||||
"runtime_on_states": "Aktive tilstande",
|
||||
"runtime_on_states_help": "Tilstande der tæller som driftstid — standard: on. F.eks. mowing, cleaning, printing. Med en valgt attribut sammenlignes dens værdier i stedet.",
|
||||
"runtime_max_session": "Maks. sessionstid (sekunder)",
|
||||
"runtime_max_session_help": "En enkelt kørsel tæller højst så mange sekunder — beskytter mod en sensor, der hænger på TÆNDT (tabt forbindelse, genstart). Tom = ingen grænse.",
|
||||
"trend_approaching": "Nærmer sig tærsklen",
|
||||
"trend_easing": "Bevæger sig væk fra tærsklen",
|
||||
"split_select_hint": "Vælg en opgave for at se detaljerne her",
|
||||
"setups_target_new": "Opret ny: {name}",
|
||||
"schedule_preview_title": "Kommende datoer",
|
||||
"schedule_preview_ontime": "Forudsat rettidig fuldførelse.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Tillad ikke at springe over",
|
||||
"disallow_skip_help": "Handlingen Spring over forsvinder fra alle flader, og serveren afviser også spring fra automatiseringer og stemme.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Nach Bereich",
|
||||
"groupby_group": "Nach Gruppe",
|
||||
"groupby_user": "Nach Verantwortlichem",
|
||||
"groupby_object": "Nach Objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Benutzer",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Aktuell wirksamer Startwert: {value}",
|
||||
"runtime_on_states": "Aktive Zustände",
|
||||
"runtime_on_states_help": "Zustände, die als Laufzeit zählen — Standard: on. Z. B. mowing, cleaning, printing. Bei gewähltem Attribut werden dessen Werte verglichen.",
|
||||
"runtime_max_session": "Maximale Sitzungslaufzeit (Sekunden)",
|
||||
"runtime_max_session_help": "Ein einzelner Lauf zählt höchstens so viele Sekunden — schützt vor Sensoren, die auf AN hängen bleiben (Verbindungsabriss, Neustart). Leer = keine Begrenzung.",
|
||||
"trend_approaching": "Läuft auf die Schwelle zu",
|
||||
"trend_easing": "Entfernt sich von der Schwelle",
|
||||
"split_select_hint": "Wähle eine Aufgabe, um hier die Details zu sehen",
|
||||
"setups_target_new": "Neu anlegen: {name}",
|
||||
"schedule_preview_title": "Nächste Termine",
|
||||
"schedule_preview_ontime": "Bei pünktlichem Abschluss.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Überspringen nicht erlauben",
|
||||
"disallow_skip_help": "Die Aktion Überspringen verschwindet von allen Oberflächen; auch Automationen und Sprachbefehle werden abgewiesen.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "By area",
|
||||
"groupby_group": "By group",
|
||||
"groupby_user": "By user",
|
||||
"groupby_object": "By object",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "User",
|
||||
"photo_label": "Photo",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Currently effective start value: {value}",
|
||||
"runtime_on_states": "Active states",
|
||||
"runtime_on_states_help": "States that count as running — default: on. E.g. mowing, cleaning, printing. With an attribute selected, its values are matched instead.",
|
||||
"runtime_max_session": "Max session runtime (seconds)",
|
||||
"runtime_max_session_help": "A single run counts at most this many seconds — protects against a sensor stuck ON (lost connection, restart). Empty = no cap.",
|
||||
"trend_approaching": "Heading toward the threshold",
|
||||
"trend_easing": "Easing away from the threshold",
|
||||
"split_select_hint": "Select a task to see its details here",
|
||||
"setups_target_new": "Create new: {name}",
|
||||
"schedule_preview_title": "Next dates",
|
||||
"schedule_preview_ontime": "Assuming on-time completion.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Don't allow skipping",
|
||||
"disallow_skip_help": "The Skip action disappears from every surface, and the server refuses a skip from automations and voice too.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Por área",
|
||||
"groupby_group": "Por grupo",
|
||||
"groupby_user": "Por usuario",
|
||||
"groupby_object": "Por objeto",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuario",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valor inicial vigente: {value}",
|
||||
"runtime_on_states": "Estados activos",
|
||||
"runtime_on_states_help": "Estados que cuentan como en funcionamiento — predeterminado: on. P. ej. mowing, cleaning, printing. Con un atributo seleccionado se comparan sus valores.",
|
||||
"runtime_max_session": "Duración máxima por sesión (segundos)",
|
||||
"runtime_max_session_help": "Una sola ejecución cuenta como máximo estos segundos — protege contra un sensor atascado en ON (conexión perdida, reinicio). Vacío = sin límite.",
|
||||
"trend_approaching": "Se acerca al umbral",
|
||||
"trend_easing": "Se aleja del umbral",
|
||||
"split_select_hint": "Selecciona una tarea para ver sus detalles aquí",
|
||||
"setups_target_new": "Crear nuevo: {name}",
|
||||
"schedule_preview_title": "Próximas fechas",
|
||||
"schedule_preview_ontime": "Suponiendo finalización puntual.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "No permitir omitir",
|
||||
"disallow_skip_help": "La acción Omitir desaparece de todas las superficies y el servidor también rechaza omisiones desde automatizaciones y voz.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Alueen mukaan",
|
||||
"groupby_group": "Ryhmän mukaan",
|
||||
"groupby_user": "Käyttäjän mukaan",
|
||||
"groupby_object": "Kohteen mukaan",
|
||||
"filter_label": "Suodata",
|
||||
"user_label": "Käyttäjä",
|
||||
"photo_label": "Valokuva",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Nyt voimassa oleva aloitusarvo: {value}",
|
||||
"runtime_on_states": "Aktiiviset tilat",
|
||||
"runtime_on_states_help": "Tilat, jotka lasketaan käyntiajaksi — oletus: on. Esim. mowing, cleaning, printing. Jos attribuutti on valittu, verrataan sen arvoja.",
|
||||
"runtime_max_session": "Istunnon enimmäiskesto (sekuntia)",
|
||||
"runtime_max_session_help": "Yksi ajo lasketaan enintään näin moneksi sekunniksi — suojaa PÄÄLLÄ-tilaan jumittuneelta anturilta (katkennut yhteys, uudelleenkäynnistys). Tyhjä = ei rajaa.",
|
||||
"trend_approaching": "Lähestyy raja-arvoa",
|
||||
"trend_easing": "Loittonee raja-arvosta",
|
||||
"split_select_hint": "Valitse tehtävä nähdäksesi sen tiedot tässä",
|
||||
"setups_target_new": "Luo uusi: {name}",
|
||||
"schedule_preview_title": "Seuraavat päivämäärät",
|
||||
"schedule_preview_ontime": "Olettaen, että valmistuminen on ajallaan.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Estä ohittaminen",
|
||||
"disallow_skip_help": "Ohita-toiminto katoaa kaikista näkymistä, ja palvelin hylkää ohituksen myös automaatioista ja puheesta.",
|
||||
"require_tag_scan_hint": "Tämä tehtävä valmistuu vain skannaamalla sen NFC-tunniste tai QR-koodi itse laitteesta — tallennus tässä estetään."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Par zone",
|
||||
"groupby_group": "Par groupe",
|
||||
"groupby_user": "Par utilisateur",
|
||||
"groupby_object": "Par objet",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Utilisateur",
|
||||
"photo_label": "Photo",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valeur de départ actuellement en vigueur : {value}",
|
||||
"runtime_on_states": "États actifs",
|
||||
"runtime_on_states_help": "États comptés comme en fonctionnement — par défaut : on. P. ex. mowing, cleaning, printing. Si un attribut est sélectionné, ce sont ses valeurs qui sont comparées.",
|
||||
"runtime_max_session": "Durée max. par session (secondes)",
|
||||
"runtime_max_session_help": "Une seule exécution compte au plus ce nombre de secondes — protège contre un capteur bloqué sur ON (connexion perdue, redémarrage). Vide = pas de limite.",
|
||||
"trend_approaching": "Se rapproche du seuil",
|
||||
"trend_easing": "S'éloigne du seuil",
|
||||
"split_select_hint": "Sélectionnez une tâche pour voir ses détails ici",
|
||||
"setups_target_new": "Créer : {name}",
|
||||
"schedule_preview_title": "Prochaines échéances",
|
||||
"schedule_preview_ontime": "En supposant un achèvement ponctuel.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Ne pas autoriser à ignorer",
|
||||
"disallow_skip_help": "L'action Ignorer disparaît de toutes les surfaces et le serveur refuse aussi un saut via les automatisations et la voix.",
|
||||
"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é."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "क्षेत्र के अनुसार",
|
||||
"groupby_group": "समूह के अनुसार",
|
||||
"groupby_user": "उपयोगकर्ता के अनुसार",
|
||||
"groupby_object": "वस्तु के अनुसार",
|
||||
"filter_label": "फ़िल्टर",
|
||||
"user_label": "उपयोगकर्ता",
|
||||
"photo_label": "फ़ोटो",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "वर्तमान में प्रभावी प्रारंभिक मान: {value}",
|
||||
"runtime_on_states": "सक्रिय अवस्थाएँ",
|
||||
"runtime_on_states_help": "वे अवस्थाएँ जो चालू समय में गिनी जाती हैं — डिफ़ॉल्ट: on। जैसे mowing, cleaning, printing। विशेषता चुनी होने पर उसकी मानों की तुलना होती है।",
|
||||
"runtime_max_session": "प्रति सत्र अधिकतम रनटाइम (सेकंड)",
|
||||
"runtime_max_session_help": "एक रन अधिकतम इतने सेकंड गिना जाता है — ON पर अटके सेंसर (कनेक्शन टूटना, रीस्टार्ट) से बचाता है। खाली = कोई सीमा नहीं।",
|
||||
"trend_approaching": "सीमा की ओर बढ़ रहा है",
|
||||
"trend_easing": "सीमा से दूर जा रहा है",
|
||||
"split_select_hint": "विवरण यहाँ देखने के लिए कोई कार्य चुनें",
|
||||
"setups_target_new": "नया बनाएँ: {name}",
|
||||
"schedule_preview_title": "अगली तिथियाँ",
|
||||
"schedule_preview_ontime": "समय पर पूर्णता मानते हुए।",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "पिछली पूर्णता जोड़ें",
|
||||
"require_tag_scan": "केवल टैग स्कैन से पूर्ण करें",
|
||||
"require_tag_scan_help": "उपस्थिति प्रमाण: जब तक वस्तु पर लगा NFC टैग या QR कोड स्कैन न हो, हर जगह “पूर्ण” अवरुद्ध रहता है। ऑटोमेशन complete सेवा को 'via_tag_scan' दे सकते हैं।",
|
||||
"disallow_skip": "छोड़ने की अनुमति न दें",
|
||||
"disallow_skip_help": "छोड़ें क्रिया सभी सतहों से हट जाती है, और सर्वर ऑटोमेशन व वॉयस से छोड़ने को भी अस्वीकार करता है।",
|
||||
"require_tag_scan_hint": "यह कार्य केवल वस्तु पर लगे NFC टैग या QR कोड को स्कैन करके ही पूर्ण होता है — यहाँ सहेजना अस्वीकार होगा।"
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "Terület szerint",
|
||||
"groupby_group": "Csoport szerint",
|
||||
"groupby_user": "Felhasználó szerint",
|
||||
"groupby_object": "Objektum szerint",
|
||||
"filter_label": "Szűrő",
|
||||
"user_label": "Felhasználó",
|
||||
"photo_label": "Fénykép",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Jelenleg érvényes kezdőérték: {value}",
|
||||
"runtime_on_states": "Aktív állapotok",
|
||||
"runtime_on_states_help": "Működésként számító állapotok — alapértelmezés: on. Pl. mowing, cleaning, printing. Attribútum kiválasztásakor annak értékei számítanak.",
|
||||
"runtime_max_session": "Max. munkamenet-futásidő (másodperc)",
|
||||
"runtime_max_session_help": "Egy futás legfeljebb ennyi másodpercet számít — véd a BE állapotban beragadt érzékelő ellen (kapcsolatvesztés, újraindítás). Üres = nincs korlát.",
|
||||
"trend_approaching": "Közeledik a küszöbhöz",
|
||||
"trend_easing": "Távolodik a küszöbtől",
|
||||
"split_select_hint": "Válassz egy feladatot, és itt jelennek meg a részletei",
|
||||
"setups_target_new": "Új létrehozása: {name}",
|
||||
"schedule_preview_title": "Következő dátumok",
|
||||
"schedule_preview_ontime": "Időben történő elvégzést feltételezve.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Kihagyás tiltása",
|
||||
"disallow_skip_help": "A Kihagyás művelet minden felületről eltűnik, és a szerver az automatizálásokból és hangból érkező kihagyást is elutasítja.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Per area",
|
||||
"groupby_group": "Per gruppo",
|
||||
"groupby_user": "Per utente",
|
||||
"groupby_object": "Per oggetto",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Utente",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valore iniziale attualmente in vigore: {value}",
|
||||
"runtime_on_states": "Stati attivi",
|
||||
"runtime_on_states_help": "Stati che contano come in funzione — predefinito: on. Ad es. mowing, cleaning, printing. Con un attributo selezionato vengono confrontati i suoi valori.",
|
||||
"runtime_max_session": "Durata massima per sessione (secondi)",
|
||||
"runtime_max_session_help": "Una singola esecuzione conta al massimo questi secondi — protegge da un sensore bloccato su ON (connessione persa, riavvio). Vuoto = nessun limite.",
|
||||
"trend_approaching": "Si avvicina alla soglia",
|
||||
"trend_easing": "Si allontana dalla soglia",
|
||||
"split_select_hint": "Seleziona un'attività per vederne i dettagli qui",
|
||||
"setups_target_new": "Crea nuovo: {name}",
|
||||
"schedule_preview_title": "Prossime date",
|
||||
"schedule_preview_ontime": "Presumendo un completamento puntuale.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Non consentire di saltare",
|
||||
"disallow_skip_help": "L'azione Salta scompare da tutte le superfici e il server rifiuta anche i salti da automazioni e voce.",
|
||||
"require_tag_scan_hint": "Questa attività si completa solo scansionando il suo tag NFC o codice QR sull'oggetto — il salvataggio qui verrà rifiutato."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "エリア別",
|
||||
"groupby_group": "グループ別",
|
||||
"groupby_user": "ユーザー別",
|
||||
"groupby_object": "対象別",
|
||||
"filter_label": "絞り込み",
|
||||
"user_label": "ユーザー",
|
||||
"photo_label": "写真",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "現在有効な開始値:{value}",
|
||||
"runtime_on_states": "稼働中とみなす状態",
|
||||
"runtime_on_states_help": "稼働時間として数える状態 — 既定値:on。例:mowing, cleaning, printing。属性を選択している場合はその値が比較されます。",
|
||||
"runtime_max_session": "1回の最大稼働時間(秒)",
|
||||
"runtime_max_session_help": "1回の稼働は最大この秒数までカウント——ONのまま固まったセンサー(接続切れ・再起動)への保護です。空欄=制限なし。",
|
||||
"trend_approaching": "しきい値に近づいています",
|
||||
"trend_easing": "しきい値から遠ざかっています",
|
||||
"split_select_hint": "タスクを選択すると、ここに詳細が表示されます",
|
||||
"setups_target_new": "新規作成:{name}",
|
||||
"schedule_preview_title": "次回の予定日",
|
||||
"schedule_preview_ontime": "期日どおりに完了した場合。",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "過去の完了を追加",
|
||||
"require_tag_scan": "タグのスキャンでのみ完了可能",
|
||||
"require_tag_scan_help": "在席証明:対象物のNFCタグまたはQRコードをスキャンするまで、どの画面でも「完了」はブロックされます。オートメーションはcompleteサービスに 'via_tag_scan' を渡せます。",
|
||||
"disallow_skip": "スキップを許可しない",
|
||||
"disallow_skip_help": "スキップ操作はすべての画面から消え、オートメーションや音声からのスキップもサーバーが拒否します。",
|
||||
"require_tag_scan_hint": "このタスクは対象物のNFCタグまたはQRコードをスキャンしないと完了できません。ここでの保存は拒否されます。"
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "구역별",
|
||||
"groupby_group": "그룹별",
|
||||
"groupby_user": "사용자별",
|
||||
"groupby_object": "대상별",
|
||||
"filter_label": "필터",
|
||||
"user_label": "사용자",
|
||||
"photo_label": "사진",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "현재 적용 중인 시작 값: {value}",
|
||||
"runtime_on_states": "활성 상태",
|
||||
"runtime_on_states_help": "가동 중으로 계산할 상태입니다 — 기본값: on. 예: mowing, cleaning, printing. 속성을 선택한 경우 해당 속성 값과 비교합니다.",
|
||||
"runtime_max_session": "세션당 최대 가동 시간(초)",
|
||||
"runtime_max_session_help": "한 번의 가동은 최대 이 초까지만 집계됩니다 — ON에 멈춘 센서(연결 끊김, 재시작)로부터 보호합니다. 비워두면 제한 없음.",
|
||||
"trend_approaching": "임계값에 가까워지는 중",
|
||||
"trend_easing": "임계값에서 멀어지는 중",
|
||||
"split_select_hint": "작업을 선택하면 여기에 세부 정보가 표시됩니다",
|
||||
"setups_target_new": "새로 만들기: {name}",
|
||||
"schedule_preview_title": "다음 예정일",
|
||||
"schedule_preview_ontime": "제때 완료한다고 가정한 일정입니다.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "지난 완료 추가",
|
||||
"require_tag_scan": "태그 스캔으로만 완료 가능",
|
||||
"require_tag_scan_help": "현장 확인: 기기의 NFC 태그나 QR 코드를 스캔할 때까지 모든 화면에서 '완료'가 차단됩니다. 자동화는 complete 서비스에 'via_tag_scan'을 전달할 수 있습니다.",
|
||||
"disallow_skip": "건너뛰기 허용 안 함",
|
||||
"disallow_skip_help": "건너뛰기 동작이 모든 화면에서 사라지며, 서버는 자동화와 음성의 건너뛰기도 거부합니다.",
|
||||
"require_tag_scan_hint": "이 작업은 기기의 NFC 태그나 QR 코드를 스캔해야만 완료됩니다. 여기서 저장하면 거부됩니다."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "Etter område",
|
||||
"groupby_group": "Etter gruppe",
|
||||
"groupby_user": "Etter bruker",
|
||||
"groupby_object": "Etter objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Bruker",
|
||||
"photo_label": "Bilde",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Gjeldende startverdi: {value}",
|
||||
"runtime_on_states": "Aktive tilstander",
|
||||
"runtime_on_states_help": "Tilstander som teller som driftstid — standard: on. F.eks. mowing, cleaning, printing. Med et valgt attributt sammenlignes verdiene dets i stedet.",
|
||||
"runtime_max_session": "Maks økt-varighet (sekunder)",
|
||||
"runtime_max_session_help": "En enkelt kjøring teller høyst så mange sekunder — beskytter mot en sensor som henger på PÅ (mistet forbindelse, omstart). Tom = ingen grense.",
|
||||
"trend_approaching": "Nærmer seg terskelen",
|
||||
"trend_easing": "Beveger seg bort fra terskelen",
|
||||
"split_select_hint": "Velg en oppgave for å se detaljene her",
|
||||
"setups_target_new": "Opprett ny: {name}",
|
||||
"schedule_preview_title": "Kommende datoer",
|
||||
"schedule_preview_ontime": "Forutsatt fullføring i tide.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Ikke tillat å hoppe over",
|
||||
"disallow_skip_help": "Handlingen Hopp over forsvinner fra alle flater, og serveren avviser også hopp fra automatiseringer og tale.",
|
||||
"require_tag_scan_hint": "Denne oppgaven fullføres bare ved å skanne NFC-taggen eller QR-koden på selve tingen — lagring her vil bli avvist."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Per gebied",
|
||||
"groupby_group": "Per groep",
|
||||
"groupby_user": "Per gebruiker",
|
||||
"groupby_object": "Per object",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Gebruiker",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Momenteel geldende startwaarde: {value}",
|
||||
"runtime_on_states": "Actieve statussen",
|
||||
"runtime_on_states_help": "Statussen die als draaitijd tellen — standaard: on. Bijv. mowing, cleaning, printing. Met een geselecteerd attribuut worden de waarden daarvan vergeleken.",
|
||||
"runtime_max_session": "Maximale sessieduur (seconden)",
|
||||
"runtime_max_session_help": "Eén run telt maximaal dit aantal seconden — beschermt tegen sensoren die op AAN blijven hangen (verbroken verbinding, herstart). Leeg = geen limiet.",
|
||||
"trend_approaching": "Beweegt richting de drempel",
|
||||
"trend_easing": "Beweegt weg van de drempel",
|
||||
"split_select_hint": "Selecteer een taak om hier de details te zien",
|
||||
"setups_target_new": "Nieuw aanmaken: {name}",
|
||||
"schedule_preview_title": "Volgende data",
|
||||
"schedule_preview_ontime": "Uitgaande van tijdige afronding.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Overslaan niet toestaan",
|
||||
"disallow_skip_help": "De actie Overslaan verdwijnt overal, en de server weigert ook een skip via automatiseringen en spraak.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Wg obszaru",
|
||||
"groupby_group": "Wg grupy",
|
||||
"groupby_user": "Wg użytkownika",
|
||||
"groupby_object": "Według obiektu",
|
||||
"filter_label": "Filtr",
|
||||
"user_label": "Użytkownik",
|
||||
"photo_label": "Zdjęcie",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Obecnie obowiązująca wartość początkowa: {value}",
|
||||
"runtime_on_states": "Stany aktywne",
|
||||
"runtime_on_states_help": "Stany liczone jako czas pracy — domyślnie: on. Np. mowing, cleaning, printing. Przy wybranym atrybucie porównywane są jego wartości.",
|
||||
"runtime_max_session": "Maks. czas sesji (sekundy)",
|
||||
"runtime_max_session_help": "Pojedynczy przebieg liczy się najwyżej tyle sekund — chroni przed czujnikiem zawieszonym na WŁ. (utrata połączenia, restart). Puste = bez limitu.",
|
||||
"trend_approaching": "Zbliża się do progu",
|
||||
"trend_easing": "Oddala się od progu",
|
||||
"split_select_hint": "Wybierz zadanie, aby zobaczyć tutaj szczegóły",
|
||||
"setups_target_new": "Utwórz nowy: {name}",
|
||||
"schedule_preview_title": "Najbliższe terminy",
|
||||
"schedule_preview_ontime": "Przy założeniu terminowego ukończenia.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Nie zezwalaj na pomijanie",
|
||||
"disallow_skip_help": "Akcja Pomiń znika ze wszystkich widoków, a serwer odrzuca pominięcie także z automatyzacji i głosu.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "Por área",
|
||||
"groupby_group": "Por grupo",
|
||||
"groupby_user": "Por usuário",
|
||||
"groupby_object": "Por objeto",
|
||||
"filter_label": "Filtro",
|
||||
"user_label": "Usuário",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valor inicial efetivo atual: {value}",
|
||||
"runtime_on_states": "Estados ativos",
|
||||
"runtime_on_states_help": "Estados que contam como em funcionamento — padrão: on. Ex.: mowing, cleaning, printing. Com um atributo selecionado, os valores dele são comparados.",
|
||||
"runtime_max_session": "Duração máxima por sessão (segundos)",
|
||||
"runtime_max_session_help": "Uma única execução conta no máximo esses segundos — protege contra um sensor travado em ON (conexão perdida, reinício). Vazio = sem limite.",
|
||||
"trend_approaching": "Aproximando-se do limite",
|
||||
"trend_easing": "Afastando-se do limite",
|
||||
"split_select_hint": "Selecione uma tarefa para ver os detalhes aqui",
|
||||
"setups_target_new": "Criar novo: {name}",
|
||||
"schedule_preview_title": "Próximas datas",
|
||||
"schedule_preview_ontime": "Supondo conclusão em dia.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Não permitir pular",
|
||||
"disallow_skip_help": "A ação Pular desaparece de todas as telas e o servidor também recusa pular via automações e voz.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -573,6 +573,7 @@
|
||||
"groupby_area": "Por área",
|
||||
"groupby_group": "Por grupo",
|
||||
"groupby_user": "Por usuário",
|
||||
"groupby_object": "Por objeto",
|
||||
"unassigned": "Não atribuído",
|
||||
"no_area": "Sem área",
|
||||
"has_overdue": "Tarefas em atraso",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Valor inicial atualmente em vigor: {value}",
|
||||
"runtime_on_states": "Estados ativos",
|
||||
"runtime_on_states_help": "Estados que contam como em funcionamento — padrão: on. P. ex. mowing, cleaning, printing. Com um atributo selecionado, são comparados os valores dele.",
|
||||
"runtime_max_session": "Duração máxima por sessão (segundos)",
|
||||
"runtime_max_session_help": "Uma única execução conta no máximo estes segundos — protege contra um sensor preso em ON (ligação perdida, reinício). Vazio = sem limite.",
|
||||
"trend_approaching": "A aproximar-se do limiar",
|
||||
"trend_easing": "A afastar-se do limiar",
|
||||
"split_select_hint": "Selecione uma tarefa para ver os detalhes aqui",
|
||||
"setups_target_new": "Criar novo: {name}",
|
||||
"schedule_preview_title": "Próximas datas",
|
||||
"schedule_preview_ontime": "Assumindo conclusão pontual.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Não permitir ignorar",
|
||||
"disallow_skip_help": "A ação Ignorar desaparece de todas as superfícies e o servidor também recusa ignorar via automações e voz.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "По области",
|
||||
"groupby_group": "По группе",
|
||||
"groupby_user": "По пользователю",
|
||||
"groupby_object": "По объекту",
|
||||
"filter_label": "Фильтр",
|
||||
"user_label": "Пользователь",
|
||||
"photo_label": "Фото",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Действующее начальное значение: {value}",
|
||||
"runtime_on_states": "Активные состояния",
|
||||
"runtime_on_states_help": "Состояния, засчитываемые как работа — по умолчанию: on. Напр. mowing, cleaning, printing. Если выбран атрибут, сравниваются его значения.",
|
||||
"runtime_max_session": "Макс. время сеанса (секунды)",
|
||||
"runtime_max_session_help": "Один запуск засчитывается не более чем на столько секунд — защита от датчика, зависшего во включённом состоянии (обрыв связи, перезапуск). Пусто = без ограничения.",
|
||||
"trend_approaching": "Приближается к порогу",
|
||||
"trend_easing": "Отдаляется от порога",
|
||||
"split_select_hint": "Выберите задачу, чтобы увидеть здесь её детали",
|
||||
"setups_target_new": "Создать новый: {name}",
|
||||
"schedule_preview_title": "Ближайшие даты",
|
||||
"schedule_preview_ontime": "При условии своевременного выполнения.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "Добавить прошлое выполнение",
|
||||
"require_tag_scan": "Завершение только по скану метки",
|
||||
"require_tag_scan_help": "Подтверждение присутствия: «Готово» заблокировано везде, пока не отсканирована NFC-метка или QR-код на самом устройстве. Автоматизации могут передавать 'via_tag_scan' в службу complete.",
|
||||
"disallow_skip": "Запретить пропуск",
|
||||
"disallow_skip_help": "Действие «Пропустить» исчезает со всех экранов, а сервер отклоняет пропуск из автоматизаций и голосовых команд.",
|
||||
"require_tag_scan_hint": "Эта задача завершается только сканированием её NFC-метки или QR-кода на самом устройстве — сохранение здесь будет отклонено."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "Per område",
|
||||
"groupby_group": "Per grupp",
|
||||
"groupby_user": "Per användare",
|
||||
"groupby_object": "Efter objekt",
|
||||
"filter_label": "Filter",
|
||||
"user_label": "Användare",
|
||||
"photo_label": "Foto",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Nu gällande startvärde: {value}",
|
||||
"runtime_on_states": "Aktiva tillstånd",
|
||||
"runtime_on_states_help": "Tillstånd som räknas som drifttid — standard: on. T.ex. mowing, cleaning, printing. Med ett valt attribut jämförs dess värden i stället.",
|
||||
"runtime_max_session": "Max körtid per session (sekunder)",
|
||||
"runtime_max_session_help": "En enskild körning räknas som högst så här många sekunder — skyddar mot en sensor som fastnat på PÅ (tappad anslutning, omstart). Tomt = ingen gräns.",
|
||||
"trend_approaching": "Närmar sig tröskeln",
|
||||
"trend_easing": "Rör sig bort från tröskeln",
|
||||
"split_select_hint": "Välj en uppgift för att se detaljerna här",
|
||||
"setups_target_new": "Skapa ny: {name}",
|
||||
"schedule_preview_title": "Kommande datum",
|
||||
"schedule_preview_ontime": "Förutsatt slutförande i tid.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Tillåt inte att hoppa över",
|
||||
"disallow_skip_help": "Åtgärden Hoppa över försvinner från alla ytor och servern nekar även hopp från automationer och röst.",
|
||||
"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."
|
||||
}
|
||||
|
||||
@@ -536,6 +536,7 @@
|
||||
"groupby_area": "Alana göre",
|
||||
"groupby_group": "Gruba göre",
|
||||
"groupby_user": "Kullanıcıya göre",
|
||||
"groupby_object": "Nesneye göre",
|
||||
"filter_label": "Filtre",
|
||||
"user_label": "Kullanıcı",
|
||||
"photo_label": "Fotoğraf",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Şu anda geçerli başlangıç değeri: {value}",
|
||||
"runtime_on_states": "Etkin durumlar",
|
||||
"runtime_on_states_help": "Çalışıyor sayılan durumlar — varsayılan: on. Örn. mowing, cleaning, printing. Bir öznitelik seçiliyse onun değerleri eşleştirilir.",
|
||||
"runtime_max_session": "Oturum başına azami çalışma süresi (saniye)",
|
||||
"runtime_max_session_help": "Tek bir çalışma en fazla bu kadar saniye sayılır — AÇIK durumda takılı kalan sensöre (bağlantı kopması, yeniden başlatma) karşı korur. Boş = sınır yok.",
|
||||
"trend_approaching": "Eşiğe yaklaşıyor",
|
||||
"trend_easing": "Eşikten uzaklaşıyor",
|
||||
"split_select_hint": "Ayrıntıları burada görmek için bir görev seçin",
|
||||
"setups_target_new": "Yeni oluştur: {name}",
|
||||
"schedule_preview_title": "Sonraki tarihler",
|
||||
"schedule_preview_ontime": "Zamanında tamamlama varsayılarak.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"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.",
|
||||
"disallow_skip": "Atlamaya izin verme",
|
||||
"disallow_skip_help": "Atla eylemi tüm yüzeylerden kaybolur; sunucu otomasyonlardan ve sesten gelen atlamayı da reddeder.",
|
||||
"require_tag_scan_hint": "Bu görev yalnızca cihazın üzerindeki NFC etiketi veya QR kodu taranarak tamamlanır — buradan kaydetme reddedilecektir."
|
||||
}
|
||||
|
||||
@@ -528,6 +528,7 @@
|
||||
"groupby_area": "За зоною",
|
||||
"groupby_group": "За групою",
|
||||
"groupby_user": "За користувачем",
|
||||
"groupby_object": "За об'єктом",
|
||||
"filter_label": "Фільтр",
|
||||
"user_label": "Користувач",
|
||||
"photo_label": "Фото",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "Чинне початкове значення: {value}",
|
||||
"runtime_on_states": "Активні стани",
|
||||
"runtime_on_states_help": "Стани, що зараховуються як робота — типово: on. Напр. mowing, cleaning, printing. Якщо вибрано атрибут, порівнюються його значення.",
|
||||
"runtime_max_session": "Макс. час сеансу (секунди)",
|
||||
"runtime_max_session_help": "Один запуск зараховується щонайбільше на стільки секунд — захист від датчика, що завис в увімкненому стані (втрата зв'язку, перезапуск). Порожньо = без обмеження.",
|
||||
"trend_approaching": "Наближається до порога",
|
||||
"trend_easing": "Віддаляється від порога",
|
||||
"split_select_hint": "Виберіть завдання, щоб побачити тут його деталі",
|
||||
"setups_target_new": "Створити новий: {name}",
|
||||
"schedule_preview_title": "Найближчі дати",
|
||||
"schedule_preview_ontime": "За умови вчасного виконання.",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "Додати минуле виконання",
|
||||
"require_tag_scan": "Завершення лише скануванням мітки",
|
||||
"require_tag_scan_help": "Підтвердження присутності: «Готово» заблоковано всюди, доки не відскановано NFC-мітку або QR-код на самому пристрої. Автоматизації можуть передати 'via_tag_scan' службі complete.",
|
||||
"disallow_skip": "Заборонити пропуск",
|
||||
"disallow_skip_help": "Дія «Пропустити» зникає з усіх поверхонь, а сервер відхиляє пропуск з автоматизацій і голосу.",
|
||||
"require_tag_scan_hint": "Це завдання завершується лише скануванням його NFC-мітки або QR-коду на самому пристрої — збереження тут буде відхилено."
|
||||
}
|
||||
|
||||
@@ -530,6 +530,7 @@
|
||||
"groupby_area": "按区域分组",
|
||||
"groupby_group": "按分组",
|
||||
"groupby_user": "按负责人",
|
||||
"groupby_object": "按对象",
|
||||
"filter_label": "过滤器",
|
||||
"user_label": "用户",
|
||||
"photo_label": "照片",
|
||||
@@ -834,6 +835,11 @@
|
||||
"baseline_current_effective": "当前生效的起始读数:{value}",
|
||||
"runtime_on_states": "活动状态",
|
||||
"runtime_on_states_help": "计入运行时间的状态 — 默认:on。例如 mowing、cleaning、printing。选择了属性时将比较该属性的值。",
|
||||
"runtime_max_session": "单次运行最长时间(秒)",
|
||||
"runtime_max_session_help": "单次运行最多计入这么多秒——防止传感器卡在开启状态(连接丢失、重启)。留空 = 不限制。",
|
||||
"trend_approaching": "正接近阈值",
|
||||
"trend_easing": "正远离阈值",
|
||||
"split_select_hint": "选择一个任务,在此查看详情",
|
||||
"setups_target_new": "新建:{name}",
|
||||
"schedule_preview_title": "下次日期",
|
||||
"schedule_preview_ontime": "假设按时完成。",
|
||||
@@ -927,5 +933,7 @@
|
||||
"history_add_past": "补录过去的完成记录",
|
||||
"require_tag_scan": "仅可通过扫描标签完成",
|
||||
"require_tag_scan_help": "在场证明:在扫描设备上的 NFC 标签或二维码之前,所有界面的“完成”均被阻止。自动化可向 complete 服务传递 'via_tag_scan'。",
|
||||
"disallow_skip": "不允许跳过",
|
||||
"disallow_skip_help": "跳过操作将从所有界面消失,服务器也会拒绝来自自动化和语音的跳过。",
|
||||
"require_tag_scan_hint": "此任务只能通过扫描设备上的 NFC 标签或二维码来完成——在此保存将被拒绝。"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
var S="2.70.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.71.0 */
|
||||
var S="2.71.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{}})();
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
import{a as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-MEZOPQBI.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PPH3NCFT.js";import{a as o,b as _,c as i,f as l,h,l as g,m as n,q as t,s as u,u as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YEY3J44W.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i`
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
import{a as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-JSPW4Q6Q.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GJI3RTUI.js";import{a as o,b as _,c as i,f as l,h,l as g,m as n,q as t,s as u,u as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PBRIQQ2X.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i`
|
||||
<div class="overlay" @click=${this._close}>
|
||||
<div class="card" @click=${e=>e.stopPropagation()}>
|
||||
<div class="title">${t("adopt_problem_title",s)}</div>
|
||||
-250
@@ -1,250 +0,0 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
import{a as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-J4OBOTUE.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KKI2ZPWC.js";import{a as o,b as _,c as i,f as l,h,l as g,m as n,q as t,s as u,u as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VSV22GFT.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i`
|
||||
<div class="overlay" @click=${this._close}>
|
||||
<div class="card" @click=${e=>e.stopPropagation()}>
|
||||
<div class="title">${t("adopt_problem_title",s)}</div>
|
||||
<div class="hint">${t("adopt_problem_hint",s)}</div>
|
||||
${this._error?i`<div class="error">${this._error}</div>`:l}
|
||||
|
||||
${this._loading?i`<div class="loading">…</div>`:this._sensors.length===0?i`<div class="empty">${t("adopt_problem_none",s)}</div>`:i`
|
||||
<label class="select-all">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${a}
|
||||
@change=${this._toggleAll}
|
||||
/>
|
||||
<span>${t("selected",s)}: ${this._selected.size} / ${this._sensors.length}</span>
|
||||
</label>
|
||||
<div class="list">
|
||||
${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),d=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
|
||||
<label class="row">
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${m}
|
||||
@change=${()=>this._toggle(e.entity_id)}
|
||||
/>
|
||||
<div class="row-main">
|
||||
<div class="row-top">
|
||||
<span class="row-name">${e.name}</span>
|
||||
<span class="chip ${d?"chip-active":"chip-ok"}">
|
||||
${d?t("adopt_problem_active",s):t("adopt_problem_ok",s)}
|
||||
</span>
|
||||
</div>
|
||||
${c?i`<div class="row-sub">${c}</div>`:l}
|
||||
<div class="row-target">
|
||||
→ ${e.suggested_object_name}${e.suggested_entry_id?l:i` <span class="new-tag">${t("adopt_problem_new_object",s)}</span>`}
|
||||
</div>
|
||||
${e.suggested_part_name?i`<div class="row-part">
|
||||
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
|
||||
${t("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
|
||||
</div>`:l}
|
||||
</div>
|
||||
</label>
|
||||
`})}
|
||||
</div>
|
||||
`}
|
||||
|
||||
${!this._loading&&this._sensors.length>0?i`
|
||||
<label class="responsible">
|
||||
<span>${t("for_at_least_minutes",s)}</span>
|
||||
<input
|
||||
class="for-input"
|
||||
type="number"
|
||||
min="0"
|
||||
max="1440"
|
||||
.value=${this._forMinutes}
|
||||
@input=${e=>this._forMinutes=e.target.value}
|
||||
/>
|
||||
</label>
|
||||
<div class="for-hint">${t("adopt_for_minutes_hint",s)}</div>
|
||||
`:l}
|
||||
|
||||
${!this._loading&&this._sensors.length>0&&this._users.length>0?i`
|
||||
<label class="responsible">
|
||||
<span>${t("adopt_problem_responsible",s)}</span>
|
||||
<select
|
||||
.value=${this._responsible}
|
||||
@change=${e=>{this._responsible=e.target.value}}
|
||||
>
|
||||
<option value="" ?selected=${!this._responsible}>${t("no_user_assigned",s)}</option>
|
||||
${this._users.map(e=>i`<option value=${e.id} ?selected=${e.id===this._responsible}>${e.name}</option>`)}
|
||||
</select>
|
||||
</label>
|
||||
`:l}
|
||||
|
||||
<div class="actions">
|
||||
<ha-button appearance="plain" @click=${this._close}>
|
||||
${t("cancel",s)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
@click=${this._adopt}
|
||||
.disabled=${this._selected.size===0||this._adopting}
|
||||
>
|
||||
${t("adopt_problem_adopt",s)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`}};r.styles=_`
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-background-color, #fff);
|
||||
color: var(--primary-text-color);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
min-width: min(360px, calc(100vw - 24px));
|
||||
max-width: 560px;
|
||||
width: 90vw;
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.hint {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color, #f44336);
|
||||
font-size: 13px;
|
||||
}
|
||||
.loading,
|
||||
.empty {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 14px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.select-all {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
.select-all input {
|
||||
cursor: pointer;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
overflow-y: auto;
|
||||
max-height: 50vh;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.row input {
|
||||
margin-top: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.row-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.row-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.row-name {
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
}
|
||||
.row-sub {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
.row-target {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
}
|
||||
.row-part {
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
.row-part ha-icon {
|
||||
--mdc-icon-size: 14px;
|
||||
}
|
||||
.new-tag {
|
||||
font-style: italic;
|
||||
}
|
||||
.chip {
|
||||
font-size: 11px;
|
||||
padding: 1px 8px;
|
||||
border-radius: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chip-active {
|
||||
background: var(--error-color, #f44336);
|
||||
color: #fff;
|
||||
}
|
||||
.chip-ok {
|
||||
background: var(--divider-color);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
.responsible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.for-input {
|
||||
width: 70px;
|
||||
background: var(--card-background-color);
|
||||
color: var(--primary-text-color);
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
}
|
||||
.for-hint {
|
||||
font-size: 11px;
|
||||
color: var(--secondary-text-color);
|
||||
margin: -4px 0 2px;
|
||||
}
|
||||
.responsible select {
|
||||
flex: 1;
|
||||
min-width: 140px;
|
||||
padding: 4px 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--divider-color);
|
||||
background: var(--card-background-color, #fff);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
`,o([g({attribute:!1})],r.prototype,"hass",2),o([n()],r.prototype,"_open",2),o([n()],r.prototype,"_loading",2),o([n()],r.prototype,"_adopting",2),o([n()],r.prototype,"_error",2),o([n()],r.prototype,"_sensors",2),o([n()],r.prototype,"_selected",2),o([n()],r.prototype,"_users",2),o([n()],r.prototype,"_responsible",2),o([n()],r.prototype,"_forMinutes",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
|
||||
@@ -1,2 +0,0 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
function c(o,t,e){let l=new Blob([o],{type:e}),d=URL.createObjectURL(l),n=document.createElement("a");n.href=d,n.download=t,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(d),6e4)}function r(o,t){let e=document.createElement("a");e.href=o,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{c as a,r as b};
|
||||
+34
-17
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
import{a as r,b as l,c as i,f as o,h as n,l as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YEY3J44W.js";var e=class extends n{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(s){let a=s.target.value;this.value=a,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:a}}))}render(){return i`
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
import{a as r,b as l,c as i,f as o,h as n,l as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PBRIQQ2X.js";var e=class extends n{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(s){let a=s.target.value;this.value=a,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:a}}))}render(){return i`
|
||||
<label class="field">
|
||||
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:o}</span>`:o}
|
||||
${this.multiline?i`
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
function c(o,t,e){let l=new Blob([o],{type:e}),d=URL.createObjectURL(l),n=document.createElement("a");n.href=d,n.download=t,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(d),6e4)}function r(o,t){let e=document.createElement("a");e.href=o,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{c as a,r as b};
|
||||
@@ -1,2 +0,0 @@
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
import{q as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YEY3J44W.js";function u(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let n=!!e.entry_id&&e.entry_id!==r,o=n?e.entry_id:r,a=s.find(p=>p.entry_id===o),t=(a?.parts||[]).find(p=>p.id===e.part_id)||null,d=n&&a?.object?.name||"",i=t?.name||_("shared_part_unknown",c);return{part:t,foreign:n,ownerName:d,label:d?`${i} (${d})`:i}}function P(e,r,s,c){let{part:n,label:o}=l(e,r,s,c),a=n&&n.stock!==null&&n.stock!==void 0?` (${n.stock}${n.unit?" "+n.unit:""})`:"",t=n?.storage_location?` \u2014 ${n.storage_location}`:"";return`${e.quantity}\xD7 ${o}${a}${t}`}function g(e,r,s,c){let o=(s.find(t=>t.entry_id===r)?.parts||[]).map(t=>({...t})),a=new Set(o.map(t=>u({part_id:t.id})));for(let t of e?.consumes_parts||[]){if(!t.entry_id||t.entry_id===r)continue;let d=u(t);if(a.has(d))continue;a.add(d);let{part:i,ownerName:p}=l(t,r,s,c);o.push({id:t.part_id,name:i?.name||_("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:t.entry_id,owner_name:p})}return o}var f=["notes","cost","duration","photo","user"],m={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{u as a,P as b,g as c,f as d,m as e};
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
import{q as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VSV22GFT.js";var s={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"};function c(e,o){let r=s[e];if(!r)return e;let t=a(r,o);return t&&t!==r?t:e}function d(e){let r=e.match(/data\['([^']+)'\]/)?.[1],t;return(t=e.match(/length of value must be at most (\d+)/))?{field:r,rule:"too_long",param:t[1]}:(t=e.match(/length of value must be at least (\d+)/))?{field:r,rule:"too_short",param:t[1]}:(t=e.match(/value must be at most (\S+)/))?{field:r,rule:"value_too_high",param:t[1]}:(t=e.match(/value must be at least (\S+)/))?{field:r,rule:"value_too_low",param:t[1]}:/required key not provided/.test(e)?{field:r,rule:"required"}:(t=e.match(/expected (\w+)/))?{field:r,rule:"wrong_type",param:t[1]}:/value must be one of/.test(e)?{field:r,rule:"invalid_choice"}:/not a valid value/.test(e)?{field:r,rule:"invalid_value"}:{field:r,rule:"unknown"}}function g(e,o,r){if(r=r??a("action_error",o),typeof e=="string")return e;if(typeof e!="object"||e===null)return r;let t=e,_=t.message||t.error?.message||"";if(!_)return r;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||r}}export{g as a};
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
import{q as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PBRIQQ2X.js";var s={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"};function c(e,o){let r=s[e];if(!r)return e;let t=a(r,o);return t&&t!==r?t:e}function d(e){let r=e.match(/data\['([^']+)'\]/)?.[1],t;return(t=e.match(/length of value must be at most (\d+)/))?{field:r,rule:"too_long",param:t[1]}:(t=e.match(/length of value must be at least (\d+)/))?{field:r,rule:"too_short",param:t[1]}:(t=e.match(/value must be at most (\S+)/))?{field:r,rule:"value_too_high",param:t[1]}:(t=e.match(/value must be at least (\S+)/))?{field:r,rule:"value_too_low",param:t[1]}:/required key not provided/.test(e)?{field:r,rule:"required"}:(t=e.match(/expected (\w+)/))?{field:r,rule:"wrong_type",param:t[1]}:/value must be one of/.test(e)?{field:r,rule:"invalid_choice"}:/not a valid value/.test(e)?{field:r,rule:"invalid_value"}:{field:r,rule:"unknown"}}function g(e,o,r){if(r=r??a("action_error",o),typeof e=="string")return e;if(typeof e!="object"||e===null)return r;let t=e,_=t.message||t.error?.message||"";if(!_)return r;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||r}}export{g as a};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
import{a as _,e as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-CPVQNQQQ.js";import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PPH3NCFT.js";import{C as g,a as s,b,c as o,f as d,h as v,l as n,m as l,q as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YEY3J44W.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this.requireTagScan=!1;this.viaTagScan=!1;this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(e={}){this._open||(this._open=!0,this.viaTagScan=!!e.viaTagScan,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((t,a)=>[String(a),!!this.checklistPrefill[t]]).filter(([,t])=>t)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(t=>[_(t),{...t}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,a=t.files?.[0];if(t.value="",!!a){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",a,a.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(a))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this.viaTagScan&&(e.via_tag_scan=!0),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=r("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=m(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?o`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let a=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(a)||a<=0?null:Math.round(this.restockUnitCost*a*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let a of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(a));c?.cost!=null&&(e+=c.cost*(a.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let a=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return o`<button
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
import{a as _,e as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WQQNFFGB.js";import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GJI3RTUI.js";import{C as g,a as s,b,c as o,f as d,h as v,l as n,m as l,q as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PBRIQQ2X.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this.phaseLabel="";this.requireTagScan=!1;this.viaTagScan=!1;this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(e={}){this._open||(this._open=!0,this.viaTagScan=!!e.viaTagScan,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((t,a)=>[String(a),!!this.checklistPrefill[t]]).filter(([,t])=>t)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(t=>[_(t),{...t}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,a=t.files?.[0];if(t.value="",!!a){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",a,a.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(a))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this.viaTagScan&&(e.via_tag_scan=!0),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=r("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=m(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?o`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let a=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(a)||a<=0?null:Math.round(this.restockUnitCost*a*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let a of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(a));c?.cost!=null&&(e+=c.cost*(a.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let a=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return o`<button
|
||||
type="button"
|
||||
class="cost-suggestion"
|
||||
@click=${()=>this._cost=t.toFixed(2)}
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
var r=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestamp<this.CACHE_TTL_MS)return this.usersCache;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/users/list"});return this.usersCache=t.users,this.cacheTimestamp=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,user_id:t})}async getTasksByUser(s){return(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tasks/by_user",user_id:s})).tasks}getUserName(s){return!s||!this.usersCache?null:this.usersCache.find(t=>t.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{r as a};
|
||||
@@ -1,147 +0,0 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KKI2ZPWC.js";import{a as r,b as _,c as l,f as o,h as p,l as d,m as s,q as i,s as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VSV22GFT.js";var a=class extends p{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return h(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(e,n){this._entryId=e,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(e){this._error=u(e,this._lang,i("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,n=this._entryId?i("edit_object",e):i("new_object",e);return l`
|
||||
<ha-dialog open @closed=${this._close}>
|
||||
<div class="dialog-title">${n}</div>
|
||||
<div class="content">
|
||||
${this._error?l`<div class="error">${this._error}</div>`:o}
|
||||
<ms-textfield
|
||||
label="${i("name",e)}"
|
||||
required
|
||||
.value=${this._name}
|
||||
@input=${t=>this._name=t.target.value}
|
||||
></ms-textfield>
|
||||
<ms-textfield
|
||||
label="${i("manufacturer_optional",e)}"
|
||||
.value=${this._manufacturer}
|
||||
@input=${t=>this._manufacturer=t.target.value}
|
||||
></ms-textfield>
|
||||
<ms-textfield
|
||||
label="${i("model_optional",e)}"
|
||||
.value=${this._model}
|
||||
@input=${t=>this._model=t.target.value}
|
||||
></ms-textfield>
|
||||
<ms-textfield
|
||||
label="${i("serial_number_optional",e)}"
|
||||
.value=${this._serialNumber}
|
||||
@input=${t=>this._serialNumber=t.target.value}
|
||||
></ms-textfield>
|
||||
<ms-textfield
|
||||
label="${i("documentation_url_optional",e)}"
|
||||
type="url"
|
||||
.value=${this._documentationUrl}
|
||||
@input=${t=>this._documentationUrl=t.target.value}
|
||||
></ms-textfield>
|
||||
<ha-area-picker
|
||||
.hass=${this.hass}
|
||||
label="${i("area_id_optional",e)}"
|
||||
.value=${this._areaId}
|
||||
@value-changed=${t=>this._areaId=t.detail.value||""}
|
||||
></ha-area-picker>
|
||||
<ms-textfield
|
||||
label="${i("installation_date_optional",e)}"
|
||||
type="date"
|
||||
.value=${this._installationDate}
|
||||
@input=${t=>this._installationDate=t.target.value}
|
||||
></ms-textfield>
|
||||
<ms-textfield
|
||||
label="${i("warranty_expiry_optional",e)}"
|
||||
type="date"
|
||||
.value=${this._warrantyExpiry}
|
||||
@input=${t=>this._warrantyExpiry=t.target.value}
|
||||
></ms-textfield>
|
||||
<ha-form
|
||||
.hass=${this.hass}
|
||||
.data=${{device:this._haDeviceId||void 0}}
|
||||
.schema=${[{name:"device",selector:{device:{}}}]}
|
||||
.computeLabel=${()=>i("link_device_optional",e)}
|
||||
@value-changed=${t=>this._haDeviceId=t.detail.value?.device||""}
|
||||
></ha-form>
|
||||
${this._parentChoices().length?l`<label class="textarea-field">
|
||||
<span class="textarea-label">${i("parent_object_optional",e)}</span>
|
||||
<select
|
||||
class="parent-select"
|
||||
.value=${this._parentEntryId}
|
||||
@change=${t=>this._parentEntryId=t.target.value}
|
||||
>
|
||||
<option value="" ?selected=${!this._parentEntryId}>
|
||||
${i("parent_none",e)}
|
||||
</option>
|
||||
${this._parentChoices().map(t=>l`<option
|
||||
value=${t.entry_id}
|
||||
?selected=${this._parentEntryId===t.entry_id}
|
||||
>${t.object.name}</option>`)}
|
||||
</select>
|
||||
</label>`:o}
|
||||
<label class="textarea-field">
|
||||
<span class="textarea-label">${i("object_notes_optional",e)}</span>
|
||||
<textarea
|
||||
rows="3"
|
||||
.value=${this._notes}
|
||||
@input=${t=>this._notes=t.target.value}
|
||||
></textarea>
|
||||
<span class="md-hint">${i("notes_markdown_hint",e)}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="dialog-actions">
|
||||
<ha-button appearance="plain" @click=${this._close}>
|
||||
${i("cancel",this._lang)}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
@click=${this._save}
|
||||
.disabled=${this._loading||!this._name.trim()}
|
||||
>
|
||||
${this._loading?i("saving",this._lang):i("save",this._lang)}
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-dialog>
|
||||
`}};a.styles=_`
|
||||
.dialog-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
min-width: 300px;
|
||||
}
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
ms-textfield {
|
||||
display: block;
|
||||
}
|
||||
.textarea-field {
|
||||
display: flex; flex-direction: column; gap: 4px;
|
||||
}
|
||||
.textarea-label {
|
||||
font-size: 12px; color: var(--secondary-text-color, #888); font-weight: 500;
|
||||
}
|
||||
.textarea-field textarea {
|
||||
padding: 8px 10px; font-size: 14px; font-family: inherit;
|
||||
background: var(--secondary-background-color, rgba(0,0,0,0.06));
|
||||
color: var(--primary-text-color);
|
||||
border: 1px solid var(--divider-color); border-radius: 6px;
|
||||
resize: vertical;
|
||||
}
|
||||
.textarea-field textarea:focus {
|
||||
outline: none; border-color: var(--primary-color);
|
||||
}
|
||||
.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));
|
||||
color: var(--primary-text-color);
|
||||
border: 1px solid var(--divider-color); border-radius: 6px;
|
||||
}
|
||||
.error {
|
||||
color: var(--error-color, #f44336);
|
||||
font-size: 13px;
|
||||
}
|
||||
`,r([d({attribute:!1})],a.prototype,"hass",2),r([d({attribute:!1})],a.prototype,"objects",2),r([s()],a.prototype,"_open",2),r([s()],a.prototype,"_loading",2),r([s()],a.prototype,"_error",2),r([s()],a.prototype,"_name",2),r([s()],a.prototype,"_manufacturer",2),r([s()],a.prototype,"_model",2),r([s()],a.prototype,"_serialNumber",2),r([s()],a.prototype,"_areaId",2),r([s()],a.prototype,"_installationDate",2),r([s()],a.prototype,"_warrantyExpiry",2),r([s()],a.prototype,"_documentationUrl",2),r([s()],a.prototype,"_notes",2),r([s()],a.prototype,"_haDeviceId",2),r([s()],a.prototype,"_parentEntryId",2),r([s()],a.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",a);export{a};
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
/*! maintenance_supporter frontend 2.70.0 */
|
||||
import{b as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3L2PFMVS.js";async function l(e,n,t=300){return(await e.connection.sendMessagePromise({type:"auth/sign_path",path:n,expires:t})).path}async function r(e,n,t=300){return l(e,`/api/maintenance_supporter/document/${n}`,t)}async function y(e,n,t=""){let a=window.open("about:blank","_blank");try{let o=await r(e,n);a&&(a.location.href=new URL(o+t,window.location.origin).href)}catch(o){throw a&&a.close(),o}}async function p(e,n,t){i(await r(e,n,30),t)}function d(e){let n=URL.createObjectURL(new Blob([e],{type:"text/html"}));window.open(n,"_blank"),setTimeout(()=>URL.revokeObjectURL(n),6e4)}var c=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],u=c.map(e=>e.key),s=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function g(e){if(!Array.isArray(e))return[...s];let n=new Set,t=[];for(let a of e)typeof a=="string"&&u.includes(a)&&!n.has(a)&&(n.add(a),t.push(a));return t.length?(t.includes("name")||t.unshift("name"),t):[...s]}export{l as a,r as b,y as c,p as d,d as e,c as f,s as g,g as h};
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
import{b as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-BQCLT4RW.js";async function l(e,n,t=300){return(await e.connection.sendMessagePromise({type:"auth/sign_path",path:n,expires:t})).path}async function r(e,n,t=300){return l(e,`/api/maintenance_supporter/document/${n}`,t)}async function y(e,n,t=""){let a=window.open("about:blank","_blank");try{let o=await r(e,n);a&&(a.location.href=new URL(o+t,window.location.origin).href)}catch(o){throw a&&a.close(),o}}async function p(e,n,t){i(await r(e,n,30),t)}function d(e){let n=URL.createObjectURL(new Blob([e],{type:"text/html"}));window.open(n,"_blank"),setTimeout(()=>URL.revokeObjectURL(n),6e4)}var c=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],u=c.map(e=>e.key),s=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function g(e){if(!Array.isArray(e))return[...s];let n=new Set,t=[];for(let a of e)typeof a=="string"&&u.includes(a)&&!n.has(a)&&(n.add(a),t.push(a));return t.length?(t.includes("name")||t.unshift("name"),t):[...s]}export{l as a,r as b,y as c,p as d,d as e,c as f,s as g,g as h};
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*! maintenance_supporter frontend 2.68.0 */
|
||||
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LATOZBYW.js";import{a,b as v,c as n,f as p,h as b,l as h,m as c,q as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YEY3J44W.js";function d(l){return l.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function f(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":d(l)}function q(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var o=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,r,s){this._entryId=e,this._taskId=i,this._objectName=r,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let r=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&r.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(r);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let r=i?.code,s=i?.message;this._error=r==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,r=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let g=this.lang||"en",u=d(i),m=d(r),_=!!this._completeResult,w=d(t("qr_action_view",g)),$=d(t("qr_action_complete",g));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
/*! maintenance_supporter frontend 2.71.0 */
|
||||
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-BQCLT4RW.js";import{a,b as v,c as n,f as p,h as b,l as h,m as c,q as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PBRIQQ2X.js";function d(l){return l.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function f(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":d(l)}function q(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var o=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,r,s){this._entryId=e,this._taskId=i,this._objectName=r,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let r=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&r.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(r);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let r=i?.code,s=i?.message;this._error=r==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,r=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let g=this.lang||"en",u=d(i),m=d(r),_=!!this._completeResult,w=d(t("qr_action_view",g)),$=d(t("qr_action_complete",g));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||
<meta name="color-scheme" content="light">
|
||||
<title>${u}</title>
|
||||
<style>
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user