200 files

This commit is contained in:
Home Assistant Version Control
2026-09-11 12:11:18 +00:00
parent ff4156dc89
commit 8769f3b12b
201 changed files with 5190 additions and 24141 deletions
@@ -383,6 +383,8 @@ async def async_maybe_send_lead_reminders(hass: HomeAssistant) -> None:
continue
if task_data.get("archived_at") is not None:
continue
if task_data.get("notify_enabled") is False:
continue # #173: muted task — no lead reminders either
task = MaintenanceTask.from_dict(task_data)
days = task.days_until_due
# days == 0 is a valid lead ("on the due date"); negatives are the
@@ -43,6 +43,8 @@ from .const import (
CONF_NOTIFICATIONS_ENABLED,
CONF_NOTIFY_DUE_SOON_ENABLED,
CONF_NOTIFY_DUE_SOON_INTERVAL,
CONF_NOTIFY_EVENT_ONLY,
CONF_NOTIFY_EXTRA_DATA,
CONF_NOTIFY_OVERDUE_ENABLED,
CONF_NOTIFY_OVERDUE_INTERVAL,
CONF_NOTIFY_SERVICE,
@@ -61,6 +63,7 @@ from .const import (
DEFAULT_PANEL_ENABLED,
DEFAULT_SNOOZE_DURATION_HOURS,
DEFAULT_WARNING_DAYS,
MAX_NOTIFY_EXTRA_DATA_LENGTH,
MAX_PANEL_TITLE_LENGTH,
TIME_HHMMSS_PATTERN,
)
@@ -351,6 +354,10 @@ async def send_test_notification(
notify_service = str(options.get(CONF_NOTIFY_SERVICE, ""))
if not notify_service:
if options.get(CONF_NOTIFY_EVENT_ONLY, False):
# #173: event-only needs no service — the test fires the event
# so the user's automation can be checked from Settings.
return await _send_test_to(hass, options, [""])
return "no_service"
# Format-only validation — existence is left to the async_call below so
@@ -670,6 +677,10 @@ class GlobalOptionsFlow(OptionsFlow):
async def async_step_notification_settings(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Per-status notification toggles, intervals, quiet hours, daily limit."""
if user_input is not None:
# Same cap as the WS settings path (settings registry max_len).
extra = user_input.get(CONF_NOTIFY_EXTRA_DATA)
if isinstance(extra, str):
user_input[CONF_NOTIFY_EXTRA_DATA] = extra[:MAX_NOTIFY_EXTRA_DATA_LENGTH]
return self._save_and_return(user_input)
current = self._current
@@ -770,6 +781,18 @@ class GlobalOptionsFlow(OptionsFlow):
translation_key="notification_title_style",
)
),
# 2.80 (#165) / #173: your own notification rule — the same
# two settings the panel's Settings tab offers.
vol.Optional(
CONF_NOTIFY_EVENT_ONLY,
default=current.get(CONF_NOTIFY_EVENT_ONLY, False),
): selector.BooleanSelector(),
vol.Optional(
CONF_NOTIFY_EXTRA_DATA,
description={"suggested_value": current.get(CONF_NOTIFY_EXTRA_DATA, "")},
): selector.TextSelector(
selector.TextSelectorConfig(type=selector.TextSelectorType.TEXT, multiline=True)
),
}
),
)
@@ -747,6 +747,11 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
for row in notifiable
if nm._is_status_enabled(row[2]) and not nm._is_snoozed(f"{self.entry.entry_id}_{row[0]}_{row[2]}")
]
# #173: a task muted in its own settings sends nothing — neither on
# its own nor inside a bundle. The flag lives in the task's config
# (entry.data), which the computed task_result (model dict) drops.
task_configs = self.entry.data.get(CONF_TASKS) or {}
notifiable = [row for row in notifiable if (task_configs.get(row[0]) or {}).get("notify_enabled") is not False]
# v2.26 notification routing: a saved-view scope ("only notify about
# view X") drops tasks the view's label/user filters don't match —
@@ -1521,6 +1526,19 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
)
def note_trigger_edge(self, task_id: str) -> None:
"""A trigger just latched on a REAL state edge — lift the post-completion
cooldown for that task.
The cooldown (``_recently_completed``, 10 min) exists so the fallback
sweep does not re-activate a task off the still-low sensor value right
after a completion; the event-driven trigger itself is reset on
completion, so a new activation from it is a genuine edge. Without
this, the refresh a flip now requests (#175) — and before that, any
5-minute tick inside the window — wiped the fresh latch again.
"""
self._recently_completed.pop(task_id, None)
async def async_refresh_now(self) -> None:
"""Recompute immediately — for changes a person just made.
@@ -220,6 +220,17 @@ class BaseTrigger(ABC):
Returns True if trigger should be active.
"""
def _request_coordinator_refresh(self) -> None:
"""#175: a trigger flip must reach the coordinator now, not on the
next 5-minute tick. Notifications (and the ``maintenance_supporter_
notification`` event) are decided in the coordinator's refresh from the
status change, so without this the reminder trailed the flip by up to a
full update interval — 30 s once, 4 min the next time. Debounced on
purpose (HA's ten-second window): a noisy sensor must not recompute the
object on every state change; user actions use async_refresh_now."""
self.hass.async_create_task(self._coordinator.async_request_refresh())
def _on_trigger_activated(self, value: float) -> None:
"""Handle trigger activation."""
_LOGGER.info(
@@ -238,6 +249,8 @@ class BaseTrigger(ABC):
# Add history entry for the trigger activation
self.hass.async_create_task(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=value))
self._coordinator.note_trigger_edge(self._task_id)
self._request_coordinator_refresh()
# Fire event
self.hass.bus.async_fire(
@@ -267,6 +280,8 @@ class BaseTrigger(ABC):
trigger_entity_id=self.entity_id,
)
self._request_coordinator_refresh()
# Fire event
self.hass.bus.async_fire(
EVENT_TRIGGER_DEACTIVATED,
@@ -248,6 +248,8 @@ class CompoundTrigger(BaseTrigger):
trigger_entity_id=None,
)
self.hass.async_create_task(self._coordinator.async_add_trigger_history_entry(self._task_id, trigger_value=None))
self._coordinator.note_trigger_edge(self._task_id)
self._request_coordinator_refresh()
self.hass.bus.async_fire(
EVENT_TRIGGER_ACTIVATED,
{
@@ -271,6 +273,7 @@ class CompoundTrigger(BaseTrigger):
current_value=None,
trigger_entity_id=None,
)
self._request_coordinator_refresh()
self.hass.bus.async_fire(
EVENT_TRIGGER_DEACTIVATED,
{
@@ -126,6 +126,8 @@ def _build_export_object(
"require_tag_scan": tdata.get("require_tag_scan"),
# #150: per-task skip lock.
"allow_skip": tdata.get("allow_skip"),
# #173: per-task notification mute.
"notify_enabled": tdata.get("notify_enabled"),
"responsible_user_id": tdata.get("responsible_user_id"),
"entity_slug": tdata.get("entity_slug"),
"adaptive_config": tdata.get("adaptive_config"),
@@ -186,18 +186,25 @@ describe("task-detail renderer", () => {
toggleArchive: () => calls.push("archive"),
}));
const items = [...host.querySelectorAll(".popup-menu-item")];
// edit, qr, worksheet, duplicate, reset, postpone, snooze, archive, delete
expect(items.length).to.equal(9);
(items[5] as HTMLElement).click(); // postpone
(items[6] as HTMLElement).click(); // snooze
// edit, qr, worksheet, duplicate, move (#23), reset, postpone, snooze, archive, delete
expect(items.length).to.equal(10);
(items[6] as HTMLElement).click(); // postpone
(items[7] as HTMLElement).click(); // snooze
(items[2] as HTMLElement).click(); // work sheet (v2.21)
(items[7] as HTMLElement).click(); // archive (demoted from the header)
(items[8] as HTMLElement).click(); // delete (danger)
(items[8] as HTMLElement).click(); // archive (demoted from the header)
(items[9] as HTMLElement).click(); // delete (danger)
expect(calls).to.deep.equal([
"close", "postpone", "close", "snooze", "close", "worksheet", "close", "archive", "close", "delete",
]);
});
it("shows a bell-off badge for a muted task (#173), and none otherwise", () => {
const muted = mount(task({ notify_enabled: false }), ctx());
expect(muted.querySelector(".muted-badge")).to.exist;
const loud = mount(task(), ctx());
expect(loud.querySelector(".muted-badge")).to.not.exist;
});
it("shows a postponed badge when the task has a due_override, and none otherwise", () => {
const plain = mount(task(), ctx());
expect(plain.querySelector(".postponed-badge")).to.be.null;
@@ -16,6 +16,8 @@ export interface PromptOptions extends ConfirmOptions {
inputLabel?: string;
inputType?: string; // "text" | "date" etc.
inputValue?: string;
/** Render a <select> instead of a text input (task/move picks an object). */
options?: { value: string; label: string }[];
}
export interface PromptResult {
@@ -33,6 +35,7 @@ export class MaintenanceConfirmDialog extends LitElement {
@state() private _danger = false;
@state() private _inputLabel = "";
@state() private _inputType = "";
@state() private _options: { value: string; label: string }[] | null = null;
@state() private _inputValue = "";
private _resolve: ((value: boolean) => void) | null = null;
@@ -45,6 +48,7 @@ export class MaintenanceConfirmDialog extends LitElement {
this._danger = opts.danger || false;
this._inputLabel = "";
this._inputType = "";
this._options = null;
this._inputValue = "";
this._open = true;
return new Promise<boolean>((resolve) => {
@@ -60,6 +64,7 @@ export class MaintenanceConfirmDialog extends LitElement {
this._danger = opts.danger || false;
this._inputLabel = opts.inputLabel || "";
this._inputType = opts.inputType || "text";
this._options = opts.options && opts.options.length ? opts.options : null;
this._inputValue = opts.inputValue || "";
this._open = true;
return new Promise<PromptResult>((resolve) => {
@@ -104,10 +109,15 @@ export class MaintenanceConfirmDialog extends LitElement {
the pause/replace prompts; same fix as complete-dialog). -->
<label class="field">
<span class="field-label">${this._inputLabel}</span>
<input class="field-input"
type="${this._inputType || "text"}"
.value=${this._inputValue}
@input=${(e: Event) => (this._inputValue = (e.target as HTMLInputElement).value)} />
${this._options
? html`<select class="field-input field-select"
@change=${(e: Event) => (this._inputValue = (e.target as HTMLSelectElement).value)}>
${this._options.map((o) => html`<option value=${o.value} ?selected=${o.value === this._inputValue}>${o.label}</option>`)}
</select>`
: html`<input class="field-input"
type="${this._inputType || "text"}"
.value=${this._inputValue}
@input=${(e: Event) => (this._inputValue = (e.target as HTMLInputElement).value)} />`}
</label>
` : nothing}
</div>
@@ -982,7 +982,9 @@ export class MaintenanceSettingsView extends LitElement {
<div class="setting-hint">${t("settings_notify_event_only_hint", L)}</div>
<label class="setting-row setting-row-block">
<span class="setting-label">${t("settings_notify_extra_data", L)}</span>
<textarea class="import-area notify-extra" .value=${live(n.extra_data || "")} maxlength="2000" spellcheck="false"
<!-- No live(): the view re-renders on every hass update and live()
would snap the DOM back to the stored value mid-typing (#176). -->
<textarea class="import-area notify-extra" .value=${n.extra_data || ""} maxlength="2000" spellcheck="false"
placeholder=${'{"category": "maintenance", "critical": {{ priority == "high" }}, "navigate_to": "{{ url }}"}'}
@change=${(e: Event) => this._updateSetting("notify_extra_data", (e.target as HTMLTextAreaElement).value)}
></textarea>
@@ -251,6 +251,7 @@ export class MaintenanceTaskDialog extends LitElement {
@state() private _nfcTagId = "";
@state() private _requireTagScan = false;
@state() private _allowSkip = true;
@state() private _notifyEnabled = true;
// v2.20 (#83): unit for `reading`-type tasks ("kWh", "m³", ...)
@state() private _readingUnit = "";
// #161 phase 2: named reading slots — several values per completion.
@@ -412,6 +413,7 @@ export class MaintenanceTaskDialog extends LitElement {
this._nfcTagId = task.nfc_tag_id || "";
this._requireTagScan = !!task.require_tag_scan;
this._allowSkip = task.allow_skip !== false;
this._notifyEnabled = task.notify_enabled !== false;
this._readingUnit = task.reading_unit || "";
this._readings = (task.readings || []).map((s) => ({ ...s }));
// Whole link, entry_id included — hydrating only part_id would turn every
@@ -1318,6 +1320,7 @@ export class MaintenanceTaskDialog extends LitElement {
data.nfc_tag_id = this._nfcTagId || null;
data.require_tag_scan = this._requireTagScan;
data.allow_skip = this._allowSkip;
data.notify_enabled = this._notifyEnabled;
data.reading_unit = this._readingUnit.trim() || null;
// #161 phase 2: always sent — [] clears the slots (single-value task).
// Sent as hydrated even when the editor is hidden (type != reading), so
@@ -2953,6 +2956,15 @@ export class MaintenanceTaskDialog extends LitElement {
<span>${t("disallow_skip", L)}</span>
</label>
${!this._allowSkip ? html`<div class="field-help">${t("disallow_skip_help", L)}</div>` : nothing}
<label class="req-option">
<input
type="checkbox"
.checked=${!this._notifyEnabled}
@change=${(e: Event) => (this._notifyEnabled = !(e.target as HTMLInputElement).checked)}
/>
<span>${t("no_notifications", L)}</span>
</label>
${!this._notifyEnabled ? html`<div class="field-help">${t("no_notifications_help", L)}</div>` : nothing}
<label class="toggle-row">
<input
type="checkbox"
@@ -13,7 +13,7 @@
import { LitElement, html, css, nothing } from "lit";
import { property, state } from "lit/decorators.js";
import { t, ensureLocale, langOf } from "../styles";
import { t, ensureLocale, langOf, syncLocaleFromHass } from "../styles";
import { registerCustomCard } from "../helpers/register-card";
import { describeWsError } from "../ws-errors";
import { sectionCardSharedStyles } from "./section-card-shared-styles";
@@ -66,6 +66,8 @@ export class MaintenanceVacationSectionCard extends LitElement {
updated(changedProps: Map<string, unknown>): void {
super.updated(changedProps);
// #163 tripwire: the date field's typed-entry placeholder formats a date.
syncLocaleFromHass(this, changedProps);
if (changedProps.has("hass") && this.hass && !this._loaded) {
this._loaded = true;
void this._load();
@@ -16,6 +16,11 @@
"edit": "Upravit",
"duplicate": "Duplikovat",
"task_duplicated": "Úkol duplikován",
"task_moved": "Úkol přesunut",
"move_task_target": "Cílový objekt",
"move_task_message": "Vyberte objekt, ke kterému má tento úkol patřit. Historie, odečty a stav spouštěče se přesunou s ním; dostane nové referenční číslo a jeho entity se vytvoří znovu pod novým objektem. Propojené dokumenty zůstanou u aktuálního objektu.",
"move_task_title": "Přesunout úkol",
"move_task": "Přesunout do jiného objektu…",
"object_duplicated": "Objekt duplikován",
"delete": "Smazat",
"add_task": "+ Přidat úkol",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Faktor musí být mezi 0.1 a 5.0",
"clear_all": "Vymazat vše",
"clear": "Vymazat",
"date_type_toggle": "Zadat datum",
"date_type_invalid": "Neplatné datum — zkuste 1978-03-15, rok jako 1978 nebo svůj formát data",
"seasonal_chart_title": "Sezónní faktory",
"seasonal_learned": "Naučené",
"seasonal_manual": "Manuální",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Bez oznámení pro tento úkol",
"no_notifications_help": "Bez připomínek pro tento úkol — žádná oznámení o stavu, opakování, předstihové připomínky ani souhrny. Zůstává vidět na přehledu a ve svých entitách; týdenní přehled ho stále počítá.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Rediger",
"duplicate": "Dupliker",
"task_duplicated": "Opgave duplikeret",
"task_moved": "Opgave flyttet",
"move_task_target": "Målobjekt",
"move_task_message": "Vælg det objekt, opgaven skal høre til. Historik, aflæsninger og triggerstatus flytter med; den får et nyt referencenummer, og dens entiteter oprettes igen under det nye objekt. Tilknyttede dokumenter bliver hos det nuværende objekt.",
"move_task_title": "Flyt opgave",
"move_task": "Flyt til et andet objekt…",
"object_duplicated": "Objekt duplikeret",
"delete": "Slet",
"add_task": "+ Tilføj opgave",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktor skal være mellem 0.1 og 5.0",
"clear_all": "Ryd alle",
"clear": "Ryd",
"date_type_toggle": "Indtast datoen",
"date_type_invalid": "Ugyldig dato — prøv 1978-03-15, et år som 1978 eller dit eget datoformat",
"seasonal_chart_title": "Sæsonfaktorer",
"seasonal_learned": "Lært",
"seasonal_manual": "Manuel",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Ingen notifikationer for denne opgave",
"no_notifications_help": "Ingen påmindelser for denne opgave — ingen statusnotifikationer, gentagelser, forudpåmindelser eller bundter. Den vises stadig på dashboardet og i sine entiteter; ugeoversigten tæller den stadig med.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Bearbeiten",
"duplicate": "Duplizieren",
"task_duplicated": "Aufgabe dupliziert",
"task_moved": "Aufgabe verschoben",
"move_task_target": "Zielobjekt",
"move_task_message": "Wähle das Objekt, zu dem diese Aufgabe gehören soll. Historie, Zählerstände und Trigger-Zustand wandern mit; sie bekommt eine neue Referenznummer und ihre Entitäten werden unter dem neuen Objekt neu angelegt. Verknüpfte Dokumente bleiben beim aktuellen Objekt.",
"move_task_title": "Aufgabe verschieben",
"move_task": "In anderes Objekt verschieben…",
"object_duplicated": "Objekt dupliziert",
"delete": "Löschen",
"add_task": "+ Aufgabe",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktor muss zwischen 0.1 und 5.0 liegen",
"clear_all": "Alle zurücksetzen",
"clear": "Leeren",
"date_type_toggle": "Datum eintippen",
"date_type_invalid": "Kein gültiges Datum — versuche 1978-03-15, ein Jahr wie 1978 oder dein Datumsformat",
"seasonal_chart_title": "Saisonale Faktoren",
"seasonal_learned": "Gelernt",
"seasonal_manual": "Manuell",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Keine Benachrichtigungen für diese Aufgabe",
"no_notifications_help": "Keine Erinnerungen für diese Aufgabe — keine Status-Benachrichtigungen, Wiederholungen, Vorlauf-Erinnerungen oder Bündel. Sie erscheint weiterhin im Dashboard und in ihren Entitäten; der Wochen-Digest zählt sie weiterhin mit.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Edit",
"duplicate": "Duplicate",
"task_duplicated": "Task duplicated",
"task_moved": "Task moved",
"move_task_target": "Target object",
"move_task_message": "Choose the object this task should belong to. Its history, readings and trigger state move with it; it gets a new reference number and its entities are recreated under the new object. Linked documents stay with the current object.",
"move_task_title": "Move task",
"move_task": "Move to another object…",
"object_duplicated": "Object duplicated",
"delete": "Delete",
"add_task": "+ Add Task",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Factor must be between 0.1 and 5.0",
"clear_all": "Clear all",
"clear": "Clear",
"date_type_toggle": "Type the date",
"date_type_invalid": "Not a valid date — try 1978-03-15, a year like 1978, or your own date format",
"seasonal_chart_title": "Seasonal Factors",
"seasonal_learned": "Learned",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "No notifications for this task",
"no_notifications_help": "No reminders for this task — no status notifications, repeats, lead-time reminders or bundles. It still shows on the dashboard and in its entities; the weekly digest still counts it.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Editar",
"duplicate": "Duplicar",
"task_duplicated": "Tarea duplicada",
"task_moved": "Tarea movida",
"move_task_target": "Objeto de destino",
"move_task_message": "Elige el objeto al que debe pertenecer esta tarea. Su historial, lecturas y estado del disparador se mueven con ella; recibe un nuevo número de referencia y sus entidades se recrean bajo el nuevo objeto. Los documentos vinculados se quedan con el objeto actual.",
"move_task_title": "Mover tarea",
"move_task": "Mover a otro objeto…",
"object_duplicated": "Objeto duplicado",
"delete": "Eliminar",
"add_task": "+ Tarea",
@@ -289,6 +294,8 @@
"seasonal_override_range": "El factor debe estar entre 0.1 y 5.0",
"clear_all": "Borrar todo",
"clear": "Borrar",
"date_type_toggle": "Escribir la fecha",
"date_type_invalid": "Fecha no válida — prueba 1978-03-15, un año como 1978 o tu formato de fecha",
"seasonal_chart_title": "Factores estacionales",
"seasonal_learned": "Aprendido",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Sin notificaciones para esta tarea",
"no_notifications_help": "Sin recordatorios para esta tarea — ni notificaciones de estado, ni repeticiones, ni avisos anticipados, ni agrupaciones. Sigue apareciendo en el panel y en sus entidades; el resumen semanal la sigue contando.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Muokkaa",
"duplicate": "Monista",
"task_duplicated": "Tehtävä monistettu",
"task_moved": "Tehtävä siirretty",
"move_task_target": "Kohde",
"move_task_message": "Valitse kohde, johon tämän tehtävän tulee kuulua. Historia, lukemat ja liipaisimen tila siirtyvät mukana; tehtävä saa uuden viitenumeron ja sen entiteetit luodaan uudelleen uuden kohteen alle. Linkitetyt asiakirjat jäävät nykyiseen kohteeseen.",
"move_task_title": "Siirrä tehtävä",
"move_task": "Siirrä toiseen kohteeseen…",
"object_duplicated": "Kohde monistettu",
"delete": "Poista",
"add_task": "+ Lisää tehtävä",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Kertoimen on oltava välillä 0,1–5,0",
"clear_all": "Tyhjennä kaikki",
"clear": "Tyhjennä",
"date_type_toggle": "Kirjoita päivämäärä",
"date_type_invalid": "Virheellinen päivämäärä — kokeile 1978-03-15, vuotta kuten 1978 tai omaa päivämäärämuotoasi",
"seasonal_chart_title": "Kausikertoimet",
"seasonal_learned": "Opittu",
"seasonal_manual": "Manuaalinen",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Ei ilmoituksia tästä tehtävästä",
"no_notifications_help": "Ei muistutuksia tästä tehtävästä — ei tilailmoituksia, toistoja, ennakkomuistutuksia eikä niputuksia. Se näkyy yhä kojelaudalla ja entiteeteissään; viikkokooste laskee sen edelleen mukaan.",
"require_tag_scan_hint": "Tämä tehtävä valmistuu vain skannaamalla sen NFC-tunniste tai QR-koodi itse laitteesta — tallennus tässä estetään."
}
@@ -16,6 +16,11 @@
"edit": "Modifier",
"duplicate": "Dupliquer",
"task_duplicated": "Tâche dupliquée",
"task_moved": "Tâche déplacée",
"move_task_target": "Objet cible",
"move_task_message": "Choisissez l'objet auquel cette tâche doit appartenir. Son historique, ses relevés et l'état de son déclencheur la suivent ; elle reçoit un nouveau numéro de référence et ses entités sont recréées sous le nouvel objet. Les documents liés restent avec l'objet actuel.",
"move_task_title": "Déplacer la tâche",
"move_task": "Déplacer vers un autre objet…",
"object_duplicated": "Objet dupliqué",
"delete": "Supprimer",
"add_task": "+ Tâche",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Le facteur doit être entre 0.1 et 5.0",
"clear_all": "Tout effacer",
"clear": "Effacer",
"date_type_toggle": "Saisir la date",
"date_type_invalid": "Date invalide — essayez 1978-03-15, une année comme 1978 ou votre format de date",
"seasonal_chart_title": "Facteurs saisonniers",
"seasonal_learned": "Appris",
"seasonal_manual": "Manuel",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Aucune notification pour cette tâche",
"no_notifications_help": "Aucun rappel pour cette tâche — ni notifications de statut, ni répétitions, ni rappels anticipés, ni regroupements. Elle reste visible sur le tableau de bord et dans ses entités ; le résumé hebdomadaire la compte toujours.",
"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é."
}
@@ -18,6 +18,11 @@
"edit": "संपादित करें",
"duplicate": "डुप्लिकेट करें",
"task_duplicated": "कार्य डुप्लिकेट किया गया",
"task_moved": "कार्य ले जाया गया",
"move_task_target": "लक्ष्य ऑब्जेक्ट",
"move_task_message": "वह ऑब्जेक्ट चुनें जिससे यह कार्य जुड़ा होना चाहिए। इतिहास, रीडिंग और ट्रिगर स्थिति साथ जाएँगे; इसे नया संदर्भ नंबर मिलेगा और इसकी एंटिटी नए ऑब्जेक्ट के अंतर्गत फिर से बनेंगी। जुड़े दस्तावेज़ वर्तमान ऑब्जेक्ट के पास रहेंगे।",
"move_task_title": "कार्य ले जाएँ",
"move_task": "दूसरे ऑब्जेक्ट में ले जाएँ…",
"object_duplicated": "ऑब्जेक्ट डुप्लिकेट किया गया",
"delete": "हटाएँ",
"add_task": "+ कार्य जोड़ें",
@@ -291,6 +296,8 @@
"seasonal_override_range": "कारक 0.1 और 5.0 के बीच होना चाहिए",
"clear_all": "सभी साफ़ करें",
"clear": "साफ़ करें",
"date_type_toggle": "तारीख टाइप करें",
"date_type_invalid": "मान्य तारीख नहीं — 1978-03-15, 1978 जैसा वर्ष या अपना तारीख प्रारूप आज़माएँ",
"seasonal_chart_title": "मौसमी कारक",
"seasonal_learned": "सीखा गया",
"seasonal_manual": "मैनुअल",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "उपस्थिति प्रमाण: जब तक वस्तु पर लगा NFC टैग या QR कोड स्कैन न हो, हर जगह “पूर्ण” अवरुद्ध रहता है। ऑटोमेशन complete सेवा को 'via_tag_scan' दे सकते हैं।",
"disallow_skip": "छोड़ने की अनुमति न दें",
"disallow_skip_help": "छोड़ें क्रिया सभी सतहों से हट जाती है, और सर्वर ऑटोमेशन व वॉयस से छोड़ने को भी अस्वीकार करता है।",
"no_notifications": "इस कार्य के लिए कोई सूचना नहीं",
"no_notifications_help": "इस कार्य के लिए कोई रिमाइंडर नहीं — न स्थिति सूचनाएँ, न दोहराव, न अग्रिम रिमाइंडर, न बंडल। यह डैशबोर्ड और अपनी एंटिटी में दिखता रहेगा; साप्ताहिक सारांश इसे अब भी गिनेगा।",
"require_tag_scan_hint": "यह कार्य केवल वस्तु पर लगे NFC टैग या QR कोड को स्कैन करके ही पूर्ण होता है — यहाँ सहेजना अस्वीकार होगा।"
}
@@ -18,6 +18,11 @@
"edit": "Szerkesztés",
"duplicate": "Duplikálás",
"task_duplicated": "Feladat duplikálva",
"task_moved": "Feladat áthelyezve",
"move_task_target": "Célobjektum",
"move_task_message": "Válaszd ki, melyik objektumhoz tartozzon ez a feladat. Az előzmények, a mérőállások és a trigger állapota vele költöznek; új hivatkozási számot kap, és az entitásai az új objektum alatt jönnek létre újra. A csatolt dokumentumok a jelenlegi objektumnál maradnak.",
"move_task_title": "Feladat áthelyezése",
"move_task": "Áthelyezés másik objektumba…",
"object_duplicated": "Objektum duplikálva",
"delete": "Törlés",
"add_task": "+ Új feladat",
@@ -291,6 +296,8 @@
"seasonal_override_range": "A tényezőnek 0,1 és 5,0 között kell lennie",
"clear_all": "Összes törlése",
"clear": "Törlés",
"date_type_toggle": "Dátum beírása",
"date_type_invalid": "Érvénytelen dátum — próbáld 1978-03-15 formában, egy évszámmal (1978) vagy a saját dátumformátumoddal",
"seasonal_chart_title": "Szezonális tényezők",
"seasonal_learned": "Tanult",
"seasonal_manual": "Kézi",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Nincs értesítés ehhez a feladathoz",
"no_notifications_help": "Nincs emlékeztető ehhez a feladathoz — sem állapotértesítés, sem ismétlés, sem előzetes emlékeztető, sem csoportos üzenet. Továbbra is látszik a műszerfalon és az entitásaiban; a heti összefoglaló továbbra is számolja.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Modifica",
"duplicate": "Duplica",
"task_duplicated": "Attività duplicata",
"task_moved": "Attività spostata",
"move_task_target": "Oggetto di destinazione",
"move_task_message": "Scegli l'oggetto a cui deve appartenere questa attività. Storico, letture e stato del trigger si spostano con lei; riceve un nuovo numero di riferimento e le sue entità vengono ricreate sotto il nuovo oggetto. I documenti collegati restano con l'oggetto attuale.",
"move_task_title": "Sposta attività",
"move_task": "Sposta in un altro oggetto…",
"object_duplicated": "Oggetto duplicato",
"delete": "Elimina",
"add_task": "+ Attività",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Il fattore deve essere tra 0.1 e 5.0",
"clear_all": "Cancella tutto",
"clear": "Cancella",
"date_type_toggle": "Digita la data",
"date_type_invalid": "Data non valida — prova 1978-03-15, un anno come 1978 o il tuo formato di data",
"seasonal_chart_title": "Fattori stagionali",
"seasonal_learned": "Appreso",
"seasonal_manual": "Manuale",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Nessuna notifica per questa attività",
"no_notifications_help": "Nessun promemoria per questa attività — niente notifiche di stato, ripetizioni, promemoria anticipati o raggruppamenti. Resta visibile nella dashboard e nelle sue entità; il riepilogo settimanale la conta ancora.",
"require_tag_scan_hint": "Questa attività si completa solo scansionando il suo tag NFC o codice QR sull'oggetto — il salvataggio qui verrà rifiutato."
}
@@ -18,6 +18,11 @@
"edit": "編集",
"duplicate": "複製",
"task_duplicated": "タスクを複製しました",
"task_moved": "タスクを移動しました",
"move_task_target": "移動先オブジェクト",
"move_task_message": "このタスクを所属させるオブジェクトを選んでください。履歴・検針値・トリガー状態は一緒に移動し、新しい参照番号が付き、エンティティは新しいオブジェクトの下で作り直されます。関連付けた文書は現在のオブジェクトに残ります。",
"move_task_title": "タスクを移動",
"move_task": "別のオブジェクトへ移動…",
"object_duplicated": "オブジェクトを複製しました",
"delete": "削除",
"add_task": "+ タスク追加",
@@ -291,6 +296,8 @@
"seasonal_override_range": "係数は0.1〜5.0の範囲で指定してください",
"clear_all": "すべてクリア",
"clear": "クリア",
"date_type_toggle": "日付を入力",
"date_type_invalid": "日付として無効です — 1978-03-15、1978 のような年、または設定中の日付形式で入力してください",
"seasonal_chart_title": "季節係数",
"seasonal_learned": "学習値",
"seasonal_manual": "手動",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "在席証明:対象物のNFCタグまたはQRコードをスキャンするまで、どの画面でも「完了」はブロックされます。オートメーションはcompleteサービスに 'via_tag_scan' を渡せます。",
"disallow_skip": "スキップを許可しない",
"disallow_skip_help": "スキップ操作はすべての画面から消え、オートメーションや音声からのスキップもサーバーが拒否します。",
"no_notifications": "このタスクは通知しない",
"no_notifications_help": "このタスクのリマインダーを止めます。ステータス通知・繰り返し・事前リマインダー・まとめ通知は送られません。ダッシュボードやエンティティには引き続き表示され、週次ダイジェストにも数えられます。",
"require_tag_scan_hint": "このタスクは対象物のNFCタグまたはQRコードをスキャンしないと完了できません。ここでの保存は拒否されます。"
}
@@ -18,6 +18,11 @@
"edit": "편집",
"duplicate": "복제",
"task_duplicated": "작업이 복제되었습니다",
"task_moved": "작업을 이동했습니다",
"move_task_target": "대상 개체",
"move_task_message": "이 작업이 속할 개체를 선택하세요. 이력, 검침값, 트리거 상태가 함께 이동하며 새 참조 번호를 받고 엔티티는 새 개체 아래에 다시 생성됩니다. 연결된 문서는 현재 개체에 남습니다.",
"move_task_title": "작업 이동",
"move_task": "다른 개체로 이동…",
"object_duplicated": "객체가 복제되었습니다",
"delete": "삭제",
"add_task": "+ 작업 추가",
@@ -291,6 +296,8 @@
"seasonal_override_range": "계수는 0.1에서 5.0 사이여야 합니다",
"clear_all": "모두 지우기",
"clear": "지우기",
"date_type_toggle": "날짜 직접 입력",
"date_type_invalid": "올바른 날짜가 아닙니다 — 1978-03-15, 1978 같은 연도 또는 사용 중인 날짜 형식으로 입력하세요",
"seasonal_chart_title": "계절 계수",
"seasonal_learned": "학습됨",
"seasonal_manual": "수동",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "현장 확인: 기기의 NFC 태그나 QR 코드를 스캔할 때까지 모든 화면에서 '완료'가 차단됩니다. 자동화는 complete 서비스에 'via_tag_scan'을 전달할 수 있습니다.",
"disallow_skip": "건너뛰기 허용 안 함",
"disallow_skip_help": "건너뛰기 동작이 모든 화면에서 사라지며, 서버는 자동화와 음성의 건너뛰기도 거부합니다.",
"no_notifications": "이 작업은 알림 없음",
"no_notifications_help": "이 작업에는 리마인더가 없습니다. 상태 알림, 반복, 사전 알림, 묶음 알림이 모두 꺼집니다. 대시보드와 엔티티에는 계속 표시되며 주간 요약에는 계속 포함됩니다.",
"require_tag_scan_hint": "이 작업은 기기의 NFC 태그나 QR 코드를 스캔해야만 완료됩니다. 여기서 저장하면 거부됩니다."
}
@@ -18,6 +18,11 @@
"edit": "Rediger",
"duplicate": "Dupliser",
"task_duplicated": "Oppgave duplisert",
"task_moved": "Oppgave flyttet",
"move_task_target": "Målobjekt",
"move_task_message": "Velg objektet denne oppgaven skal tilhøre. Historikk, avlesninger og utløserstatus følger med; den får et nytt referansenummer, og entitetene opprettes på nytt under det nye objektet. Tilknyttede dokumenter blir hos det nåværende objektet.",
"move_task_title": "Flytt oppgave",
"move_task": "Flytt til et annet objekt…",
"object_duplicated": "Objekt duplisert",
"delete": "Slett",
"add_task": "+ Legg til oppgave",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktor må være mellom 0,1 og 5,0",
"clear_all": "Fjern alle",
"clear": "Tøm",
"date_type_toggle": "Skriv inn datoen",
"date_type_invalid": "Ugyldig dato — prøv 1978-03-15, et år som 1978 eller ditt eget datoformat",
"seasonal_chart_title": "Sesongfaktorer",
"seasonal_learned": "Lært",
"seasonal_manual": "Manuell",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Ingen varsler for denne oppgaven",
"no_notifications_help": "Ingen påminnelser for denne oppgaven — ingen statusvarsler, gjentakelser, forhåndspåminnelser eller bunter. Den vises fortsatt på dashbordet og i entitetene sine; ukesoppsummeringen teller den fortsatt med.",
"require_tag_scan_hint": "Denne oppgaven fullføres bare ved å skanne NFC-taggen eller QR-koden på selve tingen — lagring her vil bli avvist."
}
@@ -16,6 +16,11 @@
"edit": "Bewerken",
"duplicate": "Dupliceren",
"task_duplicated": "Taak gedupliceerd",
"task_moved": "Taak verplaatst",
"move_task_target": "Doelobject",
"move_task_message": "Kies het object waar deze taak bij hoort. Geschiedenis, meterstanden en triggerstatus verhuizen mee; de taak krijgt een nieuw referentienummer en haar entiteiten worden onder het nieuwe object opnieuw aangemaakt. Gekoppelde documenten blijven bij het huidige object.",
"move_task_title": "Taak verplaatsen",
"move_task": "Naar ander object verplaatsen…",
"object_duplicated": "Object gedupliceerd",
"delete": "Verwijderen",
"add_task": "+ Taak",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Factor moet tussen 0.1 en 5.0 liggen",
"clear_all": "Alles wissen",
"clear": "Wissen",
"date_type_toggle": "Datum typen",
"date_type_invalid": "Geen geldige datum — probeer 1978-03-15, een jaar zoals 1978 of je eigen datumnotatie",
"seasonal_chart_title": "Seizoensfactoren",
"seasonal_learned": "Geleerd",
"seasonal_manual": "Handmatig",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Geen notificaties voor deze taak",
"no_notifications_help": "Geen herinneringen voor deze taak — geen statusnotificaties, herhalingen, vooraankondigingen of bundels. Ze blijft zichtbaar op het dashboard en in haar entiteiten; het weekoverzicht telt haar nog mee.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Edytuj",
"duplicate": "Duplikuj",
"task_duplicated": "Zadanie zduplikowane",
"task_moved": "Zadanie przeniesione",
"move_task_target": "Obiekt docelowy",
"move_task_message": "Wybierz obiekt, do którego ma należeć to zadanie. Historia, odczyty i stan wyzwalacza przenoszą się razem z nim; dostaje nowy numer referencyjny, a jego encje są tworzone na nowo pod nowym obiektem. Powiązane dokumenty zostają przy obecnym obiekcie.",
"move_task_title": "Przenieś zadanie",
"move_task": "Przenieś do innego obiektu…",
"object_duplicated": "Obiekt zduplikowany",
"delete": "Usuń",
"add_task": "+ Dodaj zadanie",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Czynnik musi być między 0.1 a 5.0",
"clear_all": "Wyczyść wszystko",
"clear": "Wyczyść",
"date_type_toggle": "Wpisz datę",
"date_type_invalid": "Nieprawidłowa data — wpisz 1978-03-15, rok jak 1978 albo swój format daty",
"seasonal_chart_title": "Czynniki sezonowe",
"seasonal_learned": "Wyuczone",
"seasonal_manual": "Ręczne",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Bez powiadomień dla tego zadania",
"no_notifications_help": "Bez przypomnień dla tego zadania — bez powiadomień o statusie, powtórzeń, wcześniejszych przypomnień i pakietów. Nadal widać je na pulpicie i w jego encjach; tygodniowe podsumowanie wciąż je liczy.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Editar",
"duplicate": "Duplicar",
"task_duplicated": "Tarefa duplicada",
"task_moved": "Tarefa movida",
"move_task_target": "Objeto de destino",
"move_task_message": "Escolha o objeto ao qual esta tarefa deve pertencer. Histórico, leituras e estado do gatilho vão junto; ela recebe um novo número de referência e suas entidades são recriadas no novo objeto. Documentos vinculados permanecem no objeto atual.",
"move_task_title": "Mover tarefa",
"move_task": "Mover para outro objeto…",
"object_duplicated": "Objeto duplicado",
"delete": "Excluir",
"add_task": "+ Adicionar tarefa",
@@ -291,6 +296,8 @@
"seasonal_override_range": "O fator deve estar entre 0,1 e 5,0",
"clear_all": "Limpar tudo",
"clear": "Limpar",
"date_type_toggle": "Digitar a data",
"date_type_invalid": "Data inválida — tente 1978-03-15, um ano como 1978 ou seu formato de data",
"seasonal_chart_title": "Fatores sazonais",
"seasonal_learned": "Aprendido",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Sem notificações para esta tarefa",
"no_notifications_help": "Sem lembretes para esta tarefa — sem notificações de status, repetições, avisos antecipados ou agrupamentos. Ela continua aparecendo no painel e em suas entidades; o resumo semanal ainda a conta.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Editar",
"duplicate": "Duplicar",
"task_duplicated": "Tarefa duplicada",
"task_moved": "Tarefa movida",
"move_task_target": "Objeto de destino",
"move_task_message": "Escolha o objeto a que esta tarefa deve pertencer. O histórico, as leituras e o estado do gatilho vão com ela; recebe um novo número de referência e as suas entidades são recriadas no novo objeto. Os documentos ligados ficam no objeto atual.",
"move_task_title": "Mover tarefa",
"move_task": "Mover para outro objeto…",
"object_duplicated": "Objeto duplicado",
"delete": "Eliminar",
"add_task": "+ Tarefa",
@@ -289,6 +294,8 @@
"seasonal_override_range": "O fator deve estar entre 0.1 e 5.0",
"clear_all": "Limpar tudo",
"clear": "Limpar",
"date_type_toggle": "Escrever a data",
"date_type_invalid": "Data inválida — tente 1978-03-15, um ano como 1978 ou o seu formato de data",
"seasonal_chart_title": "Fatores sazonais",
"seasonal_learned": "Aprendido",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Sem notificações para esta tarefa",
"no_notifications_help": "Sem lembretes para esta tarefa — sem notificações de estado, repetições, avisos antecipados ou agrupamentos. Continua a aparecer no painel e nas suas entidades; o resumo semanal continua a contá-la.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Изменить",
"duplicate": "Дублировать",
"task_duplicated": "Задача продублирована",
"task_moved": "Задача перемещена",
"move_task_target": "Целевой объект",
"move_task_message": "Выберите объект, к которому должна относиться эта задача. История, показания и состояние триггера переедут вместе с ней; она получит новый номер, а её сущности будут созданы заново под новым объектом. Привязанные документы остаются у текущего объекта.",
"move_task_title": "Переместить задачу",
"move_task": "Переместить в другой объект…",
"object_duplicated": "Объект продублирован",
"delete": "Удалить",
"add_task": "+ Добавить задачу",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Коэффициент должен быть от 0.1 до 5.0",
"clear_all": "Очистить все",
"clear": "Очистить",
"date_type_toggle": "Ввести дату",
"date_type_invalid": "Некорректная дата — попробуйте 1978-03-15, год вроде 1978 или ваш формат даты",
"seasonal_chart_title": "Сезонные факторы",
"seasonal_learned": "Изученные",
"seasonal_manual": "Ручные",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "Подтверждение присутствия: «Готово» заблокировано везде, пока не отсканирована NFC-метка или QR-код на самом устройстве. Автоматизации могут передавать 'via_tag_scan' в службу complete.",
"disallow_skip": "Запретить пропуск",
"disallow_skip_help": "Действие «Пропустить» исчезает со всех экранов, а сервер отклоняет пропуск из автоматизаций и голосовых команд.",
"no_notifications": "Без уведомлений для этой задачи",
"no_notifications_help": "Без напоминаний для этой задачи — ни уведомлений о статусе, ни повторов, ни заблаговременных напоминаний, ни объединённых сообщений. Она по-прежнему видна на панели и в своих сущностях; еженедельная сводка её учитывает.",
"require_tag_scan_hint": "Эта задача завершается только сканированием её NFC-метки или QR-кода на самом устройстве — сохранение здесь будет отклонено."
}
@@ -16,6 +16,11 @@
"edit": "Redigera",
"duplicate": "Duplicera",
"task_duplicated": "Uppgift duplicerad",
"task_moved": "Uppgift flyttad",
"move_task_target": "Målobjekt",
"move_task_message": "Välj vilket objekt uppgiften ska tillhöra. Historik, avläsningar och utlösarstatus följer med; den får ett nytt referensnummer och dess entiteter skapas om under det nya objektet. Länkade dokument stannar hos det nuvarande objektet.",
"move_task_title": "Flytta uppgift",
"move_task": "Flytta till ett annat objekt…",
"object_duplicated": "Objekt duplicerat",
"delete": "Ta bort",
"add_task": "+ Lägg till uppgift",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Faktor måste vara mellan 0.1 och 5.0",
"clear_all": "Rensa alla",
"clear": "Rensa",
"date_type_toggle": "Skriv datumet",
"date_type_invalid": "Ogiltigt datum — prova 1978-03-15, ett år som 1978 eller ditt eget datumformat",
"seasonal_chart_title": "Säsongsfaktorer",
"seasonal_learned": "Lärt",
"seasonal_manual": "Manuell",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Inga aviseringar för den här uppgiften",
"no_notifications_help": "Inga påminnelser för den här uppgiften — inga statusaviseringar, upprepningar, förhandspåminnelser eller buntar. Den syns fortfarande på instrumentpanelen och i sina entiteter; veckosammanfattningen räknar den fortfarande.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Düzenle",
"duplicate": "Çoğalt",
"task_duplicated": "Görev çoğaltıldı",
"task_moved": "Görev taşındı",
"move_task_target": "Hedef nesne",
"move_task_message": "Bu görevin ait olacağı nesneyi seçin. Geçmiş, okumalar ve tetikleyici durumu birlikte taşınır; yeni bir referans numarası alır ve varlıkları yeni nesne altında yeniden oluşturulur. Bağlı belgeler mevcut nesnede kalır.",
"move_task_title": "Görevi taşı",
"move_task": "Başka bir nesneye taşı…",
"object_duplicated": "Nesne çoğaltıldı",
"delete": "Sil",
"add_task": "+ Görev Ekle",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktör 0.1 ile 5.0 arasında olmalıdır",
"clear_all": "Tümünü temizle",
"clear": "Temizle",
"date_type_toggle": "Tarihi yazın",
"date_type_invalid": "Geçersiz tarih — 1978-03-15, 1978 gibi bir yıl ya da kendi tarih biçiminizi deneyin",
"seasonal_chart_title": "Mevsimsel Faktörler",
"seasonal_learned": "Öğrenilmiş",
"seasonal_manual": "Manuel",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Bu görev için bildirim yok",
"no_notifications_help": "Bu görev için hatırlatma yok — durum bildirimi, tekrar, önceden hatırlatma veya toplu bildirim gönderilmez. Panoda ve varlıklarında görünmeye devam eder; haftalık özet onu saymaya devam eder.",
"require_tag_scan_hint": "Bu görev yalnızca cihazın üzerindeki NFC etiketi veya QR kodu taranarak tamamlanır — buradan kaydetme reddedilecektir."
}
@@ -16,6 +16,11 @@
"edit": "Редагувати",
"duplicate": "Дублювати",
"task_duplicated": "Завдання продубльовано",
"task_moved": "Завдання переміщено",
"move_task_target": "Цільовий об'єкт",
"move_task_message": "Виберіть об'єкт, до якого має належати це завдання. Історія, показники та стан тригера переїдуть разом із ним; воно отримає новий номер, а його сутності буде створено заново під новим об'єктом. Прив'язані документи залишаться в поточному об'єкті.",
"move_task_title": "Перемістити завдання",
"move_task": "Перемістити до іншого об'єкта…",
"object_duplicated": "Об'єкт продубльовано",
"delete": "Видалити",
"add_task": "+ Додати завдання",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Коефіцієнт має бути між 0.1 та 5.0",
"clear_all": "Очистити все",
"clear": "Очистити",
"date_type_toggle": "Ввести дату",
"date_type_invalid": "Некоректна дата — спробуйте 1978-03-15, рік на кшталт 1978 або ваш формат дати",
"seasonal_chart_title": "Сезонні коефіцієнти",
"seasonal_learned": "Навчена",
"seasonal_manual": "Ручна",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "Підтвердження присутності: «Готово» заблоковано всюди, доки не відскановано NFC-мітку або QR-код на самому пристрої. Автоматизації можуть передати 'via_tag_scan' службі complete.",
"disallow_skip": "Заборонити пропуск",
"disallow_skip_help": "Дія «Пропустити» зникає з усіх поверхонь, а сервер відхиляє пропуск з автоматизацій і голосу.",
"no_notifications": "Без сповіщень для цього завдання",
"no_notifications_help": "Без нагадувань для цього завдання — ні сповіщень про статус, ні повторів, ні завчасних нагадувань, ні об'єднаних повідомлень. Воно й далі видиме на панелі та у своїх сутностях; тижневий підсумок його враховує.",
"require_tag_scan_hint": "Це завдання завершується лише скануванням його NFC-мітки або QR-коду на самому пристрої — збереження тут буде відхилено."
}
@@ -18,6 +18,11 @@
"edit": "编辑",
"duplicate": "复制",
"task_duplicated": "任务已复制",
"task_moved": "任务已移动",
"move_task_target": "目标对象",
"move_task_message": "选择此任务应归属的对象。历史记录、读数和触发器状态会一并迁移;任务将获得新的编号,其实体会在新对象下重新创建。关联的文档仍留在当前对象。",
"move_task_title": "移动任务",
"move_task": "移动到其他对象…",
"object_duplicated": "对象已复制",
"delete": "删除",
"add_task": "+ 添加任务",
@@ -291,6 +296,8 @@
"seasonal_override_range": "系数必须介于 0.1 到 5.0 之间",
"clear_all": "全部清除",
"clear": "清除",
"date_type_toggle": "输入日期",
"date_type_invalid": "日期无效 — 请试试 1978-03-15、年份如 1978 或你的日期格式",
"seasonal_chart_title": "季节性因素",
"seasonal_learned": "自动学习",
"seasonal_manual": "手动设置",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "在场证明:在扫描设备上的 NFC 标签或二维码之前,所有界面的“完成”均被阻止。自动化可向 complete 服务传递 'via_tag_scan'。",
"disallow_skip": "不允许跳过",
"disallow_skip_help": "跳过操作将从所有界面消失,服务器也会拒绝来自自动化和语音的跳过。",
"no_notifications": "此任务不发送通知",
"no_notifications_help": "此任务不再提醒——没有状态通知、重复、提前提醒或合并通知。它仍显示在仪表板和其实体中;每周摘要仍会统计它。",
"require_tag_scan_hint": "此任务只能通过扫描设备上的 NFC 标签或二维码来完成——在此保存将被拒绝。"
}
@@ -1091,6 +1091,7 @@ export class MaintenanceSupporterPanel extends LitElement {
task_id: task.id,
object_name: obj.object.name,
allow_skip: task.allow_skip !== false,
notify_enabled: task.notify_enabled !== false,
task_name: task.name,
type: task.type,
schedule_type: task.schedule_type,
@@ -2048,6 +2049,32 @@ export class MaintenanceSupporterPanel extends LitElement {
if (res?.task_id) this._showTask(entryId, res.task_id);
}
// Forum #23: a task created on the wrong object — move it instead of
// re-creating it. Config, history, readings and trigger state travel with
// the task; it gets a new reference number and entities under the target.
private async _moveTask(entryId: string, taskId: string): Promise<void> {
this._moreMenuOpen = false;
const others = this._objects
.filter((o) => o.entry_id !== entryId && !o.object.archived_at)
.sort((a, b) => (a.object.name || "").localeCompare(b.object.name || ""));
if (!others.length) return;
const dlg = this.shadowRoot!.querySelector<MaintenanceConfirmDialog>("maintenance-confirm-dialog");
const result = await dlg?.prompt({
title: t("move_task_title", this._lang),
message: t("move_task_message", this._lang),
confirmText: t("move_task_title", this._lang),
inputLabel: t("move_task_target", this._lang),
inputValue: others[0].entry_id,
options: others.map((o) => ({ value: o.entry_id, label: o.object.name || o.entry_id })),
});
if (!result?.confirmed || !result.value) return;
const res = await this._runAction<{ task_id?: string; entry_id?: string }>(
{ type: "maintenance_supporter/task/move", entry_id: entryId, task_id: taskId, target_entry_id: result.value },
{ successToast: t("task_moved", this._lang) },
);
if (res?.entry_id && res.task_id) this._showTask(res.entry_id, res.task_id);
}
private async _toggleArchiveTask(entryId: string, taskId: string, archived: boolean): Promise<void> {
const res = await this._runAction({
type: archived
@@ -4353,6 +4380,7 @@ export class MaintenanceSupporterPanel extends LitElement {
toggleArchive: (archived) => this._toggleArchiveTask(entryId, taskId, archived),
openQr: (taskName) => this._openQrForTask(entryId, taskId, obj?.object.name || "", taskName),
duplicateTask: () => this._duplicateTask(entryId, taskId),
moveTask: () => this._moveTask(entryId, taskId),
promptReset: () => this._promptResetTask(entryId, taskId),
promptPostpone: () => this._promptPostponeTask(entryId, taskId),
snoozeTask: () => this._snoozeTask(entryId, taskId),
@@ -4480,9 +4480,9 @@
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true,
"funding": [
{
@@ -78,6 +78,8 @@ export interface TaskDetailContext {
toggleArchive: (archived: boolean) => void;
openQr: (taskName: string) => void;
duplicateTask: () => void;
/** Forum #23: move the task to another object. */
moveTask: () => void;
promptReset: () => void;
promptPostpone: () => void;
snoozeTask: () => void;
@@ -133,6 +135,9 @@ function renderTaskHeader(task: MaintenanceTask, ctx: TaskDetailContext) {
<ha-icon icon="mdi:calendar-arrow-right"></ha-icon>${formatDate(task.due_override, L)}
</span>` : nothing}
${renderUserBadge(task, ctx.getUserName, ctx.getPerson)}
${task.notify_enabled === false
? html`<span class="nfc-badge muted-badge" title="${t("no_notifications", L)}"><ha-icon icon="mdi:bell-off-outline"></ha-icon></span>`
: nothing}
${task.nfc_tag_id
? html`<span class="nfc-badge" title="${t("nfc_tag_id", L)}: ${task.nfc_tag_id}"><ha-icon icon="mdi:nfc-variant"></ha-icon> NFC</span>`
: !isOperator ? html`<span class="nfc-badge unlinked" title="${t("nfc_link_hint", L)}"
@@ -157,6 +162,7 @@ function renderTaskHeader(task: MaintenanceTask, ctx: TaskDetailContext) {
<div class="popup-menu-item" @click=${() => { ctx.closeMoreMenu(); ctx.printWorksheet(); }}>${t("worksheet", L)}</div>
${!isOperator ? html`
<div class="popup-menu-item" @click=${() => ctx.duplicateTask()}>${t("duplicate", L)}</div>
<div class="popup-menu-item" @click=${() => ctx.moveTask()}>${t("move_task", L)}</div>
<div class="popup-menu-item" @click=${() => { ctx.closeMoreMenu(); ctx.promptReset(); }}>${t("reset", L)}</div>
<div class="popup-menu-item" @click=${() => { ctx.closeMoreMenu(); ctx.promptPostpone(); }}>${t("postpone", L)}…</div>
<div class="popup-menu-item" @click=${() => { ctx.closeMoreMenu(); ctx.snoozeTask(); }}>${t("snooze", L)}</div>
@@ -410,6 +410,14 @@ interface RecurrenceLike {
* day_of_month / one_time / manual. Used by the panel, card, and quick-actions. */
export function formatRecurrence(task: RecurrenceLike, lang?: string): string {
const s = task.schedule;
// A sensor-driven task is not "Manual": say so, and append the safety
// interval when one is set (the KPI card read "Manual" for the HVAC filter
// in the docs shot — reported as "schief").
if (task.schedule_type === "sensor_based") {
const base = t("sensor_based", lang);
const every = s && s.kind === "interval" && s.every ? s.every : task.interval_days;
return every ? `${base} · ${formatInterval(every, (s && s.kind === "interval" ? s.unit : task.interval_unit) || "days", lang)}` : base;
}
// (#83) ±N-day shift suffix shared by the calendar kinds.
const off = s?.offset ? ` ${s.offset > 0 ? "+" : "−"}${Math.abs(s.offset)}d` : "";
switch (s?.kind) {
@@ -204,6 +204,8 @@ export interface MaintenanceTask {
enabled: boolean;
/** #150: false = the skip lock — Skip hidden everywhere, server refuses. */
allow_skip?: boolean;
/** #173: false = this task sends no reminders (dashboard/entities unaffected). */
notify_enabled?: boolean;
schedule_type: string; // "time_based" | "sensor_based" | "one_time" | "manual" | calendar kind
interval_days?: number | null;
interval_unit?: string; // "days" | "weeks" | "months" | "years"
@@ -544,6 +546,8 @@ export interface TaskRow {
enabled: boolean;
/** #150: false = skip locked for this task. */
allow_skip: boolean;
/** #173: false = no reminders for this task. */
notify_enabled: boolean;
nfc_tag_id: string | null;
priority: string;
labels: string[];
@@ -16,6 +16,11 @@
"edit": "Upravit",
"duplicate": "Duplikovat",
"task_duplicated": "Úkol duplikován",
"task_moved": "Úkol přesunut",
"move_task_target": "Cílový objekt",
"move_task_message": "Vyberte objekt, ke kterému má tento úkol patřit. Historie, odečty a stav spouštěče se přesunou s ním; dostane nové referenční číslo a jeho entity se vytvoří znovu pod novým objektem. Propojené dokumenty zůstanou u aktuálního objektu.",
"move_task_title": "Přesunout úkol",
"move_task": "Přesunout do jiného objektu…",
"object_duplicated": "Objekt duplikován",
"delete": "Smazat",
"add_task": "+ Přidat úkol",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Faktor musí být mezi 0.1 a 5.0",
"clear_all": "Vymazat vše",
"clear": "Vymazat",
"date_type_toggle": "Zadat datum",
"date_type_invalid": "Neplatné datum — zkuste 1978-03-15, rok jako 1978 nebo svůj formát data",
"seasonal_chart_title": "Sezónní faktory",
"seasonal_learned": "Naučené",
"seasonal_manual": "Manuální",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Bez oznámení pro tento úkol",
"no_notifications_help": "Bez připomínek pro tento úkol — žádná oznámení o stavu, opakování, předstihové připomínky ani souhrny. Zůstává vidět na přehledu a ve svých entitách; týdenní přehled ho stále počítá.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Rediger",
"duplicate": "Dupliker",
"task_duplicated": "Opgave duplikeret",
"task_moved": "Opgave flyttet",
"move_task_target": "Målobjekt",
"move_task_message": "Vælg det objekt, opgaven skal høre til. Historik, aflæsninger og triggerstatus flytter med; den får et nyt referencenummer, og dens entiteter oprettes igen under det nye objekt. Tilknyttede dokumenter bliver hos det nuværende objekt.",
"move_task_title": "Flyt opgave",
"move_task": "Flyt til et andet objekt…",
"object_duplicated": "Objekt duplikeret",
"delete": "Slet",
"add_task": "+ Tilføj opgave",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktor skal være mellem 0.1 og 5.0",
"clear_all": "Ryd alle",
"clear": "Ryd",
"date_type_toggle": "Indtast datoen",
"date_type_invalid": "Ugyldig dato — prøv 1978-03-15, et år som 1978 eller dit eget datoformat",
"seasonal_chart_title": "Sæsonfaktorer",
"seasonal_learned": "Lært",
"seasonal_manual": "Manuel",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Ingen notifikationer for denne opgave",
"no_notifications_help": "Ingen påmindelser for denne opgave — ingen statusnotifikationer, gentagelser, forudpåmindelser eller bundter. Den vises stadig på dashboardet og i sine entiteter; ugeoversigten tæller den stadig med.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Bearbeiten",
"duplicate": "Duplizieren",
"task_duplicated": "Aufgabe dupliziert",
"task_moved": "Aufgabe verschoben",
"move_task_target": "Zielobjekt",
"move_task_message": "Wähle das Objekt, zu dem diese Aufgabe gehören soll. Historie, Zählerstände und Trigger-Zustand wandern mit; sie bekommt eine neue Referenznummer und ihre Entitäten werden unter dem neuen Objekt neu angelegt. Verknüpfte Dokumente bleiben beim aktuellen Objekt.",
"move_task_title": "Aufgabe verschieben",
"move_task": "In anderes Objekt verschieben…",
"object_duplicated": "Objekt dupliziert",
"delete": "Löschen",
"add_task": "+ Aufgabe",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktor muss zwischen 0.1 und 5.0 liegen",
"clear_all": "Alle zurücksetzen",
"clear": "Leeren",
"date_type_toggle": "Datum eintippen",
"date_type_invalid": "Kein gültiges Datum — versuche 1978-03-15, ein Jahr wie 1978 oder dein Datumsformat",
"seasonal_chart_title": "Saisonale Faktoren",
"seasonal_learned": "Gelernt",
"seasonal_manual": "Manuell",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Keine Benachrichtigungen für diese Aufgabe",
"no_notifications_help": "Keine Erinnerungen für diese Aufgabe — keine Status-Benachrichtigungen, Wiederholungen, Vorlauf-Erinnerungen oder Bündel. Sie erscheint weiterhin im Dashboard und in ihren Entitäten; der Wochen-Digest zählt sie weiterhin mit.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Edit",
"duplicate": "Duplicate",
"task_duplicated": "Task duplicated",
"task_moved": "Task moved",
"move_task_target": "Target object",
"move_task_message": "Choose the object this task should belong to. Its history, readings and trigger state move with it; it gets a new reference number and its entities are recreated under the new object. Linked documents stay with the current object.",
"move_task_title": "Move task",
"move_task": "Move to another object…",
"object_duplicated": "Object duplicated",
"delete": "Delete",
"add_task": "+ Add Task",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Factor must be between 0.1 and 5.0",
"clear_all": "Clear all",
"clear": "Clear",
"date_type_toggle": "Type the date",
"date_type_invalid": "Not a valid date — try 1978-03-15, a year like 1978, or your own date format",
"seasonal_chart_title": "Seasonal Factors",
"seasonal_learned": "Learned",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "No notifications for this task",
"no_notifications_help": "No reminders for this task — no status notifications, repeats, lead-time reminders or bundles. It still shows on the dashboard and in its entities; the weekly digest still counts it.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Editar",
"duplicate": "Duplicar",
"task_duplicated": "Tarea duplicada",
"task_moved": "Tarea movida",
"move_task_target": "Objeto de destino",
"move_task_message": "Elige el objeto al que debe pertenecer esta tarea. Su historial, lecturas y estado del disparador se mueven con ella; recibe un nuevo número de referencia y sus entidades se recrean bajo el nuevo objeto. Los documentos vinculados se quedan con el objeto actual.",
"move_task_title": "Mover tarea",
"move_task": "Mover a otro objeto…",
"object_duplicated": "Objeto duplicado",
"delete": "Eliminar",
"add_task": "+ Tarea",
@@ -289,6 +294,8 @@
"seasonal_override_range": "El factor debe estar entre 0.1 y 5.0",
"clear_all": "Borrar todo",
"clear": "Borrar",
"date_type_toggle": "Escribir la fecha",
"date_type_invalid": "Fecha no válida — prueba 1978-03-15, un año como 1978 o tu formato de fecha",
"seasonal_chart_title": "Factores estacionales",
"seasonal_learned": "Aprendido",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Sin notificaciones para esta tarea",
"no_notifications_help": "Sin recordatorios para esta tarea — ni notificaciones de estado, ni repeticiones, ni avisos anticipados, ni agrupaciones. Sigue apareciendo en el panel y en sus entidades; el resumen semanal la sigue contando.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Muokkaa",
"duplicate": "Monista",
"task_duplicated": "Tehtävä monistettu",
"task_moved": "Tehtävä siirretty",
"move_task_target": "Kohde",
"move_task_message": "Valitse kohde, johon tämän tehtävän tulee kuulua. Historia, lukemat ja liipaisimen tila siirtyvät mukana; tehtävä saa uuden viitenumeron ja sen entiteetit luodaan uudelleen uuden kohteen alle. Linkitetyt asiakirjat jäävät nykyiseen kohteeseen.",
"move_task_title": "Siirrä tehtävä",
"move_task": "Siirrä toiseen kohteeseen…",
"object_duplicated": "Kohde monistettu",
"delete": "Poista",
"add_task": "+ Lisää tehtävä",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Kertoimen on oltava välillä 0,1–5,0",
"clear_all": "Tyhjennä kaikki",
"clear": "Tyhjennä",
"date_type_toggle": "Kirjoita päivämäärä",
"date_type_invalid": "Virheellinen päivämäärä — kokeile 1978-03-15, vuotta kuten 1978 tai omaa päivämäärämuotoasi",
"seasonal_chart_title": "Kausikertoimet",
"seasonal_learned": "Opittu",
"seasonal_manual": "Manuaalinen",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Ei ilmoituksia tästä tehtävästä",
"no_notifications_help": "Ei muistutuksia tästä tehtävästä — ei tilailmoituksia, toistoja, ennakkomuistutuksia eikä niputuksia. Se näkyy yhä kojelaudalla ja entiteeteissään; viikkokooste laskee sen edelleen mukaan.",
"require_tag_scan_hint": "Tämä tehtävä valmistuu vain skannaamalla sen NFC-tunniste tai QR-koodi itse laitteesta — tallennus tässä estetään."
}
@@ -16,6 +16,11 @@
"edit": "Modifier",
"duplicate": "Dupliquer",
"task_duplicated": "Tâche dupliquée",
"task_moved": "Tâche déplacée",
"move_task_target": "Objet cible",
"move_task_message": "Choisissez l'objet auquel cette tâche doit appartenir. Son historique, ses relevés et l'état de son déclencheur la suivent ; elle reçoit un nouveau numéro de référence et ses entités sont recréées sous le nouvel objet. Les documents liés restent avec l'objet actuel.",
"move_task_title": "Déplacer la tâche",
"move_task": "Déplacer vers un autre objet…",
"object_duplicated": "Objet dupliqué",
"delete": "Supprimer",
"add_task": "+ Tâche",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Le facteur doit être entre 0.1 et 5.0",
"clear_all": "Tout effacer",
"clear": "Effacer",
"date_type_toggle": "Saisir la date",
"date_type_invalid": "Date invalide — essayez 1978-03-15, une année comme 1978 ou votre format de date",
"seasonal_chart_title": "Facteurs saisonniers",
"seasonal_learned": "Appris",
"seasonal_manual": "Manuel",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Aucune notification pour cette tâche",
"no_notifications_help": "Aucun rappel pour cette tâche — ni notifications de statut, ni répétitions, ni rappels anticipés, ni regroupements. Elle reste visible sur le tableau de bord et dans ses entités ; le résumé hebdomadaire la compte toujours.",
"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é."
}
@@ -18,6 +18,11 @@
"edit": "संपादित करें",
"duplicate": "डुप्लिकेट करें",
"task_duplicated": "कार्य डुप्लिकेट किया गया",
"task_moved": "कार्य ले जाया गया",
"move_task_target": "लक्ष्य ऑब्जेक्ट",
"move_task_message": "वह ऑब्जेक्ट चुनें जिससे यह कार्य जुड़ा होना चाहिए। इतिहास, रीडिंग और ट्रिगर स्थिति साथ जाएँगे; इसे नया संदर्भ नंबर मिलेगा और इसकी एंटिटी नए ऑब्जेक्ट के अंतर्गत फिर से बनेंगी। जुड़े दस्तावेज़ वर्तमान ऑब्जेक्ट के पास रहेंगे।",
"move_task_title": "कार्य ले जाएँ",
"move_task": "दूसरे ऑब्जेक्ट में ले जाएँ…",
"object_duplicated": "ऑब्जेक्ट डुप्लिकेट किया गया",
"delete": "हटाएँ",
"add_task": "+ कार्य जोड़ें",
@@ -291,6 +296,8 @@
"seasonal_override_range": "कारक 0.1 और 5.0 के बीच होना चाहिए",
"clear_all": "सभी साफ़ करें",
"clear": "साफ़ करें",
"date_type_toggle": "तारीख टाइप करें",
"date_type_invalid": "मान्य तारीख नहीं — 1978-03-15, 1978 जैसा वर्ष या अपना तारीख प्रारूप आज़माएँ",
"seasonal_chart_title": "मौसमी कारक",
"seasonal_learned": "सीखा गया",
"seasonal_manual": "मैनुअल",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "उपस्थिति प्रमाण: जब तक वस्तु पर लगा NFC टैग या QR कोड स्कैन न हो, हर जगह “पूर्ण” अवरुद्ध रहता है। ऑटोमेशन complete सेवा को 'via_tag_scan' दे सकते हैं।",
"disallow_skip": "छोड़ने की अनुमति न दें",
"disallow_skip_help": "छोड़ें क्रिया सभी सतहों से हट जाती है, और सर्वर ऑटोमेशन व वॉयस से छोड़ने को भी अस्वीकार करता है।",
"no_notifications": "इस कार्य के लिए कोई सूचना नहीं",
"no_notifications_help": "इस कार्य के लिए कोई रिमाइंडर नहीं — न स्थिति सूचनाएँ, न दोहराव, न अग्रिम रिमाइंडर, न बंडल। यह डैशबोर्ड और अपनी एंटिटी में दिखता रहेगा; साप्ताहिक सारांश इसे अब भी गिनेगा।",
"require_tag_scan_hint": "यह कार्य केवल वस्तु पर लगे NFC टैग या QR कोड को स्कैन करके ही पूर्ण होता है — यहाँ सहेजना अस्वीकार होगा।"
}
@@ -18,6 +18,11 @@
"edit": "Szerkesztés",
"duplicate": "Duplikálás",
"task_duplicated": "Feladat duplikálva",
"task_moved": "Feladat áthelyezve",
"move_task_target": "Célobjektum",
"move_task_message": "Válaszd ki, melyik objektumhoz tartozzon ez a feladat. Az előzmények, a mérőállások és a trigger állapota vele költöznek; új hivatkozási számot kap, és az entitásai az új objektum alatt jönnek létre újra. A csatolt dokumentumok a jelenlegi objektumnál maradnak.",
"move_task_title": "Feladat áthelyezése",
"move_task": "Áthelyezés másik objektumba…",
"object_duplicated": "Objektum duplikálva",
"delete": "Törlés",
"add_task": "+ Új feladat",
@@ -291,6 +296,8 @@
"seasonal_override_range": "A tényezőnek 0,1 és 5,0 között kell lennie",
"clear_all": "Összes törlése",
"clear": "Törlés",
"date_type_toggle": "Dátum beírása",
"date_type_invalid": "Érvénytelen dátum — próbáld 1978-03-15 formában, egy évszámmal (1978) vagy a saját dátumformátumoddal",
"seasonal_chart_title": "Szezonális tényezők",
"seasonal_learned": "Tanult",
"seasonal_manual": "Kézi",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Nincs értesítés ehhez a feladathoz",
"no_notifications_help": "Nincs emlékeztető ehhez a feladathoz — sem állapotértesítés, sem ismétlés, sem előzetes emlékeztető, sem csoportos üzenet. Továbbra is látszik a műszerfalon és az entitásaiban; a heti összefoglaló továbbra is számolja.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Modifica",
"duplicate": "Duplica",
"task_duplicated": "Attività duplicata",
"task_moved": "Attività spostata",
"move_task_target": "Oggetto di destinazione",
"move_task_message": "Scegli l'oggetto a cui deve appartenere questa attività. Storico, letture e stato del trigger si spostano con lei; riceve un nuovo numero di riferimento e le sue entità vengono ricreate sotto il nuovo oggetto. I documenti collegati restano con l'oggetto attuale.",
"move_task_title": "Sposta attività",
"move_task": "Sposta in un altro oggetto…",
"object_duplicated": "Oggetto duplicato",
"delete": "Elimina",
"add_task": "+ Attività",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Il fattore deve essere tra 0.1 e 5.0",
"clear_all": "Cancella tutto",
"clear": "Cancella",
"date_type_toggle": "Digita la data",
"date_type_invalid": "Data non valida — prova 1978-03-15, un anno come 1978 o il tuo formato di data",
"seasonal_chart_title": "Fattori stagionali",
"seasonal_learned": "Appreso",
"seasonal_manual": "Manuale",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Nessuna notifica per questa attività",
"no_notifications_help": "Nessun promemoria per questa attività — niente notifiche di stato, ripetizioni, promemoria anticipati o raggruppamenti. Resta visibile nella dashboard e nelle sue entità; il riepilogo settimanale la conta ancora.",
"require_tag_scan_hint": "Questa attività si completa solo scansionando il suo tag NFC o codice QR sull'oggetto — il salvataggio qui verrà rifiutato."
}
@@ -18,6 +18,11 @@
"edit": "編集",
"duplicate": "複製",
"task_duplicated": "タスクを複製しました",
"task_moved": "タスクを移動しました",
"move_task_target": "移動先オブジェクト",
"move_task_message": "このタスクを所属させるオブジェクトを選んでください。履歴・検針値・トリガー状態は一緒に移動し、新しい参照番号が付き、エンティティは新しいオブジェクトの下で作り直されます。関連付けた文書は現在のオブジェクトに残ります。",
"move_task_title": "タスクを移動",
"move_task": "別のオブジェクトへ移動…",
"object_duplicated": "オブジェクトを複製しました",
"delete": "削除",
"add_task": "+ タスク追加",
@@ -291,6 +296,8 @@
"seasonal_override_range": "係数は0.1〜5.0の範囲で指定してください",
"clear_all": "すべてクリア",
"clear": "クリア",
"date_type_toggle": "日付を入力",
"date_type_invalid": "日付として無効です — 1978-03-15、1978 のような年、または設定中の日付形式で入力してください",
"seasonal_chart_title": "季節係数",
"seasonal_learned": "学習値",
"seasonal_manual": "手動",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "在席証明:対象物のNFCタグまたはQRコードをスキャンするまで、どの画面でも「完了」はブロックされます。オートメーションはcompleteサービスに 'via_tag_scan' を渡せます。",
"disallow_skip": "スキップを許可しない",
"disallow_skip_help": "スキップ操作はすべての画面から消え、オートメーションや音声からのスキップもサーバーが拒否します。",
"no_notifications": "このタスクは通知しない",
"no_notifications_help": "このタスクのリマインダーを止めます。ステータス通知・繰り返し・事前リマインダー・まとめ通知は送られません。ダッシュボードやエンティティには引き続き表示され、週次ダイジェストにも数えられます。",
"require_tag_scan_hint": "このタスクは対象物のNFCタグまたはQRコードをスキャンしないと完了できません。ここでの保存は拒否されます。"
}
@@ -18,6 +18,11 @@
"edit": "편집",
"duplicate": "복제",
"task_duplicated": "작업이 복제되었습니다",
"task_moved": "작업을 이동했습니다",
"move_task_target": "대상 개체",
"move_task_message": "이 작업이 속할 개체를 선택하세요. 이력, 검침값, 트리거 상태가 함께 이동하며 새 참조 번호를 받고 엔티티는 새 개체 아래에 다시 생성됩니다. 연결된 문서는 현재 개체에 남습니다.",
"move_task_title": "작업 이동",
"move_task": "다른 개체로 이동…",
"object_duplicated": "객체가 복제되었습니다",
"delete": "삭제",
"add_task": "+ 작업 추가",
@@ -291,6 +296,8 @@
"seasonal_override_range": "계수는 0.1에서 5.0 사이여야 합니다",
"clear_all": "모두 지우기",
"clear": "지우기",
"date_type_toggle": "날짜 직접 입력",
"date_type_invalid": "올바른 날짜가 아닙니다 — 1978-03-15, 1978 같은 연도 또는 사용 중인 날짜 형식으로 입력하세요",
"seasonal_chart_title": "계절 계수",
"seasonal_learned": "학습됨",
"seasonal_manual": "수동",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "현장 확인: 기기의 NFC 태그나 QR 코드를 스캔할 때까지 모든 화면에서 '완료'가 차단됩니다. 자동화는 complete 서비스에 'via_tag_scan'을 전달할 수 있습니다.",
"disallow_skip": "건너뛰기 허용 안 함",
"disallow_skip_help": "건너뛰기 동작이 모든 화면에서 사라지며, 서버는 자동화와 음성의 건너뛰기도 거부합니다.",
"no_notifications": "이 작업은 알림 없음",
"no_notifications_help": "이 작업에는 리마인더가 없습니다. 상태 알림, 반복, 사전 알림, 묶음 알림이 모두 꺼집니다. 대시보드와 엔티티에는 계속 표시되며 주간 요약에는 계속 포함됩니다.",
"require_tag_scan_hint": "이 작업은 기기의 NFC 태그나 QR 코드를 스캔해야만 완료됩니다. 여기서 저장하면 거부됩니다."
}
@@ -18,6 +18,11 @@
"edit": "Rediger",
"duplicate": "Dupliser",
"task_duplicated": "Oppgave duplisert",
"task_moved": "Oppgave flyttet",
"move_task_target": "Målobjekt",
"move_task_message": "Velg objektet denne oppgaven skal tilhøre. Historikk, avlesninger og utløserstatus følger med; den får et nytt referansenummer, og entitetene opprettes på nytt under det nye objektet. Tilknyttede dokumenter blir hos det nåværende objektet.",
"move_task_title": "Flytt oppgave",
"move_task": "Flytt til et annet objekt…",
"object_duplicated": "Objekt duplisert",
"delete": "Slett",
"add_task": "+ Legg til oppgave",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktor må være mellom 0,1 og 5,0",
"clear_all": "Fjern alle",
"clear": "Tøm",
"date_type_toggle": "Skriv inn datoen",
"date_type_invalid": "Ugyldig dato — prøv 1978-03-15, et år som 1978 eller ditt eget datoformat",
"seasonal_chart_title": "Sesongfaktorer",
"seasonal_learned": "Lært",
"seasonal_manual": "Manuell",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Ingen varsler for denne oppgaven",
"no_notifications_help": "Ingen påminnelser for denne oppgaven — ingen statusvarsler, gjentakelser, forhåndspåminnelser eller bunter. Den vises fortsatt på dashbordet og i entitetene sine; ukesoppsummeringen teller den fortsatt med.",
"require_tag_scan_hint": "Denne oppgaven fullføres bare ved å skanne NFC-taggen eller QR-koden på selve tingen — lagring her vil bli avvist."
}
@@ -16,6 +16,11 @@
"edit": "Bewerken",
"duplicate": "Dupliceren",
"task_duplicated": "Taak gedupliceerd",
"task_moved": "Taak verplaatst",
"move_task_target": "Doelobject",
"move_task_message": "Kies het object waar deze taak bij hoort. Geschiedenis, meterstanden en triggerstatus verhuizen mee; de taak krijgt een nieuw referentienummer en haar entiteiten worden onder het nieuwe object opnieuw aangemaakt. Gekoppelde documenten blijven bij het huidige object.",
"move_task_title": "Taak verplaatsen",
"move_task": "Naar ander object verplaatsen…",
"object_duplicated": "Object gedupliceerd",
"delete": "Verwijderen",
"add_task": "+ Taak",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Factor moet tussen 0.1 en 5.0 liggen",
"clear_all": "Alles wissen",
"clear": "Wissen",
"date_type_toggle": "Datum typen",
"date_type_invalid": "Geen geldige datum — probeer 1978-03-15, een jaar zoals 1978 of je eigen datumnotatie",
"seasonal_chart_title": "Seizoensfactoren",
"seasonal_learned": "Geleerd",
"seasonal_manual": "Handmatig",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Geen notificaties voor deze taak",
"no_notifications_help": "Geen herinneringen voor deze taak — geen statusnotificaties, herhalingen, vooraankondigingen of bundels. Ze blijft zichtbaar op het dashboard en in haar entiteiten; het weekoverzicht telt haar nog mee.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Edytuj",
"duplicate": "Duplikuj",
"task_duplicated": "Zadanie zduplikowane",
"task_moved": "Zadanie przeniesione",
"move_task_target": "Obiekt docelowy",
"move_task_message": "Wybierz obiekt, do którego ma należeć to zadanie. Historia, odczyty i stan wyzwalacza przenoszą się razem z nim; dostaje nowy numer referencyjny, a jego encje są tworzone na nowo pod nowym obiektem. Powiązane dokumenty zostają przy obecnym obiekcie.",
"move_task_title": "Przenieś zadanie",
"move_task": "Przenieś do innego obiektu…",
"object_duplicated": "Obiekt zduplikowany",
"delete": "Usuń",
"add_task": "+ Dodaj zadanie",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Czynnik musi być między 0.1 a 5.0",
"clear_all": "Wyczyść wszystko",
"clear": "Wyczyść",
"date_type_toggle": "Wpisz datę",
"date_type_invalid": "Nieprawidłowa data — wpisz 1978-03-15, rok jak 1978 albo swój format daty",
"seasonal_chart_title": "Czynniki sezonowe",
"seasonal_learned": "Wyuczone",
"seasonal_manual": "Ręczne",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Bez powiadomień dla tego zadania",
"no_notifications_help": "Bez przypomnień dla tego zadania — bez powiadomień o statusie, powtórzeń, wcześniejszych przypomnień i pakietów. Nadal widać je na pulpicie i w jego encjach; tygodniowe podsumowanie wciąż je liczy.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Editar",
"duplicate": "Duplicar",
"task_duplicated": "Tarefa duplicada",
"task_moved": "Tarefa movida",
"move_task_target": "Objeto de destino",
"move_task_message": "Escolha o objeto ao qual esta tarefa deve pertencer. Histórico, leituras e estado do gatilho vão junto; ela recebe um novo número de referência e suas entidades são recriadas no novo objeto. Documentos vinculados permanecem no objeto atual.",
"move_task_title": "Mover tarefa",
"move_task": "Mover para outro objeto…",
"object_duplicated": "Objeto duplicado",
"delete": "Excluir",
"add_task": "+ Adicionar tarefa",
@@ -291,6 +296,8 @@
"seasonal_override_range": "O fator deve estar entre 0,1 e 5,0",
"clear_all": "Limpar tudo",
"clear": "Limpar",
"date_type_toggle": "Digitar a data",
"date_type_invalid": "Data inválida — tente 1978-03-15, um ano como 1978 ou seu formato de data",
"seasonal_chart_title": "Fatores sazonais",
"seasonal_learned": "Aprendido",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Sem notificações para esta tarefa",
"no_notifications_help": "Sem lembretes para esta tarefa — sem notificações de status, repetições, avisos antecipados ou agrupamentos. Ela continua aparecendo no painel e em suas entidades; o resumo semanal ainda a conta.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Editar",
"duplicate": "Duplicar",
"task_duplicated": "Tarefa duplicada",
"task_moved": "Tarefa movida",
"move_task_target": "Objeto de destino",
"move_task_message": "Escolha o objeto a que esta tarefa deve pertencer. O histórico, as leituras e o estado do gatilho vão com ela; recebe um novo número de referência e as suas entidades são recriadas no novo objeto. Os documentos ligados ficam no objeto atual.",
"move_task_title": "Mover tarefa",
"move_task": "Mover para outro objeto…",
"object_duplicated": "Objeto duplicado",
"delete": "Eliminar",
"add_task": "+ Tarefa",
@@ -289,6 +294,8 @@
"seasonal_override_range": "O fator deve estar entre 0.1 e 5.0",
"clear_all": "Limpar tudo",
"clear": "Limpar",
"date_type_toggle": "Escrever a data",
"date_type_invalid": "Data inválida — tente 1978-03-15, um ano como 1978 ou o seu formato de data",
"seasonal_chart_title": "Fatores sazonais",
"seasonal_learned": "Aprendido",
"seasonal_manual": "Manual",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Sem notificações para esta tarefa",
"no_notifications_help": "Sem lembretes para esta tarefa — sem notificações de estado, repetições, avisos antecipados ou agrupamentos. Continua a aparecer no painel e nas suas entidades; o resumo semanal continua a contá-la.",
"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."
}
@@ -16,6 +16,11 @@
"edit": "Изменить",
"duplicate": "Дублировать",
"task_duplicated": "Задача продублирована",
"task_moved": "Задача перемещена",
"move_task_target": "Целевой объект",
"move_task_message": "Выберите объект, к которому должна относиться эта задача. История, показания и состояние триггера переедут вместе с ней; она получит новый номер, а её сущности будут созданы заново под новым объектом. Привязанные документы остаются у текущего объекта.",
"move_task_title": "Переместить задачу",
"move_task": "Переместить в другой объект…",
"object_duplicated": "Объект продублирован",
"delete": "Удалить",
"add_task": "+ Добавить задачу",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Коэффициент должен быть от 0.1 до 5.0",
"clear_all": "Очистить все",
"clear": "Очистить",
"date_type_toggle": "Ввести дату",
"date_type_invalid": "Некорректная дата — попробуйте 1978-03-15, год вроде 1978 или ваш формат даты",
"seasonal_chart_title": "Сезонные факторы",
"seasonal_learned": "Изученные",
"seasonal_manual": "Ручные",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "Подтверждение присутствия: «Готово» заблокировано везде, пока не отсканирована NFC-метка или QR-код на самом устройстве. Автоматизации могут передавать 'via_tag_scan' в службу complete.",
"disallow_skip": "Запретить пропуск",
"disallow_skip_help": "Действие «Пропустить» исчезает со всех экранов, а сервер отклоняет пропуск из автоматизаций и голосовых команд.",
"no_notifications": "Без уведомлений для этой задачи",
"no_notifications_help": "Без напоминаний для этой задачи — ни уведомлений о статусе, ни повторов, ни заблаговременных напоминаний, ни объединённых сообщений. Она по-прежнему видна на панели и в своих сущностях; еженедельная сводка её учитывает.",
"require_tag_scan_hint": "Эта задача завершается только сканированием её NFC-метки или QR-кода на самом устройстве — сохранение здесь будет отклонено."
}
@@ -16,6 +16,11 @@
"edit": "Redigera",
"duplicate": "Duplicera",
"task_duplicated": "Uppgift duplicerad",
"task_moved": "Uppgift flyttad",
"move_task_target": "Målobjekt",
"move_task_message": "Välj vilket objekt uppgiften ska tillhöra. Historik, avläsningar och utlösarstatus följer med; den får ett nytt referensnummer och dess entiteter skapas om under det nya objektet. Länkade dokument stannar hos det nuvarande objektet.",
"move_task_title": "Flytta uppgift",
"move_task": "Flytta till ett annat objekt…",
"object_duplicated": "Objekt duplicerat",
"delete": "Ta bort",
"add_task": "+ Lägg till uppgift",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Faktor måste vara mellan 0.1 och 5.0",
"clear_all": "Rensa alla",
"clear": "Rensa",
"date_type_toggle": "Skriv datumet",
"date_type_invalid": "Ogiltigt datum — prova 1978-03-15, ett år som 1978 eller ditt eget datumformat",
"seasonal_chart_title": "Säsongsfaktorer",
"seasonal_learned": "Lärt",
"seasonal_manual": "Manuell",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Inga aviseringar för den här uppgiften",
"no_notifications_help": "Inga påminnelser för den här uppgiften — inga statusaviseringar, upprepningar, förhandspåminnelser eller buntar. Den syns fortfarande på instrumentpanelen och i sina entiteter; veckosammanfattningen räknar den fortfarande.",
"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."
}
@@ -18,6 +18,11 @@
"edit": "Düzenle",
"duplicate": "Çoğalt",
"task_duplicated": "Görev çoğaltıldı",
"task_moved": "Görev taşındı",
"move_task_target": "Hedef nesne",
"move_task_message": "Bu görevin ait olacağı nesneyi seçin. Geçmiş, okumalar ve tetikleyici durumu birlikte taşınır; yeni bir referans numarası alır ve varlıkları yeni nesne altında yeniden oluşturulur. Bağlı belgeler mevcut nesnede kalır.",
"move_task_title": "Görevi taşı",
"move_task": "Başka bir nesneye taşı…",
"object_duplicated": "Nesne çoğaltıldı",
"delete": "Sil",
"add_task": "+ Görev Ekle",
@@ -291,6 +296,8 @@
"seasonal_override_range": "Faktör 0.1 ile 5.0 arasında olmalıdır",
"clear_all": "Tümünü temizle",
"clear": "Temizle",
"date_type_toggle": "Tarihi yazın",
"date_type_invalid": "Geçersiz tarih — 1978-03-15, 1978 gibi bir yıl ya da kendi tarih biçiminizi deneyin",
"seasonal_chart_title": "Mevsimsel Faktörler",
"seasonal_learned": "Öğrenilmiş",
"seasonal_manual": "Manuel",
@@ -995,5 +1002,7 @@
"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.",
"no_notifications": "Bu görev için bildirim yok",
"no_notifications_help": "Bu görev için hatırlatma yok — durum bildirimi, tekrar, önceden hatırlatma veya toplu bildirim gönderilmez. Panoda ve varlıklarında görünmeye devam eder; haftalık özet onu saymaya devam eder.",
"require_tag_scan_hint": "Bu görev yalnızca cihazın üzerindeki NFC etiketi veya QR kodu taranarak tamamlanır — buradan kaydetme reddedilecektir."
}
@@ -16,6 +16,11 @@
"edit": "Редагувати",
"duplicate": "Дублювати",
"task_duplicated": "Завдання продубльовано",
"task_moved": "Завдання переміщено",
"move_task_target": "Цільовий об'єкт",
"move_task_message": "Виберіть об'єкт, до якого має належати це завдання. Історія, показники та стан тригера переїдуть разом із ним; воно отримає новий номер, а його сутності буде створено заново під новим об'єктом. Прив'язані документи залишаться в поточному об'єкті.",
"move_task_title": "Перемістити завдання",
"move_task": "Перемістити до іншого об'єкта…",
"object_duplicated": "Об'єкт продубльовано",
"delete": "Видалити",
"add_task": "+ Додати завдання",
@@ -289,6 +294,8 @@
"seasonal_override_range": "Коефіцієнт має бути між 0.1 та 5.0",
"clear_all": "Очистити все",
"clear": "Очистити",
"date_type_toggle": "Ввести дату",
"date_type_invalid": "Некоректна дата — спробуйте 1978-03-15, рік на кшталт 1978 або ваш формат дати",
"seasonal_chart_title": "Сезонні коефіцієнти",
"seasonal_learned": "Навчена",
"seasonal_manual": "Ручна",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "Підтвердження присутності: «Готово» заблоковано всюди, доки не відскановано NFC-мітку або QR-код на самому пристрої. Автоматизації можуть передати 'via_tag_scan' службі complete.",
"disallow_skip": "Заборонити пропуск",
"disallow_skip_help": "Дія «Пропустити» зникає з усіх поверхонь, а сервер відхиляє пропуск з автоматизацій і голосу.",
"no_notifications": "Без сповіщень для цього завдання",
"no_notifications_help": "Без нагадувань для цього завдання — ні сповіщень про статус, ні повторів, ні завчасних нагадувань, ні об'єднаних повідомлень. Воно й далі видиме на панелі та у своїх сутностях; тижневий підсумок його враховує.",
"require_tag_scan_hint": "Це завдання завершується лише скануванням його NFC-мітки або QR-коду на самому пристрої — збереження тут буде відхилено."
}
@@ -18,6 +18,11 @@
"edit": "编辑",
"duplicate": "复制",
"task_duplicated": "任务已复制",
"task_moved": "任务已移动",
"move_task_target": "目标对象",
"move_task_message": "选择此任务应归属的对象。历史记录、读数和触发器状态会一并迁移;任务将获得新的编号,其实体会在新对象下重新创建。关联的文档仍留在当前对象。",
"move_task_title": "移动任务",
"move_task": "移动到其他对象…",
"object_duplicated": "对象已复制",
"delete": "删除",
"add_task": "+ 添加任务",
@@ -291,6 +296,8 @@
"seasonal_override_range": "系数必须介于 0.1 到 5.0 之间",
"clear_all": "全部清除",
"clear": "清除",
"date_type_toggle": "输入日期",
"date_type_invalid": "日期无效 — 请试试 1978-03-15、年份如 1978 或你的日期格式",
"seasonal_chart_title": "季节性因素",
"seasonal_learned": "自动学习",
"seasonal_manual": "手动设置",
@@ -995,5 +1002,7 @@
"require_tag_scan_help": "在场证明:在扫描设备上的 NFC 标签或二维码之前,所有界面的“完成”均被阻止。自动化可向 complete 服务传递 'via_tag_scan'。",
"disallow_skip": "不允许跳过",
"disallow_skip_help": "跳过操作将从所有界面消失,服务器也会拒绝来自自动化和语音的跳过。",
"no_notifications": "此任务不发送通知",
"no_notifications_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
File diff suppressed because one or more lines are too long
@@ -1,250 +0,0 @@
/*! 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>
<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,5 +1,5 @@
/*! maintenance_supporter frontend 2.80.0 */
import{e as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PRQXFMNO.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.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-NAI3MD3R.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.82.0 */
import{e as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-B4ELBSAY.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XCPYIT7M.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-4OPY4VQ5.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>
@@ -1,250 +0,0 @@
/*! maintenance_supporter frontend 2.74.0 */
import{a as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-S2VAROHZ.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NYOACMQR.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-Y2JXJRDF.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,64 +0,0 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as r,b as d,c as a,f as l,h as p,l as s,q as c}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";function h(o,e){if(e)switch(o){case"date":return e.split("T")[0];case"time":return e.length===5?`${e}:00`:e;case"datetime":{let[i,n="00:00:00"]=e.split("T");return`${i} ${n.length===5?`${n}:00`:n}`}}}function u(o,e){if(typeof e!="string"||!e)return"";switch(o){case"date":return e.slice(0,10);case"time":return e.slice(0,5);case"datetime":{let[i,n="00:00:00"]=e.split(" ");return`${i}T${n.length===5?`${n}:00`:n}`}}}var t=class extends p{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en"}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(i){i.stopPropagation(),this._emit(u(this.kind,i.detail?.value))}_clear(){this._emit("")}_emit(i){i!==this.value&&(this.value=i,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:i}})))}render(){return a`
<div class="field">
${this.label?a`<span class="label">${this.label}${this.required?a`<span class="req">*</span>`:l}</span>`:l}
<div class="row">
<ha-selector
.hass=${this.hass}
.selector=${this._selector()}
.value=${h(this.kind,this.value)}
.required=${this.required}
.disabled=${this.disabled}
@value-changed=${this._onSelectorChange}
></ha-selector>
${this.clearable&&this.value&&!this.disabled?a`<button type="button" class="clear" title=${c("clear",this.lang)} aria-label=${c("clear",this.lang)} @click=${this._clear}>
<ha-icon icon="mdi:close"></ha-icon>
</button>`:l}
</div>
${this.helper?a`<span class="helper">${this.helper}</span>`:l}
</div>
`}};t.styles=d`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
.row {
display: flex;
align-items: center;
gap: 4px;
}
ha-selector {
flex: 1;
min-width: 0;
}
.clear {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 20px;
}
.clear:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); }
.clear:focus-visible { outline: 2px solid var(--primary-color); }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,r([s({attribute:!1})],t.prototype,"hass",2),r([s()],t.prototype,"kind",2),r([s()],t.prototype,"label",2),r([s()],t.prototype,"value",2),r([s()],t.prototype,"helper",2),r([s({type:Boolean})],t.prototype,"clearable",2),r([s({type:Boolean})],t.prototype,"disabled",2),r([s({type:Boolean})],t.prototype,"required",2),r([s()],t.prototype,"lang",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",t);
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
/*! maintenance_supporter frontend 2.74.0 */
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NYOACMQR.js";import{a as r,b as _,c as l,f as o,h,l as d,m as s,q as i,s as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y2JXJRDF.js";var a=class extends h{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 p(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`
/*! maintenance_supporter frontend 2.82.0 */
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XCPYIT7M.js";import{a as r,b as _,c as l,f as o,h,l as d,m as s,q as i,s as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4OPY4VQ5.js";var a=class extends h{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 p(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">
@@ -1,79 +0,0 @@
/*! 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`
<textarea
.value=${this.value??""}
rows=${this.rows}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
@input=${this._onInput}
@change=${this._onInput}
></textarea>`:i`
<input
.value=${this.value??""}
.type=${this.type}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
step=${this.step??o}
min=${this.min??o}
max=${this.max??o}
pattern=${this.pattern??o}
@input=${this._onInput}
@change=${this._onInput}
/>`}
${this.helper?i`<span class="helper">${this.helper}</span>`:o}
</label>
`}};e.styles=l`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
input {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
}
input:focus {
border-color: var(--primary-color);
}
input:disabled { opacity: 0.5; cursor: not-allowed; }
textarea {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
resize: vertical;
}
textarea:focus { border-color: var(--primary-color); }
textarea:disabled { opacity: 0.5; cursor: not-allowed; }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,r([t()],e.prototype,"label",2),r([t()],e.prototype,"value",2),r([t()],e.prototype,"placeholder",2),r([t()],e.prototype,"type",2),r([t({type:Boolean})],e.prototype,"required",2),r([t({type:Boolean})],e.prototype,"disabled",2),r([t()],e.prototype,"step",2),r([t()],e.prototype,"min",2),r([t()],e.prototype,"max",2),r([t()],e.prototype,"pattern",2),r([t()],e.prototype,"helper",2),r([t({type:Boolean})],e.prototype,"multiline",2),r([t({type:Number})],e.prototype,"rows",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e);
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{c as r,f as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";var a=["#c62828","#ad1457","#6a1b9a","#4527a0","#283593","#1565c0","#00838f","#2e7d32","#558b2f","#ef6c00","#6d4c41","#546e7a"];function u(e){let s=(e||"").split(/\s+/).filter(Boolean);return s.length===0?"?":s.length===1?s[0][0].toUpperCase():(s[0][0]+s[s.length-1][0]).toUpperCase()}function c(e){let s=0;for(let n of e)s=s*31+n.charCodeAt(0)>>>0;return a[s%a.length]}function o(e){return e?{id:e.id,name:e.name,initials:e.initials||u(e.name),color:e.color||c(e.id)}:null}function h(e){return e?r`<span class="person-avatar" style="--person-color: ${e.color}" title=${e.name}>${e.initials}</span>`:i}function m(e,s=""){return e?r`<span class="person-chip ${s}" title=${e.name}>${h(e)}<span class="person-name">${e.name}</span></span>`:i}var l=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 n=Date.now();if(!s&&this.usersCache&&n-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=n,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,n,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:n,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}getPerson(s){return o(this.getUser(s))}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(n=>n.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{a,o as b,h as c,m as d,l as e};
/*! maintenance_supporter frontend 2.82.0 */
import{c as r,f as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4OPY4VQ5.js";var a=["#c62828","#ad1457","#6a1b9a","#4527a0","#283593","#1565c0","#00838f","#2e7d32","#558b2f","#ef6c00","#6d4c41","#546e7a"];function u(e){let s=(e||"").split(/\s+/).filter(Boolean);return s.length===0?"?":s.length===1?s[0][0].toUpperCase():(s[0][0]+s[s.length-1][0]).toUpperCase()}function c(e){let s=0;for(let n of e)s=s*31+n.charCodeAt(0)>>>0;return a[s%a.length]}function o(e){return e?{id:e.id,name:e.name,initials:e.initials||u(e.name),color:e.color||c(e.id)}:null}function h(e){return e?r`<span class="person-avatar" style="--person-color: ${e.color}" title=${e.name}>${e.initials}</span>`:i}function m(e,s=""){return e?r`<span class="person-chip ${s}" title=${e.name}>${h(e)}<span class="person-name">${e.name}</span></span>`:i}var l=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 n=Date.now();if(!s&&this.usersCache&&n-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=n,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,n,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:n,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}getPerson(s){return o(this.getUser(s))}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(n=>n.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{a,o as b,h as c,m as d,l as e};
@@ -1,2 +0,0 @@
/*! maintenance_supporter frontend 2.74.0 */
import{b as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-EOLJHFO4.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};
@@ -1,2 +0,0 @@
/*! maintenance_supporter frontend 2.74.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,341 +0,0 @@
/*! maintenance_supporter frontend 2.74.0 */
import{a as _,e as y}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YP2KCO26.js";import{a as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NYOACMQR.js";import{K as m,a as s,b,c as o,f as d,h as v,l as n,m as l,q as r,x as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y2JXJRDF.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=k(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=g(t,this.currencySymbol,e);return o`<button
type="button"
class="cost-suggestion"
@click=${()=>this._cost=String(Math.round(t*100)/100)}
>${r("cost_from_parts",e).replace("{amount}",a)}</button>`}_close(){this._open=!1}_pickCompletedAt(){let e=new Date,t=a=>String(a).padStart(2,"0");this._completedAt=`${e.getFullYear()}-${t(e.getMonth()+1)}-${t(e.getDate())}T${t(e.getHours())}:${t(e.getMinutes())}:00`}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en";return o`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div>
${this.phaseLabel?o`<div class="phase-line">${r("phase_current",e)}: ${this.phaseLabel}</div>`:d}
${this.requireTagScan&&!this.viaTagScan?o`<div class="scan-required-note">${r("require_tag_scan_hint",e)}</div>`:d}
<div class="content">
${this._error?o`<div class="error">${this._error}</div>`:d}
${this.checklist.length>0?o`
<div class="checklist-section">
<label class="checklist-label">${r("checklist",e)}</label>
${this.checklist.map((t,a)=>o`
<label class="checklist-item" @click=${()=>this._toggleCheck(a)}>
<input type="checkbox" .checked=${!!this._checklistState[String(a)]} />
<span>${t}</span>
</label>
`)}
</div>
`:d}
${this.taskType==="reading"?o`
<label class="field">
<span class="field-label">${r("reading_value_label",e)}${this.readingUnit?` (${this.readingUnit})`:""}</span>
<input type="number" step="any" class="field-input"
.value=${this._readingValue}
@input=${t=>this._readingValue=t.target.value} />
</label>`:d}
${this.parts.length?o`<div class="used-parts">
<span class="field-label">${r("complete_parts_used",e)}</span>
${this.parts.map(t=>{let a=_({part_id:t.id,entry_id:t.entry_id}),c=this._usedParts[a],p=c!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return o`<div class="used-part-row">
<label class="used-part-check">
<input type="checkbox" .checked=${p}
@change=${f=>{let h={...this._usedParts};f.target.checked?h[a]=h[a]||u:delete h[a],this._usedParts=h}} />
<span
>${t.name}${t.owner_name?o`<span class="used-part-owner"> (${t.owner_name})</span>`:d}${t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:""}</span
>
</label>
${p?o`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(c.quantity)}
@input=${f=>{let h=parseFloat(f.target.value);this._usedParts={...this._usedParts,[a]:{...u,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:d}
</div>`})}
</div>`:this.consumesInfo.length?o`<div class="consumes-hint">
${this.consumesInfo.map(t=>o`<div>${t}</div>`)}
</div>`:d}
${this.restockDefault!==null?o`
<label class="field">
<span class="field-label">${r("restock_quantity_label",e)}</span>
<input type="number" step="0.01" min="0.01" class="field-input"
.value=${this._restockQty}
@input=${t=>this._restockQty=t.target.value} />
</label>`:d}
<!-- Native <input>s rather than <ha-textfield>: when this dialog
is opened from a Lovelace card via dialog-mount, ha-textfield
isn't yet registered (HA loads it lazily when its own panels
need it) so the elements render with zero height and the user
only sees the title + Cancel/Complete buttons — the original
bug report. Native inputs always render. -->
<label class="field">
<span class="field-label">${r("notes_optional",e)}${this._req("notes")}</span>
<input type="text" class="field-input"
.value=${this._notes}
@input=${t=>this._notes=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("cost_optional",e)}${this._req("cost")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._cost}
@input=${t=>this._cost=t.target.value} />
${this._renderCostSuggestion(e)}
</label>
<label class="field">
<span class="field-label">${r("duration_minutes",e)}${this._req("duration")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._duration}
@input=${t=>this._duration=t.target.value} />
</label>
<div class="field">
<span class="field-label">${r("completed_at_optional",e)}</span>
${this._completedAt?o`<ms-date-field
kind="datetime"
clearable
.hass=${this.hass}
.lang=${e}
.value=${this._completedAt}
@value-changed=${t=>this._completedAt=t.detail.value}
></ms-date-field>`:o`<button type="button" class="backdate-pick" @click=${this._pickCompletedAt}>
<ha-icon icon="mdi:calendar-clock"></ha-icon>${r("completed_at_pick",e)}
</button>`}
</div>
<div class="field">
<span class="field-label">${r("completion_photo_optional",e)}${this._req("photo")}</span>
${this._photoPreview?o`
<div class="photo-preview">
<img src=${this._photoPreview} alt="" />
<button type="button" class="photo-remove" @click=${this._removePhoto}
title="${r("remove",e)}">✕</button>
</div>`:o`
<label class="photo-pick">
<ha-icon icon="mdi:camera"></ha-icon>
<span>${this._photoUploading?r("uploading",e):r("add_photo",e)}</span>
<input type="file" accept="image/*" capture="environment"
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>`}
</div>
${this.adaptiveEnabled?o`
<div class="feedback-section">
<label class="feedback-label">${r("was_maintenance_needed",e)}</label>
<div class="feedback-buttons">
<button
class="feedback-btn ${this._feedback==="needed"?"selected":""}"
@click=${()=>this._setFeedback("needed")}
>${r("feedback_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_needed"?"selected":""}"
@click=${()=>this._setFeedback("not_needed")}
>${r("feedback_not_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_sure"?"selected":""}"
@click=${()=>this._setFeedback("not_sure")}
>${r("feedback_not_sure",e)}</button>
</div>
</div>
`:d}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${r("cancel",e)}
</ha-button>
<ha-button
@click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(y[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};i.styles=[m,b`
.req-mark {
color: var(--error-color, #f44336);
margin-left: 2px;
font-weight: 600;
}
/* #104: one-click cost suggestion from parts — quiet link-style chip. */
.cost-suggestion {
align-self: flex-start;
margin-top: 4px;
padding: 0;
border: none;
background: none;
color: var(--primary-color);
font-size: 12.5px;
cursor: pointer;
text-decoration: underline dotted;
text-underline-offset: 2px;
}
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.scan-required-note {
margin: -4px 0 12px;
padding: 8px 10px;
border-radius: 6px;
background: rgba(255, 152, 0, 0.12);
color: var(--primary-text-color);
font-size: 13px;
}
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.consumes-hint {
font-size: 13px;
color: var(--secondary-text-color);
border-left: 3px solid var(--primary-color);
padding: 4px 8px;
margin: 4px 0 8px;
}
/* #99: editable per-completion parts selection */
.used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; }
.used-part-row { display: flex; align-items: center; gap: 8px; }
.used-part-check {
display: flex; align-items: center; gap: 6px; flex: 1;
font-size: 13px; cursor: pointer;
}
.used-part-check input { cursor: pointer; }
/* #111: whose stock this row draws on. Muted but never omitted — an
unlabelled foreign pool is indistinguishable from an own part. */
.used-part-owner { color: var(--secondary-text-color); }
.used-part-qty {
width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px;
border: 1px solid var(--divider-color);
background: var(--card-background-color);
color: var(--primary-text-color);
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
/* .field/.field-label/.field-input come from nativeFieldStyles */
.photo-pick {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px dashed var(--divider-color);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
width: fit-content;
}
.photo-pick:hover { border-color: var(--primary-color); }
/* #163: the backdate moment starts EMPTY (= now); the button seeds the
HA date+time picker with the current minute instead of the picker's
own 00:00 default, so a backdated completion never lands at midnight
by accident. */
.backdate-pick {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px dashed var(--divider-color);
border-radius: 8px;
background: transparent;
cursor: pointer;
font: inherit;
font-size: 13px;
color: var(--secondary-text-color);
width: fit-content;
--mdc-icon-size: 18px;
}
.backdate-pick:hover { border-color: var(--primary-color); }
.photo-pick input[type="file"] { display: none; }
.photo-preview {
position: relative;
width: fit-content;
}
.photo-preview img {
max-width: 160px;
max-height: 160px;
border-radius: 8px;
display: block;
}
.photo-remove {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: var(--error-color, #db4437);
color: #fff;
cursor: pointer;
font-size: 12px;
line-height: 1;
}
.checklist-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid var(--divider-color);
margin-bottom: 4px;
}
.checklist-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
font-size: 14px;
}
.checklist-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.feedback-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-top: 1px solid var(--divider-color);
}
.feedback-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.feedback-buttons {
display: flex;
gap: 8px;
}
.feedback-btn {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.feedback-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
}
.feedback-btn.selected {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
`],s([n({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"entryId",2),s([n()],i.prototype,"taskId",2),s([n()],i.prototype,"taskName",2),s([n()],i.prototype,"lang",2),s([n({type:Array})],i.prototype,"checklist",2),s([n({type:Boolean})],i.prototype,"adaptiveEnabled",2),s([n()],i.prototype,"taskType",2),s([n()],i.prototype,"readingUnit",2),s([n({attribute:!1})],i.prototype,"restockDefault",2),s([n({attribute:!1})],i.prototype,"restockUnitCost",2),s([n()],i.prototype,"currencySymbol",2),s([n({attribute:!1})],i.prototype,"parts",2),s([n({attribute:!1})],i.prototype,"consumesParts",2),s([n({type:Array})],i.prototype,"consumesInfo",2),s([n({type:Array})],i.prototype,"requiredFields",2),s([n()],i.prototype,"phaseLabel",2),s([n({type:Boolean})],i.prototype,"requireTagScan",2),s([n({type:Boolean})],i.prototype,"viaTagScan",2),s([l()],i.prototype,"_open",2),s([l()],i.prototype,"_notes",2),s([l()],i.prototype,"_cost",2),s([l()],i.prototype,"_duration",2),s([l()],i.prototype,"_loading",2),s([l()],i.prototype,"_error",2),s([l()],i.prototype,"_checklistState",2),s([l()],i.prototype,"_feedback",2),s([l()],i.prototype,"_photoDocId",2),s([l()],i.prototype,"_photoPreview",2),s([l()],i.prototype,"_photoUploading",2),s([l()],i.prototype,"_readingValue",2),s([l()],i.prototype,"_restockQty",2),s([l()],i.prototype,"_completedAt",2),s([l()],i.prototype,"_usedParts",2),s([n({attribute:!1})],i.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",i);export{i as a};
@@ -1,2 +0,0 @@
/*! 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};
File diff suppressed because one or more lines are too long
@@ -1,315 +0,0 @@
/*! 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)}
>${r("cost_from_parts",e).replace("{amount}",a)}</button>`}_close(){this._open=!1}render(){if(!this._open)return o``;let e=this.lang||this.hass?.language||"en";return o`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div>
${this.phaseLabel?o`<div class="phase-line">${r("phase_current",e)}: ${this.phaseLabel}</div>`:d}
${this.requireTagScan&&!this.viaTagScan?o`<div class="scan-required-note">${r("require_tag_scan_hint",e)}</div>`:d}
<div class="content">
${this._error?o`<div class="error">${this._error}</div>`:d}
${this.checklist.length>0?o`
<div class="checklist-section">
<label class="checklist-label">${r("checklist",e)}</label>
${this.checklist.map((t,a)=>o`
<label class="checklist-item" @click=${()=>this._toggleCheck(a)}>
<input type="checkbox" .checked=${!!this._checklistState[String(a)]} />
<span>${t}</span>
</label>
`)}
</div>
`:d}
${this.taskType==="reading"?o`
<label class="field">
<span class="field-label">${r("reading_value_label",e)}${this.readingUnit?` (${this.readingUnit})`:""}</span>
<input type="number" step="any" class="field-input"
.value=${this._readingValue}
@input=${t=>this._readingValue=t.target.value} />
</label>`:d}
${this.parts.length?o`<div class="used-parts">
<span class="field-label">${r("complete_parts_used",e)}</span>
${this.parts.map(t=>{let a=_({part_id:t.id,entry_id:t.entry_id}),c=this._usedParts[a],p=c!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return o`<div class="used-part-row">
<label class="used-part-check">
<input type="checkbox" .checked=${p}
@change=${f=>{let h={...this._usedParts};f.target.checked?h[a]=h[a]||u:delete h[a],this._usedParts=h}} />
<span
>${t.name}${t.owner_name?o`<span class="used-part-owner"> (${t.owner_name})</span>`:d}${t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:""}</span
>
</label>
${p?o`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(c.quantity)}
@input=${f=>{let h=parseFloat(f.target.value);this._usedParts={...this._usedParts,[a]:{...u,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:d}
</div>`})}
</div>`:this.consumesInfo.length?o`<div class="consumes-hint">
${this.consumesInfo.map(t=>o`<div>${t}</div>`)}
</div>`:d}
${this.restockDefault!==null?o`
<label class="field">
<span class="field-label">${r("restock_quantity_label",e)}</span>
<input type="number" step="0.01" min="0.01" class="field-input"
.value=${this._restockQty}
@input=${t=>this._restockQty=t.target.value} />
</label>`:d}
<!-- Native <input>s rather than <ha-textfield>: when this dialog
is opened from a Lovelace card via dialog-mount, ha-textfield
isn't yet registered (HA loads it lazily when its own panels
need it) so the elements render with zero height and the user
only sees the title + Cancel/Complete buttons — the original
bug report. Native inputs always render. -->
<label class="field">
<span class="field-label">${r("notes_optional",e)}${this._req("notes")}</span>
<input type="text" class="field-input"
.value=${this._notes}
@input=${t=>this._notes=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("cost_optional",e)}${this._req("cost")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._cost}
@input=${t=>this._cost=t.target.value} />
${this._renderCostSuggestion(e)}
</label>
<label class="field">
<span class="field-label">${r("duration_minutes",e)}${this._req("duration")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._duration}
@input=${t=>this._duration=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("completed_at_optional",e)}</span>
<input type="datetime-local" class="field-input"
max=${new Date(Date.now()-new Date().getTimezoneOffset()*6e4).toISOString().slice(0,16)}
.value=${this._completedAt}
@change=${t=>this._completedAt=t.target.value} />
</label>
<div class="field">
<span class="field-label">${r("completion_photo_optional",e)}${this._req("photo")}</span>
${this._photoPreview?o`
<div class="photo-preview">
<img src=${this._photoPreview} alt="" />
<button type="button" class="photo-remove" @click=${this._removePhoto}
title="${r("remove",e)}">✕</button>
</div>`:o`
<label class="photo-pick">
<ha-icon icon="mdi:camera"></ha-icon>
<span>${this._photoUploading?r("uploading",e):r("add_photo",e)}</span>
<input type="file" accept="image/*" capture="environment"
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>`}
</div>
${this.adaptiveEnabled?o`
<div class="feedback-section">
<label class="feedback-label">${r("was_maintenance_needed",e)}</label>
<div class="feedback-buttons">
<button
class="feedback-btn ${this._feedback==="needed"?"selected":""}"
@click=${()=>this._setFeedback("needed")}
>${r("feedback_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_needed"?"selected":""}"
@click=${()=>this._setFeedback("not_needed")}
>${r("feedback_not_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_sure"?"selected":""}"
@click=${()=>this._setFeedback("not_sure")}
>${r("feedback_not_sure",e)}</button>
</div>
</div>
`:d}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${r("cancel",e)}
</ha-button>
<ha-button
@click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(k[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};i.styles=[g,b`
.req-mark {
color: var(--error-color, #f44336);
margin-left: 2px;
font-weight: 600;
}
/* #104: one-click cost suggestion from parts — quiet link-style chip. */
.cost-suggestion {
align-self: flex-start;
margin-top: 4px;
padding: 0;
border: none;
background: none;
color: var(--primary-color);
font-size: 12.5px;
cursor: pointer;
text-decoration: underline dotted;
text-underline-offset: 2px;
}
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.scan-required-note {
margin: -4px 0 12px;
padding: 8px 10px;
border-radius: 6px;
background: rgba(255, 152, 0, 0.12);
color: var(--primary-text-color);
font-size: 13px;
}
.phase-line {
margin-top: -8px;
padding-bottom: 12px;
font-size: 13px;
color: var(--secondary-text-color);
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.consumes-hint {
font-size: 13px;
color: var(--secondary-text-color);
border-left: 3px solid var(--primary-color);
padding: 4px 8px;
margin: 4px 0 8px;
}
/* #99: editable per-completion parts selection */
.used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; }
.used-part-row { display: flex; align-items: center; gap: 8px; }
.used-part-check {
display: flex; align-items: center; gap: 6px; flex: 1;
font-size: 13px; cursor: pointer;
}
.used-part-check input { cursor: pointer; }
/* #111: whose stock this row draws on. Muted but never omitted — an
unlabelled foreign pool is indistinguishable from an own part. */
.used-part-owner { color: var(--secondary-text-color); }
.used-part-qty {
width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px;
border: 1px solid var(--divider-color);
background: var(--card-background-color);
color: var(--primary-text-color);
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
/* .field/.field-label/.field-input come from nativeFieldStyles */
.photo-pick {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px dashed var(--divider-color);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
width: fit-content;
}
.photo-pick:hover { border-color: var(--primary-color); }
.photo-pick input[type="file"] { display: none; }
.photo-preview {
position: relative;
width: fit-content;
}
.photo-preview img {
max-width: 160px;
max-height: 160px;
border-radius: 8px;
display: block;
}
.photo-remove {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: var(--error-color, #db4437);
color: #fff;
cursor: pointer;
font-size: 12px;
line-height: 1;
}
.checklist-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid var(--divider-color);
margin-bottom: 4px;
}
.checklist-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
font-size: 14px;
}
.checklist-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.feedback-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-top: 1px solid var(--divider-color);
}
.feedback-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.feedback-buttons {
display: flex;
gap: 8px;
}
.feedback-btn {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.feedback-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
}
.feedback-btn.selected {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
`],s([n({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"entryId",2),s([n()],i.prototype,"taskId",2),s([n()],i.prototype,"taskName",2),s([n()],i.prototype,"lang",2),s([n({type:Array})],i.prototype,"checklist",2),s([n({type:Boolean})],i.prototype,"adaptiveEnabled",2),s([n()],i.prototype,"taskType",2),s([n()],i.prototype,"readingUnit",2),s([n({attribute:!1})],i.prototype,"restockDefault",2),s([n({attribute:!1})],i.prototype,"restockUnitCost",2),s([n()],i.prototype,"currencySymbol",2),s([n({attribute:!1})],i.prototype,"parts",2),s([n({attribute:!1})],i.prototype,"consumesParts",2),s([n({type:Array})],i.prototype,"consumesInfo",2),s([n({type:Array})],i.prototype,"requiredFields",2),s([n()],i.prototype,"phaseLabel",2),s([n({type:Boolean})],i.prototype,"requireTagScan",2),s([n({type:Boolean})],i.prototype,"viaTagScan",2),s([l()],i.prototype,"_open",2),s([l()],i.prototype,"_notes",2),s([l()],i.prototype,"_cost",2),s([l()],i.prototype,"_duration",2),s([l()],i.prototype,"_loading",2),s([l()],i.prototype,"_error",2),s([l()],i.prototype,"_checklistState",2),s([l()],i.prototype,"_feedback",2),s([l()],i.prototype,"_photoDocId",2),s([l()],i.prototype,"_photoPreview",2),s([l()],i.prototype,"_photoUploading",2),s([l()],i.prototype,"_readingValue",2),s([l()],i.prototype,"_restockQty",2),s([l()],i.prototype,"_completedAt",2),s([l()],i.prototype,"_usedParts",2),s([n({attribute:!1})],i.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",i);export{i as a};
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
/*! maintenance_supporter frontend 2.82.0 */
var l=[{key:"name",labelKey:"name",required:!0},{key:"ref_no",labelKey:"ref_number"},{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"}],i=l.map(a=>a.key),r=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function o(a){if(!Array.isArray(a))return[...r];let t=new Set,e=[];for(let n of a)typeof n=="string"&&i.includes(n)&&!t.has(n)&&(t.add(n),e.push(n));return e.length?(e.includes("name")||e.unshift("name"),e):[...r]}export{l as a,r as b,o as c};
@@ -0,0 +1,88 @@
/*! maintenance_supporter frontend 2.82.0 */
import{a,b as y,c as s,f as l,h as g,l as o,m as f,q as d,z as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4OPY4VQ5.js";function $(n){let t=m("2001-02-03",n).match(/\d+/g)||[],e=v=>t.findIndex(_=>Number(_)===v),h=e(2001),c=e(2),u=e(3);return h===0?"YMD":c>=0&&u>=0&&c<u?"MDY":"DMY"}function p(n,i,t){if(n<1e3||n>9999||i<1||i>12||t<1||t>31)return null;let e=new Date(n,i-1,t);return e.getFullYear()!==n||e.getMonth()!==i-1||e.getDate()!==t?null:`${n}-${String(i).padStart(2,"0")}-${String(t).padStart(2,"0")}`}function b(n,i){let t=n.trim(),e;if(e=t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return p(+e[1],+e[2],+e[3]);if(e=t.match(/^(\d{4})$/))return p(+e[1],1,1);if(e=t.match(/^(\d{1,2})[./](\d{4})$/))return p(+e[2],+e[1],1);if(e=t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return p(+e[3],+e[2],+e[1]);if(e=t.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let h=+e[1],c=+e[2],u=+e[3];return $(i)==="MDY"?p(u,h,c):p(u,c,h)}return null}function x(n,i){if(i)switch(n){case"date":return i.split("T")[0];case"time":return i.length===5?`${i}:00`:i;case"datetime":{let[t,e="00:00:00"]=i.split("T");return`${t} ${e.length===5?`${e}:00`:e}`}}}function k(n,i){if(typeof i!="string"||!i)return"";switch(n){case"date":return i.slice(0,10);case"time":return i.slice(0,5);case"datetime":{let[t,e="00:00:00"]=i.split(" ");return`${t}T${e.length===5?`${e}:00`:e}`}}}var r=class extends g{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let t=b(this._typed,this.lang);if(!t){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(t)}_onTypedKey(t){t.key==="Enter"?(t.preventDefault(),this._commitTyped()):t.key==="Escape"&&(t.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(t){t.stopPropagation(),this._emit(k(this.kind,t.detail?.value))}_clear(){this._emit("")}_emit(t){t!==this.value&&(this.value=t,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:t}})))}render(){return s`
<div class="field">
${this.label?s`<span class="label">${this.label}${this.required?s`<span class="req">*</span>`:l}</span>`:l}
<div class="row">
${this._typing?s`<input class="typed" type="text" inputmode="numeric" autocomplete="off"
.value=${this._typed}
placeholder=${m("1978-03-15",this.lang)}
aria-invalid=${this._typedInvalid?"true":"false"}
@input=${t=>{this._typed=t.target.value,this._typedInvalid=!1}}
@keydown=${this._onTypedKey}
@blur=${this._commitTyped} />`:s`<ha-selector
.hass=${this.hass}
.selector=${this._selector()}
.value=${x(this.kind,this.value)}
.required=${this.required}
.disabled=${this.disabled}
@value-changed=${this._onSelectorChange}
></ha-selector>`}
${this.kind==="date"&&!this.disabled&&!this._typing?s`<button type="button" class="type-toggle" title=${d("date_type_toggle",this.lang)} aria-label=${d("date_type_toggle",this.lang)} @click=${this._startTyping}>
<ha-icon icon="mdi:keyboard-outline"></ha-icon>
</button>`:l}
${this.clearable&&this.value&&!this.disabled&&!this._typing?s`<button type="button" class="clear" title=${d("clear",this.lang)} aria-label=${d("clear",this.lang)} @click=${this._clear}>
<ha-icon icon="mdi:close"></ha-icon>
</button>`:l}
</div>
${this._typedInvalid?s`<span class="helper invalid">${d("date_type_invalid",this.lang)}</span>`:this.helper?s`<span class="helper">${this.helper}</span>`:l}
</div>
`}};r.styles=y`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
.row {
display: flex;
align-items: center;
gap: 4px;
}
ha-selector {
flex: 1;
min-width: 0;
}
.clear, .type-toggle {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 20px;
}
.clear:hover, .type-toggle:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); }
.clear:focus-visible, .type-toggle:focus-visible { outline: 2px solid var(--primary-color); }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
.helper.invalid { color: var(--error-color, #f44336); font-style: normal; }
input.typed {
flex: 1;
min-width: 0;
height: 40px;
box-sizing: border-box;
padding: 0 12px;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font: inherit;
}
input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); }
input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; }
`,a([o({attribute:!1})],r.prototype,"hass",2),a([o()],r.prototype,"kind",2),a([o()],r.prototype,"label",2),a([o()],r.prototype,"value",2),a([o()],r.prototype,"helper",2),a([o({type:Boolean})],r.prototype,"clearable",2),a([o({type:Boolean})],r.prototype,"disabled",2),a([o({type:Boolean})],r.prototype,"required",2),a([o()],r.prototype,"lang",2),a([f()],r.prototype,"_typing",2),a([f()],r.prototype,"_typed",2),a([f()],r.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",r);
@@ -1,2 +0,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,153 +0,0 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import{a as r,b as _,c as l,f as o,h,l as d,m as s,q as i,s as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";var a=class extends h{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 p(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-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${e}
label="${i("installation_date_optional",e)}"
.value=${this._installationDate}
@value-changed=${t=>this._installationDate=t.detail.value}
></ms-date-field>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${e}
label="${i("warranty_expiry_optional",e)}"
.value=${this._warrantyExpiry}
@value-changed=${t=>this._warrantyExpiry=t.detail.value}
></ms-date-field>
<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};
@@ -1,2 +0,0 @@
/*! 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};
@@ -1,213 +0,0 @@
/*! 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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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>
/* Printable sheet \u2014 must not inherit the phone's dark theme. The QR images
carry their own white quiet zone and stay scannable either way, but the
labels below are explicit dark greys and would vanish on a WebView's dark
canvas. Same reasoning as helpers/report.ts. */
:root{color-scheme:light}
body{font-family:sans-serif;text-align:center;padding:20px;background:#fff;color:#1a1a1a}
h2{margin:0 0 4px}
.sub{color:#666;font-size:14px;margin-bottom:16px}
.qr-row{display:flex;justify-content:center;gap:24px;margin:12px 0}
.qr-col{display:flex;flex-direction:column;align-items:center;gap:6px}
.qr-col img{width:${_?"200px":"280px"}}
.qr-label{font-size:13px;font-weight:500;color:#333}
.url{font-size:10px;color:#999;word-break:break-all;margin-top:8px;max-width:480px}
</style></head><body>
<h2>${u}</h2>
${m?`<div class="sub">${m}</div>`:""}
<div class="qr-row">
<div class="qr-col">
<img src="${f(this._viewResult.svg_data_uri)}" alt="QR Info" />
<div class="qr-label">${w}</div>
</div>
${_?`<div class="qr-col">
<img src="${f(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${$}</div>
</div>`:""}
</div>
<div class="url">${d(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
</body></html>`),s.document.close()}_downloadSvg(e,i){let r=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),s=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;x(r,`qr-${q(s)}-${i}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return n``;let e=this.lang||this.hass?.language||"en",i=this._taskName?`${t("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${t("qr_code",e)}: ${this._objectName}`,r=!!this._viewResult;return n`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${i}</div>
<div class="content">
${this._loading?n`<div class="loading">${t("qr_generating",e)}</div>`:this._error?n`<div class="error">${this._error}</div>`:r?n`
<div class="qr-pair">
<div class="qr-item">
<img
class="qr-image ${this._completeResult?"small":""}"
src="${this._viewResult.svg_data_uri}"
alt="QR Info"
/>
<div class="qr-item-label">${t("qr_action_view",e)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._viewResult,"info")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download",e)}
</button>
</div>
${this._completeResult?n`
<div class="qr-item">
<img
class="qr-image small"
src="${this._completeResult.svg_data_uri}"
alt="QR Complete"
/>
<div class="qr-item-label">${t("qr_action_complete",e)}</div>
<button class="dl-btn"
@click=${()=>this._downloadSvg(this._completeResult,"complete")}>
<ha-icon icon="mdi:download"></ha-icon>
${t("qr_download",e)}
</button>
</div>
`:p}
</div>
<div class="url-display">${this._viewResult.url}</div>
`:p}
<div class="action-row">
<label>${t("qr_url_mode",e)}</label>
<div class="action-toggle">
<button class="toggle-btn ${this._urlMode==="companion"?"active":""}"
@click=${()=>this._setUrlMode("companion")}>${t("qr_mode_companion",e)}</button>
<button class="toggle-btn ${this._urlMode==="local"?"active":""}"
@click=${()=>this._setUrlMode("local")}>${t("qr_mode_local",e)}</button>
<button class="toggle-btn ${this._urlMode==="server"?"active":""}"
@click=${()=>this._setUrlMode("server")}>${t("qr_mode_server",e)}</button>
</div>
</div>
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${t("cancel",e)}
</ha-button>
<ha-button
@click=${this._print}
.disabled=${!r}
>
${t("qr_print",e)}
</ha-button>
</div>
</ha-dialog>
`}};o.styles=v`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.qr-pair {
display: flex;
gap: 20px;
justify-content: center;
width: 100%;
}
.qr-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
}
.qr-image {
width: 240px;
height: 240px;
image-rendering: pixelated;
}
.qr-image.small {
width: 180px;
height: 180px;
}
.qr-item-label {
font-size: 12px;
font-weight: 500;
color: var(--secondary-text-color);
text-align: center;
}
.dl-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: none;
border: 1px solid var(--divider-color, #e0e0e0);
cursor: pointer;
font-size: 13px;
color: var(--primary-text-color);
padding: 6px 14px;
border-radius: 18px;
transition: background 0.2s, border-color 0.2s;
}
.dl-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
border-color: var(--primary-color);
}
.dl-btn ha-icon {
--mdc-icon-size: 18px;
}
.url-display {
font-size: 11px;
color: var(--secondary-text-color);
word-break: break-all;
text-align: center;
max-width: 400px;
}
.loading {
padding: 40px 0;
color: var(--secondary-text-color);
}
.error {
padding: 20px 0;
color: var(--error-color, #f44336);
}
.action-row {
display: flex;
flex-direction: column;
gap: 6px;
width: 100%;
}
.action-row label {
font-size: 13px;
color: var(--secondary-text-color);
}
.action-toggle {
display: flex;
gap: 4px;
background: var(--divider-color, #e0e0e0);
border-radius: 6px;
padding: 3px;
}
.toggle-btn {
flex: 1;
padding: 8px 12px;
border: none;
background: transparent;
color: var(--primary-text-color);
cursor: pointer;
border-radius: 4px;
font-size: 13px;
transition: all 0.2s;
line-height: 1.3;
}
.toggle-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.toggle-btn.active {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
`,a([h({attribute:!1})],o.prototype,"hass",2),a([h()],o.prototype,"lang",2),a([c()],o.prototype,"_open",2),a([c()],o.prototype,"_loading",2),a([c()],o.prototype,"_error",2),a([c()],o.prototype,"_viewResult",2),a([c()],o.prototype,"_completeResult",2),a([c()],o.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",o);export{o as a};
@@ -1,5 +1,5 @@
/*! maintenance_supporter frontend 2.74.0 */
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-EOLJHFO4.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-Y2JXJRDF.js";function d(l){return l.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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.82.0 */
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZSJQYRHU.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-4OPY4VQ5.js";function d(l){return l.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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>
@@ -1,2 +0,0 @@
/*! maintenance_supporter frontend 2.74.0 */
import{q as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y2JXJRDF.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};
@@ -1,2 +0,0 @@
/*! maintenance_supporter frontend 2.80.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};
File diff suppressed because one or more lines are too long
@@ -1,79 +0,0 @@
/*! maintenance_supporter frontend 2.74.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-Y2JXJRDF.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`
<textarea
.value=${this.value??""}
rows=${this.rows}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
@input=${this._onInput}
@change=${this._onInput}
></textarea>`:i`
<input
.value=${this.value??""}
.type=${this.type}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
step=${this.step??o}
min=${this.min??o}
max=${this.max??o}
pattern=${this.pattern??o}
@input=${this._onInput}
@change=${this._onInput}
/>`}
${this.helper?i`<span class="helper">${this.helper}</span>`:o}
</label>
`}};e.styles=l`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
input {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
}
input:focus {
border-color: var(--primary-color);
}
input:disabled { opacity: 0.5; cursor: not-allowed; }
textarea {
padding: 8px 10px;
font-size: 14px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
color: var(--primary-text-color);
border: 1px solid var(--divider-color, rgba(255,255,255,0.12));
border-radius: 6px;
font-family: inherit;
width: 100%;
box-sizing: border-box;
outline: none;
resize: vertical;
}
textarea:focus { border-color: var(--primary-color); }
textarea:disabled { opacity: 0.5; cursor: not-allowed; }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,r([t()],e.prototype,"label",2),r([t()],e.prototype,"value",2),r([t()],e.prototype,"placeholder",2),r([t()],e.prototype,"type",2),r([t({type:Boolean})],e.prototype,"required",2),r([t({type:Boolean})],e.prototype,"disabled",2),r([t()],e.prototype,"step",2),r([t()],e.prototype,"min",2),r([t()],e.prototype,"max",2),r([t()],e.prototype,"pattern",2),r([t()],e.prototype,"helper",2),r([t({type:Boolean})],e.prototype,"multiline",2),r([t({type:Number})],e.prototype,"rows",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e);
File diff suppressed because one or more lines are too long
@@ -1,2 +0,0 @@
/*! maintenance_supporter frontend 2.74.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};
File diff suppressed because one or more lines are too long
@@ -1,147 +0,0 @@
/*! maintenance_supporter frontend 2.71.0 */
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-GJI3RTUI.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-PBRIQQ2X.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};
@@ -1,5 +1,5 @@
/*! maintenance_supporter frontend 2.80.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-NAI3MD3R.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.82.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-4OPY4VQ5.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,64 +0,0 @@
/*! maintenance_supporter frontend 2.74.0 */
import{a as r,b as d,c as a,f as l,h as p,l as s,q as c}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y2JXJRDF.js";function h(o,e){if(e)switch(o){case"date":return e.split("T")[0];case"time":return e.length===5?`${e}:00`:e;case"datetime":{let[i,n="00:00:00"]=e.split("T");return`${i} ${n.length===5?`${n}:00`:n}`}}}function u(o,e){if(typeof e!="string"||!e)return"";switch(o){case"date":return e.slice(0,10);case"time":return e.slice(0,5);case"datetime":{let[i,n="00:00:00"]=e.split(" ");return`${i}T${n.length===5?`${n}:00`:n}`}}}var t=class extends p{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en"}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(i){i.stopPropagation(),this._emit(u(this.kind,i.detail?.value))}_clear(){this._emit("")}_emit(i){i!==this.value&&(this.value=i,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:i}})))}render(){return a`
<div class="field">
${this.label?a`<span class="label">${this.label}${this.required?a`<span class="req">*</span>`:l}</span>`:l}
<div class="row">
<ha-selector
.hass=${this.hass}
.selector=${this._selector()}
.value=${h(this.kind,this.value)}
.required=${this.required}
.disabled=${this.disabled}
@value-changed=${this._onSelectorChange}
></ha-selector>
${this.clearable&&this.value&&!this.disabled?a`<button type="button" class="clear" title=${c("clear",this.lang)} aria-label=${c("clear",this.lang)} @click=${this._clear}>
<ha-icon icon="mdi:close"></ha-icon>
</button>`:l}
</div>
${this.helper?a`<span class="helper">${this.helper}</span>`:l}
</div>
`}};t.styles=d`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
.row {
display: flex;
align-items: center;
gap: 4px;
}
ha-selector {
flex: 1;
min-width: 0;
}
.clear {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 20px;
}
.clear:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); }
.clear:focus-visible { outline: 2px solid var(--primary-color); }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,r([s({attribute:!1})],t.prototype,"hass",2),r([s()],t.prototype,"kind",2),r([s()],t.prototype,"label",2),r([s()],t.prototype,"value",2),r([s()],t.prototype,"helper",2),r([s({type:Boolean})],t.prototype,"clearable",2),r([s({type:Boolean})],t.prototype,"disabled",2),r([s({type:Boolean})],t.prototype,"required",2),r([s()],t.prototype,"lang",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",t);
@@ -1,2 +0,0 @@
/*! maintenance_supporter frontend 2.71.0 */
import{q as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-PBRIQQ2X.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};

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