189 files

This commit is contained in:
Home Assistant Version Control committed 2026-09-08 18:05:32 +00:00
1 parent 79fefc0a7c
commit 303886f212
189 files changed
+21402 -3514

No files matched your search

@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: neplatná hodnota",
"feat_schedule_time": "Plánování podle denní doby",
"feat_schedule_time_desc": "Úkoly se stanou po termínu v určenou denní dobu místo o půlnoci.",
"schedule_time_optional": "Termín v čase (volitelné)",
"schedule_time_toggle": "Termín v určitou hodinu",
"schedule_time_help": "Prázdné = půlnoc (výchozí). Časové pásmo HA.",
"at_time": "v",
"notes_optional": "Poznámky (volitelné)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Žádný tag —",
"dashboard": "Přehled",
"tab_today": "Dnes",
"palette_placeholder": "Hledat objekty a úkoly…",
"palette_placeholder": "Hledat objekty, úkoly, díly, dokumenty, poznámky…",
"palette_no_results": "Žádné výsledky",
"palette_hint": "↑↓ pohyb · Enter otevřít · Esc zavřít",
"palette_hint": "↑↓ navigace · Enter otevřít · Esc zavřít · / otevře hledání kdekoli",
"search_open": "Hledat",
"search_group_parts": "Náhradní díly",
"search_group_content": "V dokumentech",
"search_group_history": "Poznámky v historii",
"search_page": "Strana {page}",
"search_empty_hint": "Pište pro hledání v objektech, úkolech, dílech, dokumentech a poznámkách",
"search_searching": "Prohledávání dokumentů a poznámek…",
"search_index_status": "Fulltextové hledání: {indexed} z {total} souborů indexováno · {no_text} bez textové vrstvy · {pending} čeká",
"today_all_caught_up": "Vše hotovo! Tento týden nic nezbývá.",
"today_overdue": "Po termínu",
"today_due_today": "Dnes",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Práh „spotřební materiál nízký“ (%)",
"settings_battery_low_percent": "Práh „baterie slabá“ (%)",
"settings_thresholds_hint": "Výchozí hodnoty pro nová Navrhovaná nastavení (zbývající procenta) a pro baterie flotily bez vlastního prahu Battery Notes. Stávající úkoly si ponechají svou hodnotu.",
"settings_value_out_of_range": "Hodnota musí být mezi {min} a {max}",
"bn_summary": "{name}: {pct} % pro {n} baterií",
"bn_floor_decides": "Tvůj práh {floor} % rozhoduje pro všechny; u každé baterie platí vyšší z obou prahů.",
"bn_above_floor": "To je nad tvým prahem {floor} %, takže o všech poznamenaných bateriích rozhoduje {name}.",
@@ -479,6 +488,11 @@
"test_notification_success": "Testovací oznámení odesláno",
"test_notification_failed": "Testovací oznámení se nezdařilo",
"notify_per_person": "Doručování podle osoby",
"member_avatars": "Avatary členů",
"member_avatars_hint": "Zobrazuje se u přiřazených úkolů — iniciály v barvě člena. Výchozí hodnota vychází ze jména; nastavte vlastní iniciály nebo vyberte barvu pro rozlišení členů.",
"member_initials": "Iniciály",
"member_color": "Barva",
"member_avatar_reset": "Obnovit",
"notify_no_own_device": "Žádné vlastní zařízení — použije domácí službu",
"settings_notify_due_soon": "Oznámit když brzy",
"settings_notify_overdue": "Oznámit když po termínu",
@@ -538,6 +552,7 @@
"group_by_label": "Seskupit podle",
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
"target_changes_help": "Počet odpovídajících přechodů, po kterých se trigger spustí (výchozí: 1).",
"state_latch_help": "Při 1 přechodu je spouštěč západka: obnoví se, když entita opustí cílový stav — nebo, je-li nastaven jen výchozí stav, když se do něj vrátí.",
"for_minutes_state_help": "0 počítá každou změnu ihned. S minutami musí nový stav nejprve vydržet — krátké bliknutí pak nespustí ani se nepočítá.",
"qr_print_title": "Tisk QR kódů",
"qr_print_desc": "Vygeneruj tiskovou stránku s QR kódy k vystřižení a nalepení na zařízení.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Smazat „{name}“?",
"doc_too_large": "Soubor je příliš velký (max. 25 MB).",
"doc_upload_failed": "Nahrání selhalo.",
"completion_photo_optional": "Fotka dokončení (volitelné)",
"add_photo": "Přidat fotku",
"completion_photos_optional": "Fotky z dokončení (volitelné)",
"completion_photos": "Fotky z dokončení",
"choose_photos": "Vybrat fotky",
"choose_photo": "Vybrat fotku",
"photos_android_hint": "Aplikace pro Android předá jednu fotku na výběr — každý výběr se přidá.",
"add_photos": "Přidat fotky",
"photos_limit": "Nejvýše {max} fotek na dokončení",
"history_edit_photos_hint": "Zde odebraná fotka zůstává v dokumentech objektu.",
"uploading": "Nahrávání…",
"remove": "Odebrat",
"doc_deduped": "Již uloženo jinde — sdíleno, bez dalšího místa.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Zobrazí se vedle zaznamenané hodnoty při dokončení tohoto úkolu.",
"reading_value_label": "Hodnota odečtu",
"reading_label": "Odečet",
"readings_section": "Odečty",
"readings_hint": "Několik pojmenovaných hodnot na dokončení — jeden řádek na měřič. Nechte prázdné pro jedinou hodnotu.",
"reading_name_label": "Název odečtu",
"reading_unit_short": "Jednotka",
"reading_add": "Přidat odečet",
"reading_duplicate_name": "Název je již použit",
"reading_last": "naposledy: {value}",
"reading_below_last": "Nižší než poslední odečet ({value})",
"settings_templates_label": "Galerie šablon",
"settings_templates_hint": "Odškrtněte šablony, které nikdy nevyužijete — zmizí z výběru „Ze šablony\" (panel i config flow). Nic jiného se nemění; kdykoli je lze znovu zapnout.",
"worksheet": "Pracovní list",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Upozorňovat jen pro zobrazení",
"settings_notify_scope_all": "Všechny úkoly",
"settings_notify_scope_hint": "Připomenutí posílají jen úkoly odpovídající filtrům štítku/uživatele zvoleného zobrazení. Stav, řazení a seskupení zobrazení se zde ignorují.",
"settings_notify_rule": "Vlastní pravidlo oznámení",
"settings_notify_rule_hint": "Každé oznámení navíc vyvolá událost maintenance_supporter_notification (druh, stav, objekt, úkol, referenční čísla, priorita, odkaz, data). Nasměrujte ji automatizací — např. do Tickeru, Telegramu nebo Pushoveru.",
"settings_notify_event_only": "Jen událost, nic neposílat",
"settings_notify_event_only_hint": "Doručení zajišťuje vaše automatizace. Intervaly opakování, tiché hodiny a denní limit dál rozhodují, kdy se událost vyvolá.",
"settings_notify_extra_data": "Další data oznámení (šablona)",
"settings_notify_extra_data_hint": "JSON nebo YAML, Jinja povoleno; sloučí se do data volání notify (vaše klíče mají přednost). Proměnné: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ověřte tlačítkem Odeslat test.",
"card_saved_view": "Uložené zobrazení",
"card_saved_view_none": "Žádné",
"card_saved_view_help": "Použije filtry stavu, uživatele a štítku zobrazení navíc k výše uvedeným filtrům. Řazení a seskupení zobrazení jsou nastavení panelu a na kartě se nepoužijí.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Označit vše jako vyměněné",
"battery_fleet_mark_one": "Označit tuto baterii jako vyměněnou",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Bez senzoru",
"battery_fleet_trigger_lost": "Senzorový spouštěč této úlohy byl ztracen — nespustí se ani se automaticky nedokončí.",
"battery_fleet_repair": "Opravit",
"battery_fleet_exclude": "Vyloučit z flotily",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Vyberte senzor baterie, který automatická detekce přehlédla — okamžitě se objeví v seznamu.",
"battery_fleet_track_self": "Sledovat samodobíjecí baterie",
"battery_fleet_track_self_hint": "Telefony, vysavače a další samodobíjecí zařízení se zobrazují jako akumulátory — při nízkém stavu žádají o nabití, nikdy o nové články.",
"battery_fleet_due_without_sensor": "Prošlou předpověď považovat za termín výměny, pokud baterie nemá senzor",
"battery_fleet_due_without_sensor_hint": "Některá zařízení vůbec nehlásí stav baterie — Battery Notes zná jen typ a poslední výměnu. Po uplynutí typické životnosti se taková baterie počítá jako vybitá a úkol flotily vás upozorní; označení jako vyměněné předpověď vynuluje.",
"battery_fleet_status_low": "Nízká",
"battery_fleet_status_soon": "Brzy",
"battery_fleet_status_ok": "V pořádku",
"battery_fleet_status_due": "K výměně",
"battery_fleet_predicted_on": "Očekáváno kolem {date}",
"battery_fleet_predicted_trend": "Předpověď z trendu vybíjení této baterie: přibližně {date} ({confidence})",
"battery_fleet_rechargeable": "Akumulátor: nabíjí se místo výměny — nikdy na nákupním seznamu",
@@ -917,6 +956,24 @@
"service_record_print": "Servisní knížka (PDF)",
"date": "Datum",
"service_record_entries": "záznamů",
"ref_number": "Referenční číslo",
"print_options_title": "Tisk servisní knížky",
"print_layout": "Rozvržení",
"print_layout_chronological": "Chronologicky",
"print_layout_by_task": "Podle úkolu",
"print_include": "Zahrnout",
"print_inc_readings": "Odečty",
"print_inc_parts": "Použité díly",
"print_inc_photos": "Fotky",
"print_inc_documents": "Propojené dokumenty",
"print_inc_checklist": "Kontrolní seznam",
"print_inc_notes": "Poznámky",
"print_inc_costs": "Náklady a doba",
"print_inc_person": "Provedl",
"print_inc_refs": "Referenční čísla",
"print_inc_qr": "QR kód pro každý úkol",
"print_button": "Tisk",
"report_scan_hint": "Naskenujte pro otevření úkolu",
"completed_by": "Dokončil",
"date_from": "Od",
"date_to": "Do",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: ugyldig værdi",
"feat_schedule_time": "Planlægning efter tidspunkt på dagen",
"feat_schedule_time_desc": "Opgaver bliver forfaldne på et bestemt tidspunkt på dagen i stedet for midnat.",
"schedule_time_optional": "Forfalder kl. (valgfrit)",
"schedule_time_toggle": "Forfalder på et bestemt tidspunkt",
"schedule_time_help": "Tom = midnat (standard). HA-tidszone.",
"at_time": "kl.",
"notes_optional": "Noter (valgfrit)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Intet tag —",
"dashboard": "Oversigt",
"tab_today": "I dag",
"palette_placeholder": "Søg objekter og opgaver…",
"palette_placeholder": "Søg objekter, opgaver, reservedele, dokumenter, noter…",
"palette_no_results": "Ingen match",
"palette_hint": "↑↓ naviger · Enter åbn · Esc luk",
"palette_hint": "↑↓ naviger · Enter åbn · Esc luk · / åbner søgningen overalt",
"search_open": "Søg",
"search_group_parts": "Reservedele",
"search_group_content": "I dokumenter",
"search_group_history": "Historiknoter",
"search_page": "Side {page}",
"search_empty_hint": "Skriv for at søge i objekter, opgaver, reservedele, dokumenter og noter",
"search_searching": "Søger i dokumenter og noter…",
"search_index_status": "Fuldtekstsøgning: {indexed} af {total} filer indekseret · {no_text} uden tekstlag · {pending} afventer",
"today_all_caught_up": "Alt er klaret! Intet forfalder i denne uge.",
"today_overdue": "Forfaldne",
"today_due_today": "Forfalder i dag",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "Tærskel „forbrugsvare lav“ (%)",
"settings_battery_low_percent": "Tærskel „batteri lavt“ (%)",
"settings_thresholds_hint": "Standardværdier for nye Foreslåede opsætninger (resterende procent) og for flådebatterier uden egen Battery Notes-tærskel. Eksisterende opgaver beholder deres værdi.",
"settings_value_out_of_range": "Værdien skal være mellem {min} og {max}",
"bn_summary": "{name}: {pct} % for {n} batterier",
"bn_floor_decides": "Din tærskel på {floor} % afgør for alle; pr. batteri gælder den højeste af de to tærskler.",
"bn_above_floor": "Det er over din tærskel på {floor} %, så {name} afgør for alle noterede batterier.",
@@ -481,6 +490,11 @@
"test_notification_success": "Testnotifikation sendt",
"test_notification_failed": "Testnotifikation mislykkedes",
"notify_per_person": "Levering pr. person",
"member_avatars": "Medlemmernes avatarer",
"member_avatars_hint": "Vises ved siden af tildelte opgaver — initialer i medlemmets farve. Standard kommer fra navnet; angiv egne initialer eller vælg en farve for at skelne medlemmer.",
"member_initials": "Initialer",
"member_color": "Farve",
"member_avatar_reset": "Nulstil",
"notify_no_own_device": "Ingen egen enhed — bruger husstandens tjeneste",
"settings_notify_due_soon": "Notificer ved snart forfalden",
"settings_notify_overdue": "Notificer ved forfalden",
@@ -540,6 +554,7 @@
"group_by_label": "Grupper efter",
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
"target_changes_help": "Antal matchende overgange, før udløseren aktiveres (standard: 1).",
"state_latch_help": "Med 1 overgang er triggeren en lås: den nulstilles, når enheden forlader Til-tilstanden — eller, hvis kun en Fra-tilstand er sat, når den vender tilbage til den.",
"for_minutes_state_help": "0 tæller hver ændring straks. Med minutter skal den nye tilstand først holde — korte blink hverken udløser eller tælles så.",
"qr_print_title": "Udskriv QR-koder",
"qr_print_desc": "Generer en udskriftsklar side med QR-koder, der kan klippes ud og sættes på dit udstyr.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Slet \"{name}\"?",
"doc_too_large": "Filen er for stor (maks. 25 MB).",
"doc_upload_failed": "Upload mislykkedes.",
"completion_photo_optional": "Fuldførelsesfoto (valgfrit)",
"add_photo": "Tilføj foto",
"completion_photos_optional": "Fotos ved fuldførelse (valgfrit)",
"completion_photos": "Fotos ved fuldførelse",
"choose_photos": "Vælg fotos",
"choose_photo": "Vælg foto",
"photos_android_hint": "Android-appen afleverer ét foto pr. valg — hvert valg tilføjes.",
"add_photos": "Tilføj fotos",
"photos_limit": "Højst {max} fotos pr. fuldførelse",
"history_edit_photos_hint": "Et foto, der fjernes her, bliver i objektets dokumenter.",
"uploading": "Uploader…",
"remove": "Fjern",
"doc_deduped": "Allerede gemt et andet sted — delt, ingen ekstra plads.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Vises ved siden af den registrerede værdi, når opgaven fuldføres.",
"reading_value_label": "Aflæst værdi",
"reading_label": "Aflæsning",
"readings_section": "Aflæsninger",
"readings_hint": "Flere navngivne værdier pr. fuldførelse — én række pr. måler. Lad stå tomt for en enkelt værdi.",
"reading_name_label": "Aflæsningens navn",
"reading_unit_short": "Enhed",
"reading_add": "Tilføj aflæsning",
"reading_duplicate_name": "Navnet er allerede i brug",
"reading_last": "sidst: {value}",
"reading_below_last": "Lavere end sidste aflæsning ({value})",
"settings_templates_label": "Skabelongalleri",
"settings_templates_hint": "Fravælg skabeloner, du aldrig får brug for — de forsvinder fra \"Fra skabelon\"-vælgerne (panel og config flow). Intet andet ændres; kan genaktiveres når som helst.",
"worksheet": "Arbejdsark",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Underret kun for visning",
"settings_notify_scope_all": "Alle opgaver",
"settings_notify_scope_hint": "Kun opgaver, der matcher den valgte visnings etiket-/brugerfiltre, sender påmindelser. Visningens status, sortering og gruppering ignoreres her.",
"settings_notify_rule": "Din egen notifikationsregel",
"settings_notify_rule_hint": "Hver notifikation udløser også hændelsen maintenance_supporter_notification (type, status, objekt, opgave, referencenumre, prioritet, link, nyttelast). Videresend den med en automatisering — fx til Ticker, Telegram eller Pushover.",
"settings_notify_event_only": "Udløs kun hændelsen, send intet selv",
"settings_notify_event_only_hint": "Din automatisering står for hele leveringen. Gentagelsesintervaller, stille timer og dagsloftet gælder stadig for, hvornår hændelsen udløses.",
"settings_notify_extra_data": "Ekstra notifikationsdata (skabelon)",
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tilladt; flettes ind i notify-kaldets data (dine nøgler vinder). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.",
"card_saved_view": "Gemt visning",
"card_saved_view_none": "Ingen",
"card_saved_view_help": "Anvender visningens status-, bruger- og etiketfiltre oven på filtrene ovenfor. Visningens sortering og gruppering er panelvisningsindstillinger og anvendes ikke på kortet.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Marker alle skiftet",
"battery_fleet_mark_one": "Marker dette batteri skiftet",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Ingen sensor",
"battery_fleet_trigger_lost": "Opgavens sensorudløser er gået tabt — den udløses ikke og fuldføres ikke automatisk.",
"battery_fleet_repair": "Reparér",
"battery_fleet_exclude": "Udeluk fra flåden",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Vælg en batterisensor, som den automatiske registrering overså — den kommer straks på listen.",
"battery_fleet_track_self": "Spor selvopladende batterier",
"battery_fleet_track_self_hint": "Telefoner, støvsugere og andre selvopladende enheder vises som genopladelige — ved lavt niveau bedes om opladning, aldrig om nye batterier.",
"battery_fleet_due_without_sensor": "Behandl en overskredet prognose som forfalden, når et batteri ikke har en sensor",
"battery_fleet_due_without_sensor_hint": "Nogle enheder rapporterer slet ikke batteriniveau — Battery Notes kender kun typen og seneste udskiftning. Når den typiske levetid er passeret, tæller sådan et batteri som lavt, og flådeopgaven minder dig om det; at markere det som udskiftet nulstiller prognosen.",
"battery_fleet_status_low": "Lav",
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I orden",
"battery_fleet_status_due": "Forfalden",
"battery_fleet_predicted_on": "Forventes omkring {date}",
"battery_fleet_predicted_trend": "Forudsagt ud fra batteriets afladningstendens: omkring {date} ({confidence})",
"battery_fleet_rechargeable": "Genopladeligt: oplad i stedet for at udskifte — aldrig på indkøbslisten",
@@ -917,6 +956,24 @@
"service_record_print": "Servicebog (PDF)",
"date": "Dato",
"service_record_entries": "poster",
"ref_number": "Referencenummer",
"print_options_title": "Udskriv servicebog",
"print_layout": "Opbygning",
"print_layout_chronological": "Kronologisk",
"print_layout_by_task": "Efter opgave",
"print_include": "Medtag",
"print_inc_readings": "Aflæsninger",
"print_inc_parts": "Brugte dele",
"print_inc_photos": "Fotos",
"print_inc_documents": "Tilknyttede dokumenter",
"print_inc_checklist": "Tjekliste",
"print_inc_notes": "Noter",
"print_inc_costs": "Omkostning og varighed",
"print_inc_person": "Udført af",
"print_inc_refs": "Referencenumre",
"print_inc_qr": "QR-kode pr. opgave",
"print_button": "Udskriv",
"report_scan_hint": "Scan for at åbne opgaven",
"completed_by": "Udført af",
"date_from": "Fra",
"date_to": "Til",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: ungültiger Wert",
"feat_schedule_time": "Uhrzeit-Scheduling",
"feat_schedule_time_desc": "Tasks werden zu einer festen Uhrzeit fällig statt um Mitternacht.",
"schedule_time_optional": "Fällig um (optional)",
"schedule_time_toggle": "Zu einer bestimmten Uhrzeit fällig",
"schedule_time_help": "Leer = Mitternacht (Default). HA-Zeitzone.",
"at_time": "um",
"notes_optional": "Notizen (optional)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Kein Tag —",
"dashboard": "Dashboard",
"tab_today": "Heute",
"palette_placeholder": "Objekte und Aufgaben suchen…",
"palette_placeholder": "Objekte, Aufgaben, Ersatzteile, Dokumente, Notizen suchen…",
"palette_no_results": "Keine Treffer",
"palette_hint": "↑↓ Navigieren · Enter Öffnen · Esc Schließen",
"palette_hint": "↑↓ navigieren · Enter öffnen · Esc schließen · / öffnet die Suche überall",
"search_open": "Suchen",
"search_group_parts": "Ersatzteile",
"search_group_content": "In Dokumenten",
"search_group_history": "Verlaufsnotizen",
"search_page": "Seite {page}",
"search_empty_hint": "Tippen, um Objekte, Aufgaben, Ersatzteile, Dokumente und Notizen zu durchsuchen",
"search_searching": "Dokumente und Notizen werden durchsucht…",
"search_index_status": "Volltextsuche: {indexed} von {total} Dateien indiziert · {no_text} ohne Textebene · {pending} ausstehend",
"today_all_caught_up": "Alles erledigt! Diese Woche steht nichts an.",
"today_overdue": "Überfällig",
"today_due_today": "Heute fällig",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "Schwelle „Verbrauchsmaterial niedrig“ (%)",
"settings_battery_low_percent": "Schwelle „Batterie niedrig“ (%)",
"settings_thresholds_hint": "Vorgaben für neue Vorgeschlagene Einrichtungen (Prozent-Restwert) und für Flotten-Batterien ohne eigene Battery-Notes-Schwelle. Bestehende Aufgaben behalten ihren Wert.",
"settings_value_out_of_range": "Der Wert muss zwischen {min} und {max} liegen",
"bn_summary": "{name}: {pct} % für {n} Batterien",
"bn_floor_decides": "Deine {floor}-%-Schwelle entscheidet für alle; pro Batterie zählt die höhere der beiden Schwellen.",
"bn_above_floor": "Das liegt über deiner {floor}-%-Schwelle — für alle notierten Batterien entscheidet {name}.",
@@ -481,6 +490,11 @@
"test_notification_success": "Test-Benachrichtigung gesendet",
"test_notification_failed": "Test-Benachrichtigung fehlgeschlagen",
"notify_per_person": "Zustellung pro Person",
"member_avatars": "Avatare der Mitglieder",
"member_avatars_hint": "Wird neben zugewiesenen Aufgaben angezeigt — Initialen in der Farbe des Mitglieds. Standard kommt aus dem Namen; eigene Initialen oder eine Farbe wählen, um Mitglieder zu unterscheiden.",
"member_initials": "Initialen",
"member_color": "Farbe",
"member_avatar_reset": "Zurücksetzen",
"notify_no_own_device": "Kein eigenes Gerät — nutzt den Haushaltsdienst",
"settings_notify_due_soon": "Bei baldiger Fälligkeit benachrichtigen",
"settings_notify_overdue": "Bei Überfälligkeit benachrichtigen",
@@ -540,6 +554,7 @@
"group_by_label": "Gruppieren nach",
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
"target_changes_help": "Anzahl der passenden Übergänge, nach denen der Trigger auslöst (Standard: 1).",
"state_latch_help": "Bei 1 Übergang ist der Trigger ein Latch: Er erholt sich, wenn die Entität den Nach-Zustand verlässt — oder, wenn nur ein Von-Zustand gesetzt ist, sobald sie dorthin zurückkehrt.",
"for_minutes_state_help": "0 zählt jede Änderung sofort. Mit Minuten muss der neue Zustand erst so lange bestehen — kurzes Flackern löst dann weder aus noch zählt es.",
"qr_print_title": "QR-Codes drucken",
"qr_print_desc": "Erzeuge eine Druckseite mit QR-Codes zum Ausschneiden und Anbringen an den Geräten.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "„{name}“ löschen?",
"doc_too_large": "Datei ist zu groß (max. 25 MB).",
"doc_upload_failed": "Upload fehlgeschlagen.",
"completion_photo_optional": "Abschlussfoto (optional)",
"add_photo": "Foto hinzufügen",
"completion_photos_optional": "Abschlussfotos (optional)",
"completion_photos": "Abschlussfotos",
"choose_photos": "Fotos auswählen",
"choose_photo": "Foto wählen",
"photos_android_hint": "Die Android-App übergibt pro Auswahl ein Foto — jede Auswahl wird hinzugefügt.",
"add_photos": "Fotos hinzufügen",
"photos_limit": "Bis zu {max} Fotos pro Abschluss",
"history_edit_photos_hint": "Ein hier entferntes Foto bleibt in den Dokumenten des Objekts erhalten.",
"uploading": "Wird hochgeladen…",
"remove": "Entfernen",
"doc_deduped": "Bereits anderswo gespeichert — geteilt, kein zusätzlicher Speicher.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Wird beim Erledigen dieser Aufgabe neben dem erfassten Wert angezeigt.",
"reading_value_label": "Ablesewert",
"reading_label": "Ablesung",
"readings_section": "Ablesewerte",
"readings_hint": "Mehrere benannte Werte je Abschluss — eine Zeile pro Zähler. Leer lassen für einen einzelnen Wert.",
"reading_name_label": "Name der Ablesung",
"reading_unit_short": "Einheit",
"reading_add": "Ablesung hinzufügen",
"reading_duplicate_name": "Name bereits vergeben",
"reading_last": "zuletzt: {value}",
"reading_below_last": "Kleiner als der letzte Wert ({value})",
"settings_templates_label": "Vorlagen-Galerie",
"settings_templates_hint": "Vorlagen abwählen, die du nie brauchst — sie verschwinden aus den \"Aus Vorlage\"-Auswahlen (Panel und Config-Flow). Sonst ändert sich nichts; jederzeit wieder aktivierbar.",
"worksheet": "Arbeitsblatt",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Nur benachrichtigen für Ansicht",
"settings_notify_scope_all": "Alle Aufgaben",
"settings_notify_scope_hint": "Nur Aufgaben, die zu den Label-/Nutzer-Filtern der gewählten Ansicht passen, senden Erinnerungen. Status, Sortierung und Gruppierung der Ansicht werden hier ignoriert.",
"settings_notify_rule": "Eigene Benachrichtigungsregel",
"settings_notify_rule_hint": "Jede Benachrichtigung löst zusätzlich das Ereignis maintenance_supporter_notification aus (Art, Status, Objekt, Aufgabe, Referenznummern, Priorität, Deep-Link, Nutzlast). Leite es per Automation weiter — z. B. an Ticker, Telegram oder Pushover.",
"settings_notify_event_only": "Nur das Ereignis auslösen, selbst nichts senden",
"settings_notify_event_only_hint": "Deine Automation übernimmt den kompletten Versand. Wiederholintervalle, Ruhezeiten und Tageslimit gelten weiterhin dafür, wann das Ereignis ausgelöst wird.",
"settings_notify_extra_data": "Zusätzliche Benachrichtigungsdaten (Template)",
"settings_notify_extra_data_hint": "JSON oder YAML, Jinja erlaubt; wird in die data des Notify-Aufrufs gemischt (deine Schlüssel gewinnen). Variablen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prüfen mit „Test senden“.",
"card_saved_view": "Gespeicherte Ansicht",
"card_saved_view_none": "Keine",
"card_saved_view_help": "Wendet die Status-, Nutzer- und Label-Filter der Ansicht zusätzlich zu den obigen Filtern an. Sortierung und Gruppierung der Ansicht sind Panel-Darstellung und werden auf der Karte nicht angewendet.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Alle als getauscht markieren",
"battery_fleet_mark_one": "Diese Batterie als getauscht markieren",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Kein Sensor",
"battery_fleet_trigger_lost": "Der Sensor-Trigger dieser Aufgabe ist verloren gegangen — sie wird nicht ausgelöst oder automatisch abgeschlossen.",
"battery_fleet_repair": "Reparieren",
"battery_fleet_exclude": "Aus der Flotte ausschließen",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Einen Batteriesensor wählen, den die automatische Erkennung übersehen hat — er erscheint sofort in der Liste.",
"battery_fleet_track_self": "Selbstladende Batterien mitverfolgen",
"battery_fleet_track_self_hint": "Telefone, Staubsauger und andere selbstladende Geräte erscheinen als Akkus — bei niedrigem Stand wird ans Laden erinnert, nie an neue Zellen.",
"battery_fleet_due_without_sensor": "Abgelaufene Prognose als fällig werten, wenn eine Batterie keinen Sensor hat",
"battery_fleet_due_without_sensor_hint": "Manche Geräte melden gar keinen Batteriestand — Battery Notes kennt nur Typ und letzten Wechsel. Ist die typische Lebensdauer verstrichen, gilt so eine Batterie als leer und die Flotten-Aufgabe erinnert dich; als ersetzt markieren setzt die Prognose zurück.",
"battery_fleet_status_low": "Schwach",
"battery_fleet_status_soon": "Bald",
"battery_fleet_status_ok": "In Ordnung",
"battery_fleet_status_due": "Fällig",
"battery_fleet_predicted_on": "Voraussichtlich um {date}",
"battery_fleet_predicted_trend": "Aus dem Entladetrend dieser Batterie vorhergesagt: etwa {date} ({confidence})",
"battery_fleet_rechargeable": "Wiederaufladbar: laden statt ersetzen — nie auf der Einkaufsliste",
@@ -917,6 +956,24 @@
"service_record_print": "Serviceheft (PDF)",
"date": "Datum",
"service_record_entries": "Einträge",
"ref_number": "Referenznummer",
"print_options_title": "Serviceheft drucken",
"print_layout": "Aufbau",
"print_layout_chronological": "Chronologisch",
"print_layout_by_task": "Nach Aufgabe",
"print_include": "Enthalten",
"print_inc_readings": "Zählerstände",
"print_inc_parts": "Verbrauchte Teile",
"print_inc_photos": "Fotos",
"print_inc_documents": "Verknüpfte Dokumente",
"print_inc_checklist": "Checkliste",
"print_inc_notes": "Notizen",
"print_inc_costs": "Kosten und Dauer",
"print_inc_person": "Erledigt von",
"print_inc_refs": "Referenznummern",
"print_inc_qr": "QR-Code je Aufgabe",
"print_button": "Drucken",
"report_scan_hint": "Scannen öffnet die Aufgabe",
"completed_by": "Erledigt von",
"date_from": "Von",
"date_to": "Bis",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: invalid value",
"feat_schedule_time": "Time-of-day scheduling",
"feat_schedule_time_desc": "Tasks become overdue at a specific time of day instead of midnight.",
"schedule_time_optional": "Due at time (optional)",
"schedule_time_toggle": "Due at a specific time",
"schedule_time_help": "Empty = midnight (default). HA timezone.",
"at_time": "at",
"notes_optional": "Notes (optional)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— No tag —",
"dashboard": "Dashboard",
"tab_today": "Today",
"palette_placeholder": "Search objects and tasks…",
"palette_placeholder": "Search objects, tasks, spare parts, documents, notes…",
"palette_no_results": "No matches",
"palette_hint": "↑↓ to navigate · Enter to open · Esc to close",
"palette_hint": "↑↓ to navigate · Enter to open · Esc to close · / opens the search anywhere",
"search_open": "Search",
"search_group_parts": "Spare parts",
"search_group_content": "In documents",
"search_group_history": "History notes",
"search_page": "Page {page}",
"search_empty_hint": "Type to search objects, tasks, spare parts, documents and notes",
"search_searching": "Searching documents and notes…",
"search_index_status": "Full-text search: {indexed} of {total} files indexed · {no_text} without text layer · {pending} pending",
"today_all_caught_up": "All caught up! Nothing due this week.",
"today_overdue": "Overdue",
"today_due_today": "Due today",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "Consumable low threshold (%)",
"settings_battery_low_percent": "Battery low threshold (%)",
"settings_thresholds_hint": "Defaults for new Suggested setups (percent-remaining consumables) and for fleet batteries without their own Battery Notes threshold. Existing tasks keep their value.",
"settings_value_out_of_range": "Value must be between {min} and {max}",
"bn_summary": "{name}: {pct} % for {n} batteries",
"bn_floor_decides": "Your {floor} % floor decides for all of them; the higher of the two thresholds counts per battery.",
"bn_above_floor": "That is above your {floor} % floor, so {name} decides for all noted batteries.",
@@ -481,6 +490,11 @@
"test_notification_success": "Test notification sent",
"test_notification_failed": "Test notification failed",
"notify_per_person": "Per-person delivery",
"member_avatars": "Member avatars",
"member_avatars_hint": "Shown next to assigned tasks — initials in the member's colour. Defaults come from the name; set your own initials or pick a colour to tell members apart.",
"member_initials": "Initials",
"member_color": "Colour",
"member_avatar_reset": "Reset",
"notify_no_own_device": "No own device — uses the household service",
"settings_notify_due_soon": "Notify when due soon",
"settings_notify_overdue": "Notify when overdue",
@@ -546,6 +560,7 @@
"group_by_label": "Group by",
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
"target_changes_help": "Number of matching transitions before the trigger fires (default: 1).",
"state_latch_help": "With 1 transition the trigger is a latch: it recovers when the entity leaves the To-state — or, with only a From-state set, when it returns to that state.",
"for_minutes_state_help": "0 counts every change immediately. Set minutes and the new state must hold that long first — brief flickers then neither trigger nor count.",
"qr_print_title": "Print QR codes",
"qr_print_desc": "Generate a printable page of QR codes to cut out and stick on your equipment.",
@@ -683,8 +698,14 @@
"doc_delete_confirm": "Delete \"{name}\"?",
"doc_too_large": "File is too large (max 25 MB).",
"doc_upload_failed": "Upload failed.",
"completion_photo_optional": "Completion photo (optional)",
"add_photo": "Add photo",
"completion_photos_optional": "Completion photos (optional)",
"completion_photos": "Completion photos",
"choose_photos": "Choose photos",
"choose_photo": "Choose photo",
"photos_android_hint": "The Android app hands over one photo per pick — each pick is added.",
"add_photos": "Add photos",
"photos_limit": "Up to {max} photos per completion",
"history_edit_photos_hint": "Removing a photo here keeps the file in the object's documents.",
"uploading": "Uploading…",
"remove": "Remove",
"doc_deduped": "Already stored elsewhere — shared, no extra space used.",
@@ -739,6 +760,14 @@
"reading_unit_help": "Shown next to the recorded value when completing this task.",
"reading_value_label": "Reading value",
"reading_label": "Reading",
"readings_section": "Readings",
"readings_hint": "Several named values per completion — one row per meter. Leave empty to record a single value.",
"reading_name_label": "Reading name",
"reading_unit_short": "Unit",
"reading_add": "Add reading",
"reading_duplicate_name": "Name already used",
"reading_last": "last: {value}",
"reading_below_last": "Lower than the last reading ({value})",
"settings_templates_label": "Template gallery",
"settings_templates_hint": "Untick templates you'll never need — they disappear from the \"From template\" pickers (panel and config flow). Nothing else changes; you can re-enable them any time.",
"worksheet": "Work sheet",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Notify only for view",
"settings_notify_scope_all": "All tasks",
"settings_notify_scope_hint": "Only tasks matching the selected saved view's label/user filters send reminders. Status, sorting and grouping of the view are ignored here.",
"settings_notify_rule": "Your own notification rule",
"settings_notify_rule_hint": "Every notification also fires the event maintenance_supporter_notification (kind, status, object, task, reference numbers, priority, deep link, the payload). Route it with an automation — e.g. into Ticker, Telegram or Pushover.",
"settings_notify_event_only": "Only fire the event, send nothing myself",
"settings_notify_event_only_hint": "Your automation is the whole delivery. Rate limits, quiet hours and the daily cap still apply to when the event fires.",
"settings_notify_extra_data": "Extra notification data (template)",
"settings_notify_extra_data_hint": "JSON or YAML, Jinja allowed; merged into the notify call's data (your keys win). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test it with the Send test button.",
"card_saved_view": "Saved view",
"card_saved_view_none": "None",
"card_saved_view_help": "Applies the view's status, user and label filters on top of the filters above. The view's sorting and grouping are panel display settings and are not applied on the card.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Mark all replaced",
"battery_fleet_mark_one": "Mark this battery replaced",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "No sensor",
"battery_fleet_trigger_lost": "This task's sensor trigger was lost — it will not fire or auto-complete.",
"battery_fleet_repair": "Repair",
"battery_fleet_exclude": "Exclude from the fleet",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Pick a battery sensor the automatic discovery missed — it joins the roster immediately.",
"battery_fleet_track_self": "Track self-charging batteries",
"battery_fleet_track_self_hint": "Phones, vacuums and other devices that recharge themselves appear as rechargeables — a low one asks for a charge, never for new cells.",
"battery_fleet_due_without_sensor": "Treat a passed forecast as due when a battery has no sensor",
"battery_fleet_due_without_sensor_hint": "Some devices report no battery level at all — Battery Notes only knows the type and the last replacement. Once the typical lifetime has passed, such a battery counts as low and the fleet task reminds you; marking it replaced resets the forecast.",
"battery_fleet_status_low": "Low",
"battery_fleet_status_soon": "Soon",
"battery_fleet_status_ok": "Healthy",
"battery_fleet_status_due": "Due",
"battery_fleet_predicted_on": "Expected around {date}",
"battery_fleet_predicted_trend": "Predicted from this battery's discharge trend: around {date} ({confidence})",
"battery_fleet_rechargeable": "Rechargeable: charge instead of replacing — never on the shopping list",
@@ -917,6 +956,24 @@
"service_record_print": "Service record (PDF)",
"date": "Date",
"service_record_entries": "entries",
"ref_number": "Reference number",
"print_options_title": "Print service record",
"print_layout": "Layout",
"print_layout_chronological": "Chronological",
"print_layout_by_task": "By task",
"print_include": "Include",
"print_inc_readings": "Readings",
"print_inc_parts": "Parts used",
"print_inc_photos": "Photos",
"print_inc_documents": "Linked documents",
"print_inc_checklist": "Checklist",
"print_inc_notes": "Notes",
"print_inc_costs": "Cost and duration",
"print_inc_person": "Completed by",
"print_inc_refs": "Reference numbers",
"print_inc_qr": "QR code per task",
"print_button": "Print",
"report_scan_hint": "Scan to open the task",
"completed_by": "Completed by",
"date_from": "From",
"date_to": "To",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: valor inválido",
"feat_schedule_time": "Programación por hora",
"feat_schedule_time_desc": "Las tareas vencen a una hora específica en lugar de medianoche.",
"schedule_time_optional": "Vence a las (opcional)",
"schedule_time_toggle": "Vence a una hora concreta",
"schedule_time_help": "Vacío = medianoche (predeterminado). Zona horaria HA.",
"at_time": "a las",
"notes_optional": "Notas (opcional)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Sin etiqueta —",
"dashboard": "Panel",
"tab_today": "Hoy",
"palette_placeholder": "Buscar objetos y tareas…",
"palette_placeholder": "Buscar objetos, tareas, repuestos, documentos, notas…",
"palette_no_results": "Sin resultados",
"palette_hint": "↑↓ navegar · Enter abrir · Esc cerrar",
"palette_hint": "↑↓ navegar · Intro abrir · Esc cerrar · / abre la búsqueda en cualquier lugar",
"search_open": "Buscar",
"search_group_parts": "Repuestos",
"search_group_content": "En documentos",
"search_group_history": "Notas del historial",
"search_page": "Página {page}",
"search_empty_hint": "Escribe para buscar objetos, tareas, repuestos, documentos y notas",
"search_searching": "Buscando en documentos y notas…",
"search_index_status": "Búsqueda de texto completo: {indexed} de {total} archivos indexados · {no_text} sin capa de texto · {pending} pendientes",
"today_all_caught_up": "¡Todo al día! Nada pendiente esta semana.",
"today_overdue": "Vencidas",
"today_due_today": "Vence hoy",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Umbral „consumible bajo“ (%)",
"settings_battery_low_percent": "Umbral „pila baja“ (%)",
"settings_thresholds_hint": "Valores predeterminados para nuevas Configuraciones sugeridas (porcentaje restante) y para pilas de la flota sin umbral propio de Battery Notes. Las tareas existentes conservan su valor.",
"settings_value_out_of_range": "El valor debe estar entre {min} y {max}",
"bn_summary": "{name}: {pct} % para {n} pilas",
"bn_floor_decides": "Tu umbral del {floor} % decide para todas; por pila cuenta el mayor de los dos umbrales.",
"bn_above_floor": "Está por encima de tu umbral del {floor} %, así que {name} decide para todas las pilas anotadas.",
@@ -479,6 +488,11 @@
"test_notification_success": "Notificación de prueba enviada",
"test_notification_failed": "La notificación de prueba falló",
"notify_per_person": "Entrega por persona",
"member_avatars": "Avatares de los miembros",
"member_avatars_hint": "Se muestra junto a las tareas asignadas — iniciales en el color del miembro. Por defecto se toman del nombre; elige tus iniciales o un color para distinguir a los miembros.",
"member_initials": "Iniciales",
"member_color": "Color",
"member_avatar_reset": "Restablecer",
"notify_no_own_device": "Sin dispositivo propio — usa el servicio del hogar",
"settings_notify_due_soon": "Notificar cuando esté próxima",
"settings_notify_overdue": "Notificar cuando esté vencida",
@@ -538,6 +552,7 @@
"group_by_label": "Agrupar por",
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
"target_changes_help": "Número de transiciones coincidentes antes de que se dispare el trigger (predeterminado: 1).",
"state_latch_help": "Con 1 transición el disparador es un cerrojo: se recupera cuando la entidad abandona el estado destino — o, si solo hay un estado origen, cuando vuelve a él.",
"for_minutes_state_help": "0 cuenta cada cambio de inmediato. Con minutos, el nuevo estado debe mantenerse primero — un parpadeo breve entonces ni dispara ni cuenta.",
"qr_print_title": "Imprimir códigos QR",
"qr_print_desc": "Genera una página imprimible de códigos QR para recortar y pegar en tus equipos.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "¿Eliminar «{name}»?",
"doc_too_large": "El archivo es demasiado grande (máx. 25 MB).",
"doc_upload_failed": "Error al subir.",
"completion_photo_optional": "Foto de finalización (opcional)",
"add_photo": "Añadir foto",
"completion_photos_optional": "Fotos de finalización (opcional)",
"completion_photos": "Fotos de finalización",
"choose_photos": "Elegir fotos",
"choose_photo": "Elegir foto",
"photos_android_hint": "La app de Android entrega una foto por selección — cada selección se añade.",
"add_photos": "Añadir fotos",
"photos_limit": "Hasta {max} fotos por finalización",
"history_edit_photos_hint": "Una foto quitada aquí se conserva en los documentos del objeto.",
"uploading": "Subiendo…",
"remove": "Quitar",
"doc_deduped": "Ya almacenado en otro lugar — compartido, sin espacio adicional.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Se muestra junto al valor registrado al completar esta tarea.",
"reading_value_label": "Valor de lectura",
"reading_label": "Lectura",
"readings_section": "Lecturas",
"readings_hint": "Varios valores con nombre por finalización — una fila por contador. Déjalo vacío para un único valor.",
"reading_name_label": "Nombre de la lectura",
"reading_unit_short": "Unidad",
"reading_add": "Añadir lectura",
"reading_duplicate_name": "Nombre ya en uso",
"reading_last": "último: {value}",
"reading_below_last": "Menor que la última lectura ({value})",
"settings_templates_label": "Galería de plantillas",
"settings_templates_hint": "Desmarca las plantillas que nunca necesitarás: desaparecen de los selectores \"Desde plantilla\" (panel y flujo de configuración). Nada más cambia; puedes reactivarlas cuando quieras.",
"worksheet": "Hoja de trabajo",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Notificar solo para la vista",
"settings_notify_scope_all": "Todas las tareas",
"settings_notify_scope_hint": "Solo las tareas que coinciden con los filtros de etiqueta/usuario de la vista seleccionada envían recordatorios. El estado, el orden y la agrupación de la vista se ignoran aquí.",
"settings_notify_rule": "Tu propia regla de notificación",
"settings_notify_rule_hint": "Cada notificación dispara además el evento maintenance_supporter_notification (tipo, estado, objeto, tarea, números de referencia, prioridad, enlace, carga útil). Enrútalo con una automatización — p. ej. a Ticker, Telegram o Pushover.",
"settings_notify_event_only": "Solo disparar el evento, no enviar nada",
"settings_notify_event_only_hint": "Tu automatización se encarga de toda la entrega. Los intervalos de repetición, las horas de silencio y el límite diario siguen decidiendo cuándo se dispara el evento.",
"settings_notify_extra_data": "Datos de notificación adicionales (plantilla)",
"settings_notify_extra_data_hint": "JSON o YAML, se permite Jinja; se fusiona en los data de la llamada notify (tus claves ganan). Variables: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Pruébalo con el botón Enviar prueba.",
"card_saved_view": "Vista guardada",
"card_saved_view_none": "Ninguna",
"card_saved_view_help": "Aplica los filtros de estado, usuario y etiqueta de la vista además de los filtros anteriores. El orden y la agrupación de la vista son ajustes de visualización del panel y no se aplican en la tarjeta.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Marcar todas como reemplazadas",
"battery_fleet_mark_one": "Marcar esta batería como reemplazada",
"battery_fleet_offline": "sin conexión",
"battery_fleet_no_sensor": "Sin sensor",
"battery_fleet_trigger_lost": "El disparador de sensor de esta tarea se perdió — no se activará ni se completará automáticamente.",
"battery_fleet_repair": "Reparar",
"battery_fleet_exclude": "Excluir de la flota",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Elige un sensor de batería que la detección automática pasó por alto — se une a la lista de inmediato.",
"battery_fleet_track_self": "Seguir baterías de carga automática",
"battery_fleet_track_self_hint": "Teléfonos, aspiradoras y otros dispositivos que se recargan solos aparecen como recargables: si están bajos piden una carga, nunca pilas nuevas.",
"battery_fleet_due_without_sensor": "Tratar una previsión vencida como pendiente cuando una pila no tiene sensor",
"battery_fleet_due_without_sensor_hint": "Algunos dispositivos no informan ningún nivel de pila — Battery Notes solo conoce el tipo y el último cambio. Pasada la vida útil típica, esa pila cuenta como baja y la tarea de la flota te lo recuerda; marcarla como sustituida reinicia la previsión.",
"battery_fleet_status_low": "Baja",
"battery_fleet_status_soon": "Pronto",
"battery_fleet_status_ok": "Correcta",
"battery_fleet_status_due": "Pendiente",
"battery_fleet_predicted_on": "Previsto hacia {date}",
"battery_fleet_predicted_trend": "Predicción según la tendencia de descarga de esta pila: hacia {date} ({confidence})",
"battery_fleet_rechargeable": "Recargable: se recarga en lugar de sustituirse — nunca en la lista de compra",
@@ -917,6 +956,24 @@
"service_record_print": "Registro de mantenimiento (PDF)",
"date": "Fecha",
"service_record_entries": "entradas",
"ref_number": "Número de referencia",
"print_options_title": "Imprimir el historial de servicio",
"print_layout": "Diseño",
"print_layout_chronological": "Cronológico",
"print_layout_by_task": "Por tarea",
"print_include": "Incluir",
"print_inc_readings": "Lecturas",
"print_inc_parts": "Piezas usadas",
"print_inc_photos": "Fotos",
"print_inc_documents": "Documentos vinculados",
"print_inc_checklist": "Lista de verificación",
"print_inc_notes": "Notas",
"print_inc_costs": "Coste y duración",
"print_inc_person": "Realizado por",
"print_inc_refs": "Números de referencia",
"print_inc_qr": "Código QR por tarea",
"print_button": "Imprimir",
"report_scan_hint": "Escanea para abrir la tarea",
"completed_by": "Realizado por",
"date_from": "Desde",
"date_to": "Hasta",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: virheellinen arvo",
"feat_schedule_time": "Kellonajan ajoitus",
"feat_schedule_time_desc": "Tehtävät erääntyvät tiettyyn kellonaikaan keskiyön sijaan.",
"schedule_time_optional": "Erääntyy klo (valinnainen)",
"schedule_time_toggle": "Erääntyy tiettyyn kellonaikaan",
"schedule_time_help": "Tyhjä = keskiyö (oletus). HA:n aikavyöhyke.",
"at_time": "klo",
"notes_optional": "Muistiinpanot (valinnainen)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Ei tunnistetta —",
"dashboard": "Kojelauta",
"tab_today": "Tänään",
"palette_placeholder": "Hae kohteita ja tehtäviä…",
"palette_placeholder": "Hae kohteita, tehtäviä, varaosia, asiakirjoja, muistiinpanoja…",
"palette_no_results": "Ei osumia",
"palette_hint": "↑↓ liiku · Enter avaa · Esc sulje",
"palette_hint": "↑↓ selaa · Enter avaa · Esc sulje · / avaa haun missä tahansa",
"search_open": "Hae",
"search_group_parts": "Varaosat",
"search_group_content": "Asiakirjoissa",
"search_group_history": "Historian muistiinpanot",
"search_page": "Sivu {page}",
"search_empty_hint": "Kirjoita hakeaksesi kohteista, tehtävistä, varaosista, asiakirjoista ja muistiinpanoista",
"search_searching": "Haetaan asiakirjoista ja muistiinpanoista…",
"search_index_status": "Kokotekstihaku: {indexed}/{total} tiedostoa indeksoitu · {no_text} ilman tekstikerrosta · {pending} odottaa",
"today_all_caught_up": "Kaikki hoidettu! Tällä viikolla ei ole mitään.",
"today_overdue": "Myöhässä",
"today_due_today": "Erääntyy tänään",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "Kynnys „kulutustarvike vähissä“ (%)",
"settings_battery_low_percent": "Kynnys „paristo vähissä“ (%)",
"settings_thresholds_hint": "Oletukset uusille Ehdotetuille asetuksille (jäljellä oleva prosentti) ja paristokannan paristoille ilman omaa Battery Notes -kynnystä. Olemassa olevat tehtävät säilyttävät arvonsa.",
"settings_value_out_of_range": "Arvon on oltava välillä {min}–{max}",
"bn_summary": "{name}: {pct} % {n} paristolle",
"bn_floor_decides": "Sinun {floor} %:n kynnyksesi ratkaisee kaikille; paristoa kohden pätee korkeampi kahdesta kynnyksestä.",
"bn_above_floor": "Se on sinun {floor} %:n kynnyksesi yläpuolella, joten {name} ratkaisee kaikille kirjatuille paristoille.",
@@ -481,6 +490,11 @@
"test_notification_success": "Testi-ilmoitus lähetetty",
"test_notification_failed": "Testi-ilmoitus epäonnistui",
"notify_per_person": "Toimitus henkilöittäin",
"member_avatars": "Jäsenten avatarit",
"member_avatars_hint": "Näytetään osoitettujen tehtävien vieressä — nimikirjaimet jäsenen värissä. Oletus tulee nimestä; aseta omat nimikirjaimet tai valitse väri jäsenten erottamiseksi.",
"member_initials": "Nimikirjaimet",
"member_color": "Väri",
"member_avatar_reset": "Palauta",
"notify_no_own_device": "Ei omaa laitetta — käyttää kotitalouden palvelua",
"settings_notify_due_soon": "Ilmoita, kun erääntyy pian",
"settings_notify_overdue": "Ilmoita, kun myöhässä",
@@ -540,6 +554,7 @@
"group_by_label": "Ryhmittele",
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
"target_changes_help": "Vastaavien siirtymien määrä ennen laukaisua (oletus: 1).",
"state_latch_help": "Yhdellä siirtymällä liipaisin on salpa: se palautuu, kun entiteetti poistuu Kohde-tilasta — tai, jos vain Lähtö-tila on asetettu, kun se palaa siihen.",
"for_minutes_state_help": "0 laskee jokaisen muutoksen heti. Minuuteilla uuden tilan on ensin pysyttävä — lyhyet välähdykset eivät silloin laukaise eivätkä kirjaudu.",
"qr_print_title": "Tulosta QR-koodit",
"qr_print_desc": "Luo tulostettava sivu QR-koodeja, jotka voit leikata ja kiinnittää laitteisiin.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Poistetaanko \"{name}\"?",
"doc_too_large": "Tiedosto on liian suuri (enint. 25 Mt).",
"doc_upload_failed": "Lataus epäonnistui.",
"completion_photo_optional": "Valmistumiskuva (valinnainen)",
"add_photo": "Lisää kuva",
"completion_photos_optional": "Valokuvat suorituksesta (valinnainen)",
"completion_photos": "Valokuvat suorituksesta",
"choose_photos": "Valitse kuvat",
"choose_photo": "Valitse kuva",
"photos_android_hint": "Android-sovellus välittää yhden kuvan per valinta — jokainen valinta lisätään.",
"add_photos": "Lisää kuvia",
"photos_limit": "Enintään {max} kuvaa suoritusta kohti",
"history_edit_photos_hint": "Tästä poistettu kuva säilyy kohteen asiakirjoissa.",
"uploading": "Ladataan…",
"remove": "Poista",
"doc_deduped": "Jo tallennettu muualle — jaettu, ei lisätilaa.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Näytetään kirjatun arvon vieressä tehtävää suoritettaessa.",
"reading_value_label": "Lukema",
"reading_label": "Lukema",
"readings_section": "Lukemat",
"readings_hint": "Useita nimettyjä arvoja suoritusta kohti — yksi rivi mittaria kohti. Jätä tyhjäksi yhtä arvoa varten.",
"reading_name_label": "Lukeman nimi",
"reading_unit_short": "Yksikkö",
"reading_add": "Lisää lukema",
"reading_duplicate_name": "Nimi on jo käytössä",
"reading_last": "viimeksi: {value}",
"reading_below_last": "Pienempi kuin edellinen lukema ({value})",
"settings_templates_label": "Mallipohjagalleria",
"settings_templates_hint": "Poista valinta mallipohjista, joita et koskaan tarvitse — ne katoavat \"Mallipohjasta\"-valitsimista (paneeli ja config flow). Mikään muu ei muutu; voi ottaa käyttöön uudelleen milloin tahansa.",
"worksheet": "Työlomake",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Ilmoita vain näkymälle",
"settings_notify_scope_all": "Kaikki tehtävät",
"settings_notify_scope_hint": "Vain valitun näkymän tunniste-/käyttäjäsuodattimiin osuvat tehtävät lähettävät muistutuksia. Näkymän tila, lajittelu ja ryhmittely ohitetaan tässä.",
"settings_notify_rule": "Oma ilmoitussääntö",
"settings_notify_rule_hint": "Jokainen ilmoitus laukaisee myös tapahtuman maintenance_supporter_notification (laji, tila, kohde, tehtävä, viitenumerot, prioriteetti, linkki, sisältö). Ohjaa se automaatiolla — esim. Tickeriin, Telegramiin tai Pushoveriin.",
"settings_notify_event_only": "Laukaise vain tapahtuma, älä lähetä itse mitään",
"settings_notify_event_only_hint": "Automaatiosi hoitaa koko toimituksen. Toistovälit, hiljaiset tunnit ja päiväraja määräävät yhä, milloin tapahtuma laukeaa.",
"settings_notify_extra_data": "Ilmoituksen lisätiedot (malli)",
"settings_notify_extra_data_hint": "JSON tai YAML, Jinja sallittu; yhdistetään notify-kutsun data-kenttään (omat avaimesi voittavat). Muuttujat: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Kokeile Lähetä testi -painikkeella.",
"card_saved_view": "Tallennettu näkymä",
"card_saved_view_none": "Ei mitään",
"card_saved_view_help": "Soveltaa näkymän tila-, käyttäjä- ja tunnistesuodattimet yllä olevien suodattimien lisäksi. Näkymän lajittelu ja ryhmittely ovat paneelin näyttöasetuksia eivätkä vaikuta korttiin.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Merkitse kaikki vaihdetuiksi",
"battery_fleet_mark_one": "Merkitse tämä paristo vaihdetuksi",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Ei anturia",
"battery_fleet_trigger_lost": "Tehtävän anturiliipaisin on kadonnut — se ei laukea eikä valmistu automaattisesti.",
"battery_fleet_repair": "Korjaa",
"battery_fleet_exclude": "Sulje pois kannasta",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Valitse paristoanturi, jonka automaattinen tunnistus ohitti — se liittyy luetteloon heti.",
"battery_fleet_track_self": "Seuraa itselatautuvia akkuja",
"battery_fleet_track_self_hint": "Puhelimet, imurit ja muut itselatautuvat laitteet näkyvät ladattavina — vähissä oleva pyytää latausta, ei koskaan uusia paristoja.",
"battery_fleet_due_without_sensor": "Käsittele ohitettu ennuste erääntyneenä, kun paristolla ei ole anturia",
"battery_fleet_due_without_sensor_hint": "Jotkin laitteet eivät raportoi paristotasoa lainkaan — Battery Notes tietää vain tyypin ja viimeisen vaihdon. Kun tyypillinen käyttöikä on kulunut, tällainen paristo lasketaan vähissä olevaksi ja kalustotehtävä muistuttaa sinua; vaihdetuksi merkitseminen nollaa ennusteen.",
"battery_fleet_status_low": "Vähissä",
"battery_fleet_status_soon": "Pian",
"battery_fleet_status_ok": "Kunnossa",
"battery_fleet_status_due": "Erääntynyt",
"battery_fleet_predicted_on": "Odotettavissa noin {date}",
"battery_fleet_predicted_trend": "Ennustettu tämän pariston purkautumistrendistä: noin {date} ({confidence})",
"battery_fleet_rechargeable": "Ladattava: lataa vaihtamisen sijaan — ei koskaan ostoslistalle",
@@ -917,6 +956,24 @@
"service_record_print": "Huoltokirja (PDF)",
"date": "Päivämäärä",
"service_record_entries": "merkintää",
"ref_number": "Viitenumero",
"print_options_title": "Tulosta huoltokirja",
"print_layout": "Asettelu",
"print_layout_chronological": "Aikajärjestyksessä",
"print_layout_by_task": "Tehtävittäin",
"print_include": "Sisällytä",
"print_inc_readings": "Lukemat",
"print_inc_parts": "Käytetyt osat",
"print_inc_photos": "Kuvat",
"print_inc_documents": "Liitetyt asiakirjat",
"print_inc_checklist": "Tarkistuslista",
"print_inc_notes": "Muistiinpanot",
"print_inc_costs": "Kustannus ja kesto",
"print_inc_person": "Suorittaja",
"print_inc_refs": "Viitenumerot",
"print_inc_qr": "QR-koodi tehtävää kohti",
"print_button": "Tulosta",
"report_scan_hint": "Skannaa avataksesi tehtävän",
"completed_by": "Suorittanut",
"date_from": "Alkaen",
"date_to": "Asti",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field} : valeur invalide",
"feat_schedule_time": "Planification à l'heure",
"feat_schedule_time_desc": "Les tâches arrivent à échéance à une heure précise plutôt qu'à minuit.",
"schedule_time_optional": "Échéance à l'heure (optionnel)",
"schedule_time_toggle": "Échéance à une heure précise",
"schedule_time_help": "Vide = minuit (défaut). Fuseau horaire HA.",
"at_time": "à",
"notes_optional": "Notes (optionnel)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Aucun tag —",
"dashboard": "Tableau de bord",
"tab_today": "Aujourd'hui",
"palette_placeholder": "Rechercher objets et tâches…",
"palette_placeholder": "Rechercher objets, tâches, pièces, documents, notes…",
"palette_no_results": "Aucun résultat",
"palette_hint": "↑↓ naviguer · Entrée ouvrir · Échap fermer",
"palette_hint": "↑↓ naviguer · Entrée ouvrir · Échap fermer · / ouvre la recherche partout",
"search_open": "Rechercher",
"search_group_parts": "Pièces détachées",
"search_group_content": "Dans les documents",
"search_group_history": "Notes d'historique",
"search_page": "p. {page}",
"search_empty_hint": "Saisissez un texte pour chercher dans les objets, tâches, pièces, documents et notes",
"search_searching": "Recherche dans les documents et notes…",
"search_index_status": "Recherche plein texte : {indexed} fichiers sur {total} indexés · {no_text} sans couche texte · {pending} en attente",
"today_all_caught_up": "Tout est à jour ! Rien cette semaine.",
"today_overdue": "En retard",
"today_due_today": "À faire aujourd'hui",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Seuil « consommable bas » (%)",
"settings_battery_low_percent": "Seuil « pile faible » (%)",
"settings_thresholds_hint": "Valeurs par défaut pour les nouvelles Configurations suggérées (pourcentage restant) et pour les piles de la flotte sans seuil Battery Notes propre. Les tâches existantes gardent leur valeur.",
"settings_value_out_of_range": "La valeur doit être comprise entre {min} et {max}",
"bn_summary": "{name} : {pct} % pour {n} piles",
"bn_floor_decides": "Votre seuil de {floor} % décide pour toutes ; par pile, c'est le plus élevé des deux seuils qui compte.",
"bn_above_floor": "C'est au-dessus de votre seuil de {floor} % : {name} décide donc pour toutes les piles notées.",
@@ -479,6 +488,11 @@
"test_notification_success": "Notification de test envoyée",
"test_notification_failed": "Échec de la notification de test",
"notify_per_person": "Distribution par personne",
"member_avatars": "Avatars des membres",
"member_avatars_hint": "Affiché à côté des tâches attribuées — initiales dans la couleur du membre. Par défaut d'après le nom ; choisissez vos initiales ou une couleur pour distinguer les membres.",
"member_initials": "Initiales",
"member_color": "Couleur",
"member_avatar_reset": "Réinitialiser",
"notify_no_own_device": "Aucun appareil propre — utilise le service du foyer",
"settings_notify_due_soon": "Notifier quand bientôt dû",
"settings_notify_overdue": "Notifier quand en retard",
@@ -538,6 +552,7 @@
"group_by_label": "Grouper par",
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
"target_changes_help": "Nombre de transitions correspondantes avant le déclenchement (par défaut : 1).",
"state_latch_help": "Avec 1 transition, le déclencheur est un verrou : il se rétablit quand l'entité quitte l'état « vers » — ou, si seul un état « depuis » est défini, quand elle y revient.",
"for_minutes_state_help": "0 compte chaque changement immédiatement. Avec des minutes, le nouvel état doit d'abord se maintenir — un bref clignotement ne déclenche ni ne compte alors.",
"qr_print_title": "Imprimer les QR codes",
"qr_print_desc": "Générer une page imprimable de QR codes à découper et coller sur votre équipement.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Supprimer « {name} » ?",
"doc_too_large": "Fichier trop volumineux (max. 25 Mo).",
"doc_upload_failed": "Échec du téléversement.",
"completion_photo_optional": "Photo d'achèvement (facultatif)",
"add_photo": "Ajouter une photo",
"completion_photos_optional": "Photos de fin de tâche (facultatif)",
"completion_photos": "Photos de fin de tâche",
"choose_photos": "Choisir des photos",
"choose_photo": "Choisir une photo",
"photos_android_hint": "L'application Android ne transmet qu'une photo par sélection — chaque sélection est ajoutée.",
"add_photos": "Ajouter des photos",
"photos_limit": "Jusqu'à {max} photos par fin de tâche",
"history_edit_photos_hint": "Une photo retirée ici reste dans les documents de l'objet.",
"uploading": "Téléversement…",
"remove": "Retirer",
"doc_deduped": "Déjà stocké ailleurs — partagé, aucun espace supplémentaire.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Affichée à côté de la valeur enregistrée lors de l'exécution de cette tâche.",
"reading_value_label": "Valeur relevée",
"reading_label": "Relevé",
"readings_section": "Relevés",
"readings_hint": "Plusieurs valeurs nommées par fin de tâche — une ligne par compteur. Laisser vide pour une valeur unique.",
"reading_name_label": "Nom du relevé",
"reading_unit_short": "Unité",
"reading_add": "Ajouter un relevé",
"reading_duplicate_name": "Nom déjà utilisé",
"reading_last": "dernier : {value}",
"reading_below_last": "Inférieur au dernier relevé ({value})",
"settings_templates_label": "Galerie de modèles",
"settings_templates_hint": "Décochez les modèles dont vous n'aurez jamais besoin — ils disparaissent des sélecteurs « Depuis un modèle » (panneau et config flow). Rien d'autre ne change ; réactivables à tout moment.",
"worksheet": "Fiche de travail",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Notifier uniquement pour la vue",
"settings_notify_scope_all": "Toutes les tâches",
"settings_notify_scope_hint": "Seules les tâches correspondant aux filtres étiquette/utilisateur de la vue choisie envoient des rappels. Le statut, le tri et le regroupement de la vue sont ignorés ici.",
"settings_notify_rule": "Votre propre règle de notification",
"settings_notify_rule_hint": "Chaque notification déclenche aussi l'événement maintenance_supporter_notification (type, statut, objet, tâche, numéros de référence, priorité, lien, charge utile). Routez-le avec une automatisation — vers Ticker, Telegram ou Pushover par exemple.",
"settings_notify_event_only": "Déclencher seulement l'événement, ne rien envoyer",
"settings_notify_event_only_hint": "Votre automatisation assure toute la livraison. Intervalles de répétition, heures calmes et plafond quotidien s'appliquent toujours au déclenchement de l'événement.",
"settings_notify_extra_data": "Données de notification supplémentaires (modèle)",
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja autorisé ; fusionné dans les data de l'appel notify (vos clés l'emportent). Variables : kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Vérifiez avec le bouton Envoyer un test.",
"card_saved_view": "Vue enregistrée",
"card_saved_view_none": "Aucune",
"card_saved_view_help": "Applique les filtres statut, utilisateur et étiquette de la vue en plus des filtres ci-dessus. Le tri et le regroupement de la vue sont des réglages d'affichage du panneau et ne s'appliquent pas à la carte.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Tout marquer remplacé",
"battery_fleet_mark_one": "Marquer cette pile remplacée",
"battery_fleet_offline": "hors ligne",
"battery_fleet_no_sensor": "Pas de capteur",
"battery_fleet_trigger_lost": "Le déclencheur de capteur de cette tâche a été perdu — elle ne se déclenchera pas et ne se terminera pas automatiquement.",
"battery_fleet_repair": "Réparer",
"battery_fleet_exclude": "Exclure du parc",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Choisissez un capteur de batterie que la détection automatique a manqué — il rejoint la liste immédiatement.",
"battery_fleet_track_self": "Suivre les batteries à recharge automatique",
"battery_fleet_track_self_hint": "Les téléphones, aspirateurs et autres appareils qui se rechargent seuls apparaissent comme rechargeables — un niveau bas demande une recharge, jamais de nouvelles piles.",
"battery_fleet_due_without_sensor": "Considérer une prévision dépassée comme échue quand une pile n'a pas de capteur",
"battery_fleet_due_without_sensor_hint": "Certains appareils ne signalent aucun niveau de pile — Battery Notes ne connaît que le type et le dernier remplacement. Une fois la durée de vie typique dépassée, cette pile compte comme faible et la tâche du parc vous le rappelle ; la marquer remplacée réinitialise la prévision.",
"battery_fleet_status_low": "Faible",
"battery_fleet_status_soon": "Bientôt",
"battery_fleet_status_ok": "Bon état",
"battery_fleet_status_due": "À remplacer",
"battery_fleet_predicted_on": "Prévu vers {date}",
"battery_fleet_predicted_trend": "Prédit à partir de la tendance de décharge de cette pile : vers {date} ({confidence})",
"battery_fleet_rechargeable": "Rechargeable : à recharger plutôt qu'à remplacer — jamais sur la liste d'achats",
@@ -917,6 +956,24 @@
"service_record_print": "Carnet d'entretien (PDF)",
"date": "Date",
"service_record_entries": "entrées",
"ref_number": "Numéro de référence",
"print_options_title": "Imprimer le carnet d'entretien",
"print_layout": "Mise en page",
"print_layout_chronological": "Chronologique",
"print_layout_by_task": "Par tâche",
"print_include": "Inclure",
"print_inc_readings": "Relevés",
"print_inc_parts": "Pièces utilisées",
"print_inc_photos": "Photographies",
"print_inc_documents": "Documents liés",
"print_inc_checklist": "Liste de contrôle",
"print_inc_notes": "Remarques",
"print_inc_costs": "Coût et durée",
"print_inc_person": "Réalisé par",
"print_inc_refs": "Numéros de référence",
"print_inc_qr": "QR code par tâche",
"print_button": "Imprimer",
"report_scan_hint": "Scanner pour ouvrir la tâche",
"completed_by": "Réalisé par",
"date_from": "Du",
"date_to": "Au",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: अमान्य मान",
"feat_schedule_time": "दिन का समय निर्धारण",
"feat_schedule_time_desc": "कार्य मध्यरात्रि के बजाय दिन के एक विशिष्ट समय पर अतिदेय हो जाते हैं।",
"schedule_time_optional": "इस समय देय (वैकल्पिक)",
"schedule_time_toggle": "किसी निश्चित समय पर देय",
"schedule_time_help": "खाली = मध्यरात्रि (डिफ़ॉल्ट)। HA समय क्षेत्र।",
"at_time": "बजे",
"notes_optional": "टिप्पणियाँ (वैकल्पिक)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— कोई टैग नहीं —",
"dashboard": "डैशबोर्ड",
"tab_today": "आज",
"palette_placeholder": "ऑब्जेक्ट और कार्य खोजें…",
"palette_placeholder": "ऑब्जेक्ट, कार्य, स्पेयर पार्ट्स, दस्तावेज़, नोट्स खोजें…",
"palette_no_results": "कोई मिलान नहीं",
"palette_hint": "↑↓ नेविगेट · Enter खोलें · Esc बंद",
"palette_hint": "↑↓ नेविगेट · Enter खोलें · Esc बंद करें · / कहीं से भी खोज खोलता है",
"search_open": "खोजें",
"search_group_parts": "स्पेयर पार्ट्स",
"search_group_content": "दस्तावेज़ों में",
"search_group_history": "इतिहास नोट्स",
"search_page": "पृष्ठ {page}",
"search_empty_hint": "ऑब्जेक्ट, कार्य, स्पेयर पार्ट्स, दस्तावेज़ और नोट्स खोजने के लिए टाइप करें",
"search_searching": "दस्तावेज़ और नोट्स खोजे जा रहे हैं…",
"search_index_status": "पूर्ण-पाठ खोज: {total} में से {indexed} फ़ाइलें अनुक्रमित · {no_text} बिना टेक्स्ट परत · {pending} लंबित",
"today_all_caught_up": "सब पूरा! इस सप्ताह कुछ भी बाकी नहीं।",
"today_overdue": "अतिदेय",
"today_due_today": "आज देय",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "उपभोग्य „कम“ सीमा (%)",
"settings_battery_low_percent": "बैटरी „कम“ सीमा (%)",
"settings_thresholds_hint": "नए सुझाए गए सेटअप (शेष प्रतिशत) और अपनी Battery Notes सीमा न रखने वाली बेड़े की बैटरियों के लिए डिफ़ॉल्ट। मौजूदा कार्य अपना मान रखते हैं।",
"settings_value_out_of_range": "मान {min} और {max} के बीच होना चाहिए",
"bn_summary": "{name}: {n} बैटरियों के लिए {pct} %",
"bn_floor_decides": "आपकी {floor} % सीमा सभी के लिए निर्णायक है; प्रत्येक बैटरी के लिए दोनों में से ऊँची सीमा मान्य है।",
"bn_above_floor": "यह आपकी {floor} % सीमा से ऊपर है, इसलिए सभी नोट की गई बैटरियों के लिए {name} निर्णायक है।",
@@ -481,6 +490,11 @@
"test_notification_success": "परीक्षण सूचना भेजी गई",
"test_notification_failed": "परीक्षण सूचना विफल",
"notify_per_person": "प्रति व्यक्ति वितरण",
"member_avatars": "सदस्यों के अवतार",
"member_avatars_hint": "सौंपे गए कार्यों के बगल में दिखाया जाता है — सदस्य के रंग में आद्याक्षर। डिफ़ॉल्ट नाम से आता है; सदस्यों में अंतर करने के लिए अपने आद्याक्षर या रंग चुनें।",
"member_initials": "आद्याक्षर",
"member_color": "रंग",
"member_avatar_reset": "रीसेट",
"notify_no_own_device": "कोई निजी डिवाइस नहीं — घरेलू सेवा का उपयोग करता है",
"settings_notify_due_soon": "जल्द देय होने पर सूचित करें",
"settings_notify_overdue": "अतिदेय होने पर सूचित करें",
@@ -540,6 +554,7 @@
"group_by_label": "इसके अनुसार समूहित करें",
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
"target_changes_help": "ट्रिगर होने से पहले मिलान वाले संक्रमणों की संख्या (डिफ़ॉल्ट: 1)।",
"state_latch_help": "1 संक्रमण पर ट्रिगर लैच बन जाता है: इकाई 'तक' स्थिति छोड़ते ही यह पुनर्स्थापित होता है — या, केवल 'से' स्थिति सेट हो तो उसमें लौटने पर।",
"for_minutes_state_help": "0 हर परिवर्तन को तुरंत गिनता है। मिनट सेट करने पर नई स्थिति को पहले उतनी देर बनी रहनी होगी — क्षणिक झिलमिलाहट तब न ट्रिगर करती है न गिनी जाती है।",
"qr_print_title": "QR कोड प्रिंट करें",
"qr_print_desc": "अपने उपकरण पर काटकर चिपकाने के लिए QR कोड का प्रिंट-योग्य पृष्ठ बनाएँ।",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "\"{name}\" हटाएँ?",
"doc_too_large": "फ़ाइल बहुत बड़ी है (अधिकतम 25 MB)।",
"doc_upload_failed": "अपलोड विफल रहा।",
"completion_photo_optional": "पूर्णता फ़ोटो (वैकल्पिक)",
"add_photo": "फ़ोटो जोड़ें",
"completion_photos_optional": "पूर्णता की तस्वीरें (वैकल्पिक)",
"completion_photos": "पूर्णता की तस्वीरें",
"choose_photos": "तस्वीरें चुनें",
"choose_photo": "फ़ोटो चुनें",
"photos_android_hint": "Android ऐप प्रति चयन एक फ़ोटो देता है — हर चयन जोड़ा जाता है।",
"add_photos": "तस्वीरें जोड़ें",
"photos_limit": "प्रति पूर्णता अधिकतम {max} तस्वीरें",
"history_edit_photos_hint": "यहाँ हटाई गई तस्वीर ऑब्जेक्ट के दस्तावेज़ों में बनी रहती है।",
"uploading": "अपलोड हो रहा है…",
"remove": "हटाएँ",
"doc_deduped": "पहले से कहीं और संग्रहीत — साझा, कोई अतिरिक्त स्थान नहीं।",
@@ -733,6 +754,14 @@
"reading_unit_help": "इस कार्य को पूरा करते समय दर्ज मान के बगल में दिखाई जाती है।",
"reading_value_label": "रीडिंग मान",
"reading_label": "रीडिंग",
"readings_section": "रीडिंग",
"readings_hint": "प्रति पूर्णता कई नामित मान — प्रति मीटर एक पंक्ति। एकल मान के लिए खाली छोड़ें।",
"reading_name_label": "रीडिंग का नाम",
"reading_unit_short": "इकाई",
"reading_add": "रीडिंग जोड़ें",
"reading_duplicate_name": "नाम पहले से उपयोग में है",
"reading_last": "पिछला: {value}",
"reading_below_last": "पिछली रीडिंग ({value}) से कम",
"settings_templates_label": "टेम्पलेट गैलरी",
"settings_templates_hint": "जिन टेम्पलेट की कभी ज़रूरत नहीं होगी उन्हें अनचेक करें — वे \"टेम्पलेट से\" चयनकर्ताओं (पैनल और कॉन्फ़िग फ़्लो) से गायब हो जाएंगे। और कुछ नहीं बदलता; कभी भी फिर से सक्षम कर सकते हैं।",
"worksheet": "कार्य पत्रक",
@@ -818,6 +847,12 @@
"settings_notify_scope": "केवल इस व्यू के लिए सूचित करें",
"settings_notify_scope_all": "सभी कार्य",
"settings_notify_scope_hint": "केवल वे कार्य अनुस्मारक भेजते हैं जो चुने गए व्यू के लेबल/उपयोगकर्ता फ़िल्टर से मेल खाते हैं। व्यू की स्थिति, क्रम और समूहन यहाँ अनदेखे रहते हैं।",
"settings_notify_rule": "आपका अपना सूचना नियम",
"settings_notify_rule_hint": "हर सूचना maintenance_supporter_notification इवेंट भी ट्रिगर करती है (प्रकार, स्थिति, ऑब्जेक्ट, कार्य, संदर्भ संख्याएँ, प्राथमिकता, डीप लिंक, पेलोड)। इसे ऑटोमेशन से रूट करें — जैसे Ticker, Telegram या Pushover में।",
"settings_notify_event_only": "केवल इवेंट ट्रिगर करें, स्वयं कुछ न भेजें",
"settings_notify_event_only_hint": "आपका ऑटोमेशन पूरी डिलीवरी संभालता है। दोहराव अंतराल, शांत घंटे और दैनिक सीमा अभी भी तय करते हैं कि इवेंट कब ट्रिगर हो।",
"settings_notify_extra_data": "अतिरिक्त सूचना डेटा (टेम्पलेट)",
"settings_notify_extra_data_hint": "JSON या YAML, Jinja की अनुमति; notify कॉल के data में मिलाया जाता है (आपकी कुंजियाँ प्राथमिक)। वेरिएबल: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message। परीक्षण भेजें बटन से जाँचें।",
"card_saved_view": "सहेजा गया व्यू",
"card_saved_view_none": "कोई नहीं",
"card_saved_view_help": "ऊपर दिए फ़िल्टरों के अतिरिक्त व्यू के स्थिति, उपयोगकर्ता और लेबल फ़िल्टर लागू करता है। व्यू का क्रम और समूहन पैनल की प्रदर्शन सेटिंग हैं और कार्ड पर लागू नहीं होते।",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "सभी बदली हुई चिह्नित करें",
"battery_fleet_mark_one": "इस बैटरी को बदली हुई चिह्नित करें",
"battery_fleet_offline": "ऑफ़लाइन",
"battery_fleet_no_sensor": "कोई सेंसर नहीं",
"battery_fleet_trigger_lost": "इस कार्य का सेंसर ट्रिगर खो गया है — यह सक्रिय नहीं होगा और स्वतः पूर्ण नहीं होगा।",
"battery_fleet_repair": "मरम्मत करें",
"battery_fleet_exclude": "फ्लीट से बाहर करें",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "स्वचालित खोज से छूटा बैटरी सेंसर चुनें — यह तुरंत सूची में जुड़ जाएगा।",
"battery_fleet_track_self": "स्वयं चार्ज होने वाली बैटरियों को ट्रैक करें",
"battery_fleet_track_self_hint": "फ़ोन, वैक्यूम और अन्य स्वयं चार्ज होने वाले उपकरण रिचार्जेबल के रूप में दिखते हैं — कम होने पर चार्ज करने को कहा जाता है, नई बैटरी कभी नहीं।",
"battery_fleet_due_without_sensor": "जब बैटरी में सेंसर न हो तो बीत चुके पूर्वानुमान को देय मानें",
"battery_fleet_due_without_sensor_hint": "कुछ डिवाइस बैटरी स्तर बिल्कुल नहीं बताते — Battery Notes को केवल प्रकार और पिछला बदलाव पता होता है। सामान्य आयु बीत जाने पर ऐसी बैटरी कम मानी जाती है और फ़्लीट कार्य आपको याद दिलाता है; बदली हुई चिह्नित करने से पूर्वानुमान रीसेट हो जाता है।",
"battery_fleet_status_low": "कम",
"battery_fleet_status_soon": "जल्द",
"battery_fleet_status_ok": "ठीक",
"battery_fleet_status_due": "देय",
"battery_fleet_predicted_on": "{date} के आसपास अपेक्षित",
"battery_fleet_predicted_trend": "इस बैटरी के डिस्चार्ज रुझान से अनुमानित: लगभग {date} ({confidence})",
"battery_fleet_rechargeable": "रिचार्जेबल: बदलने के बजाय चार्ज करें — खरीदारी सूची में कभी नहीं",
@@ -917,6 +956,24 @@
"service_record_print": "सेवा रिकॉर्ड (PDF)",
"date": "दिनांक",
"service_record_entries": "प्रविष्टियाँ",
"ref_number": "संदर्भ संख्या",
"print_options_title": "सेवा रिकॉर्ड प्रिंट करें",
"print_layout": "लेआउट",
"print_layout_chronological": "कालानुक्रमिक",
"print_layout_by_task": "कार्य के अनुसार",
"print_include": "शामिल करें",
"print_inc_readings": "रीडिंग",
"print_inc_parts": "प्रयुक्त पुर्जे",
"print_inc_photos": "फ़ोटो",
"print_inc_documents": "लिंक किए गए दस्तावेज़",
"print_inc_checklist": "चेकलिस्ट",
"print_inc_notes": "नोट्स",
"print_inc_costs": "लागत और अवधि",
"print_inc_person": "पूर्ण किया",
"print_inc_refs": "संदर्भ संख्याएँ",
"print_inc_qr": "प्रति कार्य QR कोड",
"print_button": "प्रिंट",
"report_scan_hint": "कार्य खोलने के लिए स्कैन करें",
"completed_by": "द्वारा पूर्ण",
"date_from": "से",
"date_to": "तक",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: érvénytelen érték",
"feat_schedule_time": "Napszakhoz kötött ütemezés",
"feat_schedule_time_desc": "A feladatok nem éjfélkor, hanem egy megadott napszakban válnak lejárttá.",
"schedule_time_optional": "Esedékesség időpontja (opcionális)",
"schedule_time_toggle": "Adott időpontban esedékes",
"schedule_time_help": "Üres = éjfél (alapértelmezés). HA időzóna.",
"at_time": "ekkor:",
"notes_optional": "Megjegyzések (opcionális)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Nincs címke —",
"dashboard": "Irányítópult",
"tab_today": "Ma",
"palette_placeholder": "Objektumok és feladatok keresése…",
"palette_placeholder": "Tárgyak, feladatok, alkatrészek, dokumentumok, jegyzetek keresése…",
"palette_no_results": "Nincs találat",
"palette_hint": "↑↓ navigálás · Enter megnyitás · Esc bezárás",
"palette_hint": "↑↓ lépkedés · Enter megnyitás · Esc bezárás · / bárhol megnyitja a keresést",
"search_open": "Keresés",
"search_group_parts": "Alkatrészek",
"search_group_content": "Dokumentumokban",
"search_group_history": "Előzmény-jegyzetek",
"search_page": "{page}. oldal",
"search_empty_hint": "Írjon a tárgyak, feladatok, alkatrészek, dokumentumok és jegyzetek kereséséhez",
"search_searching": "Keresés a dokumentumokban és jegyzetekben…",
"search_index_status": "Teljes szöveges keresés: {indexed} / {total} fájl indexelve · {no_text} szövegréteg nélkül · {pending} függőben",
"today_all_caught_up": "Minden rendben! Ezen a héten nincs esedékes feladat.",
"today_overdue": "Lejárt",
"today_due_today": "Ma esedékes",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "„Fogyóeszköz alacsony” küszöb (%)",
"settings_battery_low_percent": "„Alacsony akkumulátor” küszöb (%)",
"settings_thresholds_hint": "Alapértékek az új Javasolt beállításokhoz (százalékos maradék) és a saját Battery Notes-küszöb nélküli flottaelemekhez. A meglévő feladatok megtartják az értéküket.",
"settings_value_out_of_range": "Az értéknek {min} és {max} között kell lennie",
"bn_summary": "{name}: {pct} % {n} elemhez",
"bn_floor_decides": "A te {floor} %-os küszöböd dönt mindegyiknél; elemenként a két küszöb közül a magasabb számít.",
"bn_above_floor": "Ez a te {floor} %-os küszöböd felett van, így minden feljegyzett elemnél a {name} dönt.",
@@ -481,6 +490,11 @@
"test_notification_success": "Tesztértesítés elküldve",
"test_notification_failed": "A tesztértesítés nem sikerült",
"notify_per_person": "Kézbesítés személyenként",
"member_avatars": "Tagok avatárjai",
"member_avatars_hint": "A kiosztott feladatok mellett jelenik meg — monogram a tag színében. Alapértelmezés a névből; adjon meg saját monogramot vagy válasszon színt a tagok megkülönböztetéséhez.",
"member_initials": "Monogram",
"member_color": "Szín",
"member_avatar_reset": "Visszaállítás",
"notify_no_own_device": "Nincs saját eszköz — a háztartási szolgáltatást használja",
"settings_notify_due_soon": "Értesítés, ha hamarosan esedékes",
"settings_notify_overdue": "Értesítés lejáratkor",
@@ -546,6 +560,7 @@
"group_by_label": "Csoportosítás",
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
"target_changes_help": "Az egyező átmenetek száma a kiváltás előtt (alapértelmezés: 1).",
"state_latch_help": "1 átmenetnél a trigger reteszként működik: helyreáll, amikor az entitás elhagyja a cél-állapotot — vagy, ha csak kiinduló állapot van megadva, amikor visszatér abba.",
"for_minutes_state_help": "A 0 minden változást azonnal számol. Percekkel az új állapotnak előbb fenn kell maradnia — a rövid villanás ekkor se nem indít, se nem számít.",
"qr_print_title": "QR-kódok nyomtatása",
"qr_print_desc": "Nyomtatható oldal QR-kódokkal — vágja ki és ragassza fel a berendezésekre.",
@@ -683,8 +698,14 @@
"doc_delete_confirm": "Törli a(z) „{name}” elemet?",
"doc_too_large": "A fájl túl nagy (max. 25 MB).",
"doc_upload_failed": "A feltöltés nem sikerült.",
"completion_photo_optional": "Fotó az elvégzésről (opcionális)",
"add_photo": "Fotó hozzáadása",
"completion_photos_optional": "Fotók az elvégzésről (nem kötelező)",
"completion_photos": "Fotók az elvégzésről",
"choose_photos": "Fotók kiválasztása",
"choose_photo": "Fotó kiválasztása",
"photos_android_hint": "Az Android-alkalmazás választásonként egy fotót ad át — minden választás hozzáadódik.",
"add_photos": "Fotók hozzáadása",
"photos_limit": "Legfeljebb {max} fotó elvégzésenként",
"history_edit_photos_hint": "Az itt eltávolított fotó megmarad az objektum dokumentumai között.",
"uploading": "Feltöltés…",
"remove": "Eltávolítás",
"doc_deduped": "Már máshol is tárolva — megosztott, nem foglal extra helyet.",
@@ -739,6 +760,14 @@
"reading_unit_help": "A rögzített érték mellett jelenik meg a feladat elvégzésekor.",
"reading_value_label": "Leolvasott érték",
"reading_label": "Leolvasás",
"readings_section": "Leolvasások",
"readings_hint": "Több nevesített érték elvégzésenként — mérőnként egy sor. Egyetlen értékhez hagyja üresen.",
"reading_name_label": "Leolvasás neve",
"reading_unit_short": "Mértékegység",
"reading_add": "Leolvasás hozzáadása",
"reading_duplicate_name": "A név már foglalt",
"reading_last": "legutóbb: {value}",
"reading_below_last": "Kisebb az előző leolvasásnál ({value})",
"settings_templates_label": "Sablongaléria",
"settings_templates_hint": "Vegye ki a pipát a sosem használt sablonoknál — így eltűnnek a „Sablonból” választókból (panel és konfigurációs folyamat). Semmi más nem változik; bármikor újra engedélyezheti őket.",
"worksheet": "Munkalap",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Értesítés csak erre a nézetre",
"settings_notify_scope_all": "Minden feladat",
"settings_notify_scope_hint": "Csak a kiválasztott mentett nézet címke-/felhasználószűrőinek megfelelő feladatok küldenek emlékeztetőt. A nézet állapot-, rendezési és csoportosítási beállításait itt figyelmen kívül hagyjuk.",
"settings_notify_rule": "Saját értesítési szabály",
"settings_notify_rule_hint": "Minden értesítés kiváltja a maintenance_supporter_notification eseményt is (típus, állapot, tárgy, feladat, hivatkozási számok, prioritás, mélylink, adatok). Irányítsa automatizálással — pl. a Tickerbe, Telegramba vagy Pushoverbe.",
"settings_notify_event_only": "Csak az esemény, saját küldés nélkül",
"settings_notify_event_only_hint": "A kézbesítést teljes egészében az automatizálás végzi. Az ismétlési időközök, a csendes órák és a napi korlát továbbra is meghatározzák, mikor váltódik ki az esemény.",
"settings_notify_extra_data": "További értesítési adatok (sablon)",
"settings_notify_extra_data_hint": "JSON vagy YAML, Jinja engedélyezett; beolvad a notify hívás data mezőjébe (a saját kulcsai nyernek). Változók: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Ellenőrizze a Teszt küldése gombbal.",
"card_saved_view": "Mentett nézet",
"card_saved_view_none": "Nincs",
"card_saved_view_help": "A nézet állapot-, felhasználó- és címkeszűrőit a fenti szűrőkön felül alkalmazza. A nézet rendezése és csoportosítása a panel megjelenítési beállítása, a kártyán nem érvényesül.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Mind cseréltként jelölése",
"battery_fleet_mark_one": "Elem cseréltként jelölése",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Nincs érzékelő",
"battery_fleet_trigger_lost": "A feladat érzékelős kiváltója elveszett — nem fog aktiválódni és automatikusan lezáródni sem.",
"battery_fleet_repair": "Javítás",
"battery_fleet_exclude": "Kizárás a flottából",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Válassz egy elem-érzékelőt, amelyet az automatikus felismerés kihagyott — azonnal felkerül a listára.",
"battery_fleet_track_self": "Öntöltő akkumulátorok követése",
"battery_fleet_track_self_hint": "A telefonok, porszívók és más öntöltő eszközök akkumulátorként jelennek meg — alacsony töltöttségnél töltést kérnek, sosem új elemet.",
"battery_fleet_due_without_sensor": "Lejárt előrejelzés esedékesnek számít, ha az elemnek nincs érzékelője",
"battery_fleet_due_without_sensor_hint": "Egyes eszközök egyáltalán nem jelentenek töltöttséget — a Battery Notes csak a típust és az utolsó cserét ismeri. A szokásos élettartam letelte után az ilyen elem lemerültnek számít, és a flottafeladat emlékeztet; a cserélve jelölés visszaállítja az előrejelzést.",
"battery_fleet_status_low": "Alacsony",
"battery_fleet_status_soon": "Hamarosan",
"battery_fleet_status_ok": "Rendben",
"battery_fleet_status_due": "Esedékes",
"battery_fleet_predicted_on": "Várhatóan {date} körül",
"battery_fleet_predicted_trend": "Az elem merülési trendjéből előrejelezve: kb. {date} ({confidence})",
"battery_fleet_rechargeable": "Újratölthető: cserélés helyett töltse fel — soha nem kerül a bevásárlólistára",
@@ -917,6 +956,24 @@
"service_record_print": "Szervizkönyv (PDF)",
"date": "Dátum",
"service_record_entries": "bejegyzés",
"ref_number": "Hivatkozási szám",
"print_options_title": "Szervizkönyv nyomtatása",
"print_layout": "Elrendezés",
"print_layout_chronological": "Időrendben",
"print_layout_by_task": "Feladatonként",
"print_include": "Tartalmazza",
"print_inc_readings": "Mérőállások",
"print_inc_parts": "Felhasznált alkatrészek",
"print_inc_photos": "Fotók",
"print_inc_documents": "Csatolt dokumentumok",
"print_inc_checklist": "Ellenőrzőlista",
"print_inc_notes": "Jegyzetek",
"print_inc_costs": "Költség és időtartam",
"print_inc_person": "Elvégezte",
"print_inc_refs": "Hivatkozási számok",
"print_inc_qr": "QR-kód feladatonként",
"print_button": "Nyomtatás",
"report_scan_hint": "Olvassa be a feladat megnyitásához",
"completed_by": "Elvégezte",
"date_from": "Ettől",
"date_to": "Eddig",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: valore non valido",
"feat_schedule_time": "Pianificazione oraria",
"feat_schedule_time_desc": "Le attività scadono a un'ora specifica anziché a mezzanotte.",
"schedule_time_optional": "Scadenza all'ora (opzionale)",
"schedule_time_toggle": "Scadenza a un orario preciso",
"schedule_time_help": "Vuoto = mezzanotte (default). Fuso orario HA.",
"at_time": "alle",
"notes_optional": "Note (opzionale)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Nessun tag —",
"dashboard": "Dashboard",
"tab_today": "Oggi",
"palette_placeholder": "Cerca oggetti e attività…",
"palette_placeholder": "Cerca oggetti, attività, ricambi, documenti, note…",
"palette_no_results": "Nessun risultato",
"palette_hint": "↑↓ naviga · Invio apri · Esc chiudi",
"palette_hint": "↑↓ navigare · Invio aprire · Esc chiudere · / apre la ricerca ovunque",
"search_open": "Cerca",
"search_group_parts": "Ricambi",
"search_group_content": "Nei documenti",
"search_group_history": "Note della cronologia",
"search_page": "Pagina {page}",
"search_empty_hint": "Digita per cercare tra oggetti, attività, ricambi, documenti e note",
"search_searching": "Ricerca in documenti e note…",
"search_index_status": "Ricerca full-text: {indexed} di {total} file indicizzati · {no_text} senza livello testo · {pending} in attesa",
"today_all_caught_up": "Tutto fatto! Niente in scadenza questa settimana.",
"today_overdue": "Scadute",
"today_due_today": "In scadenza oggi",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Soglia „consumabile basso“ (%)",
"settings_battery_low_percent": "Soglia „batteria scarica“ (%)",
"settings_thresholds_hint": "Valori predefiniti per le nuove Configurazioni suggerite (percentuale residua) e per le batterie della flotta senza soglia Battery Notes propria. Le attività esistenti mantengono il loro valore.",
"settings_value_out_of_range": "Il valore deve essere compreso tra {min} e {max}",
"bn_summary": "{name}: {pct} % per {n} batterie",
"bn_floor_decides": "La tua soglia del {floor} % decide per tutte; per ogni batteria conta la più alta delle due soglie.",
"bn_above_floor": "È sopra la tua soglia del {floor} %, quindi {name} decide per tutte le batterie annotate.",
@@ -479,6 +488,11 @@
"test_notification_success": "Notifica di test inviata",
"test_notification_failed": "Notifica di test non riuscita",
"notify_per_person": "Consegna per persona",
"member_avatars": "Avatar dei membri",
"member_avatars_hint": "Mostrato accanto alle attività assegnate — iniziali nel colore del membro. Predefinito dal nome; imposta iniziali proprie o scegli un colore per distinguere i membri.",
"member_initials": "Iniziali",
"member_color": "Colore",
"member_avatar_reset": "Ripristina",
"notify_no_own_device": "Nessun dispositivo proprio — usa il servizio della casa",
"settings_notify_due_soon": "Notifica quando in scadenza",
"settings_notify_overdue": "Notifica quando scaduto",
@@ -538,6 +552,7 @@
"group_by_label": "Raggruppa per",
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
"target_changes_help": "Numero di transizioni corrispondenti prima che il trigger si attivi (predefinito: 1).",
"state_latch_help": "Con 1 transizione il trigger è un latch: si ripristina quando l'entità lascia lo stato di destinazione — oppure, se è impostato solo lo stato di partenza, quando vi ritorna.",
"for_minutes_state_help": "0 conta ogni cambiamento subito. Con i minuti, il nuovo stato deve prima mantenersi — un breve sfarfallio allora non attiva né conta.",
"qr_print_title": "Stampa codici QR",
"qr_print_desc": "Genera una pagina stampabile di codici QR da ritagliare e applicare sulle apparecchiature.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Eliminare «{name}»?",
"doc_too_large": "File troppo grande (max 25 MB).",
"doc_upload_failed": "Caricamento non riuscito.",
"completion_photo_optional": "Foto di completamento (facoltativa)",
"add_photo": "Aggiungi foto",
"completion_photos_optional": "Foto di completamento (facoltativo)",
"completion_photos": "Foto di completamento",
"choose_photos": "Scegli foto",
"choose_photo": "Scegli foto",
"photos_android_hint": "L'app Android passa una foto per selezione — ogni selezione viene aggiunta.",
"add_photos": "Aggiungi foto",
"photos_limit": "Fino a {max} foto per completamento",
"history_edit_photos_hint": "Una foto rimossa qui resta nei documenti dell'oggetto.",
"uploading": "Caricamento…",
"remove": "Rimuovi",
"doc_deduped": "Già archiviato altrove — condiviso, nessuno spazio aggiuntivo.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Mostrata accanto al valore registrato al completamento di questa attività.",
"reading_value_label": "Valore letto",
"reading_label": "Lettura",
"readings_section": "Letture",
"readings_hint": "Più valori denominati per completamento — una riga per contatore. Lascia vuoto per un valore singolo.",
"reading_name_label": "Nome lettura",
"reading_unit_short": "Unità",
"reading_add": "Aggiungi lettura",
"reading_duplicate_name": "Nome già in uso",
"reading_last": "ultimo: {value}",
"reading_below_last": "Inferiore all'ultima lettura ({value})",
"settings_templates_label": "Galleria modelli",
"settings_templates_hint": "Deseleziona i modelli che non ti serviranno mai: spariscono dai selettori \"Da modello\" (pannello e config flow). Nient'altro cambia; riattivabili in qualsiasi momento.",
"worksheet": "Scheda di lavoro",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Notifica solo per la vista",
"settings_notify_scope_all": "Tutte le attività",
"settings_notify_scope_hint": "Solo le attività che corrispondono ai filtri etichetta/utente della vista scelta inviano promemoria. Stato, ordinamento e raggruppamento della vista qui vengono ignorati.",
"settings_notify_rule": "La tua regola di notifica",
"settings_notify_rule_hint": "Ogni notifica genera anche l'evento maintenance_supporter_notification (tipo, stato, oggetto, attività, numeri di riferimento, priorità, link, payload). Instradalo con un'automazione — ad es. verso Ticker, Telegram o Pushover.",
"settings_notify_event_only": "Genera solo l'evento, non inviare nulla",
"settings_notify_event_only_hint": "La tua automazione gestisce l'intera consegna. Intervalli di ripetizione, ore di silenzio e limite giornaliero valgono ancora per quando scatta l'evento.",
"settings_notify_extra_data": "Dati di notifica aggiuntivi (template)",
"settings_notify_extra_data_hint": "JSON o YAML, Jinja consentito; unito ai data della chiamata notify (le tue chiavi vincono). Variabili: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Prova con il pulsante Invia test.",
"card_saved_view": "Vista salvata",
"card_saved_view_none": "Nessuna",
"card_saved_view_help": "Applica i filtri stato, utente ed etichetta della vista in aggiunta ai filtri sopra. Ordinamento e raggruppamento della vista sono impostazioni di visualizzazione del pannello e non si applicano alla scheda.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Segna tutte sostituite",
"battery_fleet_mark_one": "Segna questa batteria sostituita",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Nessun sensore",
"battery_fleet_trigger_lost": "Il trigger del sensore di questa attività è andato perso — non si attiverà né si completerà automaticamente.",
"battery_fleet_repair": "Ripara",
"battery_fleet_exclude": "Escludi dal parco",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Scegli un sensore batteria sfuggito al rilevamento automatico — entra subito nell'elenco.",
"battery_fleet_track_self": "Traccia le batterie autoricaricabili",
"battery_fleet_track_self_hint": "Telefoni, aspirapolvere e altri dispositivi che si ricaricano da soli appaiono come ricaricabili — se scarichi chiedono una ricarica, mai batterie nuove.",
"battery_fleet_due_without_sensor": "Considerare una previsione scaduta come in scadenza se una batteria non ha sensore",
"battery_fleet_due_without_sensor_hint": "Alcuni dispositivi non riportano alcun livello di batteria — Battery Notes conosce solo il tipo e l'ultima sostituzione. Trascorsa la durata tipica, la batteria conta come scarica e l'attività del parco te lo ricorda; segnarla come sostituita azzera la previsione.",
"battery_fleet_status_low": "Scarica",
"battery_fleet_status_soon": "A breve",
"battery_fleet_status_ok": "A posto",
"battery_fleet_status_due": "In scadenza",
"battery_fleet_predicted_on": "Previsto intorno al {date}",
"battery_fleet_predicted_trend": "Previsto dalla tendenza di scarica di questa batteria: intorno al {date} ({confidence})",
"battery_fleet_rechargeable": "Ricaricabile: da ricaricare anziché sostituire — mai nella lista della spesa",
@@ -917,6 +956,24 @@
"service_record_print": "Libretto di manutenzione (PDF)",
"date": "Data",
"service_record_entries": "voci",
"ref_number": "Numero di riferimento",
"print_options_title": "Stampa il libretto di servizio",
"print_layout": "Impaginazione",
"print_layout_chronological": "Cronologico",
"print_layout_by_task": "Per attività",
"print_include": "Includi",
"print_inc_readings": "Letture",
"print_inc_parts": "Ricambi usati",
"print_inc_photos": "Foto",
"print_inc_documents": "Documenti collegati",
"print_inc_checklist": "Lista di controllo",
"print_inc_notes": "Note",
"print_inc_costs": "Costo e durata",
"print_inc_person": "Eseguito da",
"print_inc_refs": "Numeri di riferimento",
"print_inc_qr": "Codice QR per attività",
"print_button": "Stampa",
"report_scan_hint": "Scansiona per aprire l'attività",
"completed_by": "Completato da",
"date_from": "Dal",
"date_to": "Al",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: 無効な値です",
"feat_schedule_time": "時刻指定スケジュール",
"feat_schedule_time_desc": "タスクが深夜0時ではなく指定した時刻に期限超過となります。",
"schedule_time_optional": "期限の時刻(任意)",
"schedule_time_toggle": "特定の時刻に期限",
"schedule_time_help": "空欄=深夜0時(既定)。HAのタイムゾーン。",
"at_time": "時刻",
"notes_optional": "メモ(任意)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— タグなし —",
"dashboard": "ダッシュボード",
"tab_today": "今日",
"palette_placeholder": "オブジェクトとタスクを検索…",
"palette_placeholder": "対象・タスク・部品・文書・メモを検索…",
"palette_no_results": "一致なし",
"palette_hint": "↑↓ 移動 · Enter 開く · Esc 閉じる",
"palette_hint": "↑↓ 移動 · Enter 開く · Esc 閉じる · / でどこからでも検索",
"search_open": "検索",
"search_group_parts": "部品",
"search_group_content": "文書内",
"search_group_history": "履歴メモ",
"search_page": "{page} ページ",
"search_empty_hint": "入力して対象・タスク・部品・文書・メモを検索",
"search_searching": "文書とメモを検索中…",
"search_index_status": "全文検索: {total} 件中 {indexed} 件をインデックス済み · テキスト層なし {no_text} 件 · 保留 {pending} 件",
"today_all_caught_up": "すべて完了!今週の予定はありません。",
"today_overdue": "期限切れ",
"today_due_today": "今日期限",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "消耗品「残量少」しきい値(%)",
"settings_battery_low_percent": "電池「残量少」しきい値(%)",
"settings_thresholds_hint": "新しい「推奨セットアップ」(残量パーセント)と、独自の Battery Notes しきい値を持たないバッテリー一覧の電池に適用される既定値です。既存のタスクは値を保持します。",
"settings_value_out_of_range": "値は {min} から {max} の間で指定してください",
"bn_summary": "{name}:{n} 個の電池に {pct} %",
"bn_floor_decides": "あなたの {floor} % のしきい値がすべてに適用されます。電池ごとに高い方のしきい値が有効です。",
"bn_above_floor": "これはあなたの {floor} % のしきい値を上回るため、記録済みのすべての電池は {name} が決定します。",
@@ -481,6 +490,11 @@
"test_notification_success": "テスト通知を送信しました",
"test_notification_failed": "テスト通知に失敗しました",
"notify_per_person": "メンバーごとの配信",
"member_avatars": "メンバーのアバター",
"member_avatars_hint": "割り当てられたタスクの横に表示されます — メンバーの色で頭文字を表示。既定は名前から生成。頭文字や色を設定してメンバーを区別できます。",
"member_initials": "頭文字",
"member_color": "色",
"member_avatar_reset": "リセット",
"notify_no_own_device": "個人の端末なし — 家庭用サービスを使用",
"settings_notify_due_soon": "期限間近で通知",
"settings_notify_overdue": "期限超過で通知",
@@ -540,6 +554,7 @@
"group_by_label": "グループ化",
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
"target_changes_help": "トリガーが発生するまでに一致する遷移の回数(既定: 1)。",
"state_latch_help": "遷移回数が1の場合トリガーはラッチになります。エンティティが「後」の状態を離れると復帰し、「前」の状態のみ設定時はその状態に戻ると復帰します。",
"for_minutes_state_help": "0 はすべての変化を即座にカウントします。分を設定すると新しい状態がその時間続く必要があり、一瞬の変動はトリガーもカウントもされません。",
"qr_print_title": "QRコードを印刷",
"qr_print_desc": "切り取って機器に貼れるQRコードの印刷ページを生成します。",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "「{name}」を削除しますか?",
"doc_too_large": "ファイルが大きすぎます(最大 25 MB)。",
"doc_upload_failed": "アップロードに失敗しました。",
"completion_photo_optional": "完了写真(任意)",
"add_photo": "写真を追加",
"completion_photos_optional": "完了写真(任意)",
"completion_photos": "完了写真",
"choose_photos": "写真を選択",
"choose_photo": "写真を選択",
"photos_android_hint": "Android アプリは1回の選択につき写真1枚を渡します。選択するたびに追加されます。",
"add_photos": "写真を追加",
"photos_limit": "1回の完了につき最大{max}枚",
"history_edit_photos_hint": "ここで外した写真は対象のドキュメントに残ります。",
"uploading": "アップロード中…",
"remove": "削除",
"doc_deduped": "既に別の場所に保存済み — 共有され、追加容量はかかりません。",
@@ -733,6 +754,14 @@
"reading_unit_help": "このタスクの完了時に記録値の横に表示されます。",
"reading_value_label": "検針値",
"reading_label": "検針",
"readings_section": "検針項目",
"readings_hint": "1回の完了で複数の名前付き値を記録 — メーターごとに1行。単一の値だけなら空のままにします。",
"reading_name_label": "検針項目名",
"reading_unit_short": "単位",
"reading_add": "検針項目を追加",
"reading_duplicate_name": "この名前は既に使われています",
"reading_last": "前回: {value}",
"reading_below_last": "前回の値({value})より小さい",
"settings_templates_label": "テンプレートギャラリー",
"settings_templates_hint": "不要なテンプレートのチェックを外すと、「テンプレートから」の選択肢(パネルと設定フロー)から消えます。それ以外は何も変わらず、いつでも再有効化できます。",
"worksheet": "作業シート",
@@ -818,6 +847,12 @@
"settings_notify_scope": "このビューのみ通知",
"settings_notify_scope_all": "すべてのタスク",
"settings_notify_scope_hint": "選択した保存済みビューのラベル/ユーザーフィルターに一致するタスクのみがリマインダーを送信します。ビューのステータス・並び順・グループ化はここでは無視されます。",
"settings_notify_rule": "独自の通知ルール",
"settings_notify_rule_hint": "各通知はイベント maintenance_supporter_notification(種類、状態、対象、タスク、参照番号、優先度、ディープリンク、ペイロード)も発火します。オートメーションで Ticker、Telegram、Pushover などへ転送してください。",
"settings_notify_event_only": "イベントのみ発火し、自身では送信しない",
"settings_notify_event_only_hint": "配信はすべてオートメーション側で行います。繰り返し間隔、静音時間、1日の上限は引き続きイベント発火の条件になります。",
"settings_notify_extra_data": "追加の通知データ(テンプレート)",
"settings_notify_extra_data_hint": "JSON または YAML、Jinja 可。notify 呼び出しの data にマージされます(あなたのキーが優先)。変数: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message。「テスト送信」ボタンで確認できます。",
"card_saved_view": "保存済みビュー",
"card_saved_view_none": "なし",
"card_saved_view_help": "上記のフィルターに加えて、ビューのステータス・ユーザー・ラベルのフィルターを適用します。ビューの並び順とグループ化はパネルの表示設定であり、カードには適用されません。",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "すべて交換済みにする",
"battery_fleet_mark_one": "この電池を交換済みにする",
"battery_fleet_offline": "オフライン",
"battery_fleet_no_sensor": "センサーなし",
"battery_fleet_trigger_lost": "このタスクのセンサートリガーが失われました — 発動も自動完了もしません。",
"battery_fleet_repair": "修復",
"battery_fleet_exclude": "フリートから除外",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "自動検出が見逃した電池センサーを選択すると、すぐに一覧に追加されます。",
"battery_fleet_track_self": "自己充電式バッテリーを追跡",
"battery_fleet_track_self_hint": "スマートフォンや掃除機など自己充電するデバイスは充電式として表示されます。残量が少ないときは充電を促し、電池交換は求めません。",
"battery_fleet_due_without_sensor": "センサーのない電池は、予測日を過ぎたら交換時期として扱う",
"battery_fleet_due_without_sensor_hint": "電池残量をまったく報告しないデバイスもあります。Battery Notes は種類と最終交換日しか知りません。一般的な寿命を過ぎると、その電池は残量低下として扱われ、一括タスクが通知します。交換済みにすると予測がリセットされます。",
"battery_fleet_status_low": "残量低下",
"battery_fleet_status_soon": "まもなく",
"battery_fleet_status_ok": "良好",
"battery_fleet_status_due": "交換時期",
"battery_fleet_predicted_on": "{date} 頃の見込み",
"battery_fleet_predicted_trend": "この電池の放電傾向から予測: {date} 頃 ({confidence})",
"battery_fleet_rechargeable": "充電式:交換ではなく充電——買い物リストには載りません",
@@ -917,6 +956,24 @@
"service_record_print": "整備記録(PDF)",
"date": "日付",
"service_record_entries": "件",
"ref_number": "参照番号",
"print_options_title": "整備記録を印刷",
"print_layout": "レイアウト",
"print_layout_chronological": "時系列",
"print_layout_by_task": "タスク別",
"print_include": "含める",
"print_inc_readings": "検針値",
"print_inc_parts": "使用部品",
"print_inc_photos": "写真",
"print_inc_documents": "関連文書",
"print_inc_checklist": "チェックリスト",
"print_inc_notes": "メモ",
"print_inc_costs": "費用と所要時間",
"print_inc_person": "実施者",
"print_inc_refs": "参照番号",
"print_inc_qr": "タスクごとのQRコード",
"print_button": "印刷",
"report_scan_hint": "スキャンしてタスクを開く",
"completed_by": "実施者",
"date_from": "開始",
"date_to": "終了",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: 유효하지 않은 값입니다",
"feat_schedule_time": "시각 지정 일정",
"feat_schedule_time_desc": "작업이 자정이 아닌 지정한 시각에 기한 초과로 전환됩니다.",
"schedule_time_optional": "기한 시각 (선택)",
"schedule_time_toggle": "특정 시각에 기한",
"schedule_time_help": "비워두면 자정(기본값)입니다. HA 시간대 기준.",
"at_time": "시각",
"notes_optional": "메모 (선택)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— 태그 없음 —",
"dashboard": "대시보드",
"tab_today": "오늘",
"palette_placeholder": "객체와 작업 검색…",
"palette_placeholder": "대상, 작업, 부품, 문서, 메모 검색…",
"palette_no_results": "일치 항목 없음",
"palette_hint": "↑↓ 이동 · Enter 열기 · Esc 닫기",
"palette_hint": "↑↓ 이동 · Enter 열기 · Esc 닫기 · / 어디서든 검색 열기",
"search_open": "검색",
"search_group_parts": "부품",
"search_group_content": "문서 내용",
"search_group_history": "이력 메모",
"search_page": "{page}페이지",
"search_empty_hint": "대상, 작업, 부품, 문서, 메모를 검색하려면 입력하세요",
"search_searching": "문서와 메모 검색 중…",
"search_index_status": "전체 텍스트 검색: {total}개 중 {indexed}개 색인됨 · 텍스트 레이어 없음 {no_text}개 · 대기 {pending}개",
"today_all_caught_up": "모두 처리했습니다! 이번 주에 예정된 작업이 없습니다.",
"today_overdue": "기한 초과",
"today_due_today": "오늘 예정",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "소모품 „부족“ 임계값 (%)",
"settings_battery_low_percent": "배터리 „부족“ 임계값 (%)",
"settings_thresholds_hint": "새 추천 설정(남은 비율)과 자체 Battery Notes 임계값이 없는 배터리 목록 배터리의 기본값입니다. 기존 작업은 값을 유지합니다.",
"settings_value_out_of_range": "값은 {min}에서 {max} 사이여야 합니다",
"bn_summary": "{name}: 배터리 {n}개에 {pct} %",
"bn_floor_decides": "당신의 {floor} % 임계값이 모두에 적용됩니다. 배터리마다 두 임계값 중 높은 쪽이 적용됩니다.",
"bn_above_floor": "이는 당신의 {floor} % 임계값보다 높으므로, 기록된 모든 배터리는 {name}이(가) 결정합니다.",
@@ -481,6 +490,11 @@
"test_notification_success": "테스트 알림을 보냈습니다",
"test_notification_failed": "테스트 알림 전송 실패",
"notify_per_person": "사용자별 전송",
"member_avatars": "구성원 아바타",
"member_avatars_hint": "할당된 작업 옆에 표시됩니다 — 구성원 색상의 이니셜. 기본값은 이름에서 가져오며, 구성원을 구분하려면 이니셜을 직접 설정하거나 색상을 선택하세요.",
"member_initials": "이니셜",
"member_color": "색상",
"member_avatar_reset": "초기화",
"notify_no_own_device": "개인 기기 없음 — 가정 서비스 사용",
"settings_notify_due_soon": "기한 임박 시 알림",
"settings_notify_overdue": "기한 초과 시 알림",
@@ -546,6 +560,7 @@
"group_by_label": "그룹화",
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
"target_changes_help": "트리거가 발동하기까지 필요한 일치 전환 횟수입니다 (기본값: 1).",
"state_latch_help": "전환 1회로 설정하면 트리거는 래치가 됩니다. 엔티티가 '이후' 상태를 벗어나면 복구되고, '이전' 상태만 설정한 경우 그 상태로 돌아오면 복구됩니다.",
"for_minutes_state_help": "0은 모든 변화를 즉시 집계합니다. 분을 설정하면 새 상태가 먼저 그 시간 동안 유지되어야 하며, 짧은 깜빡임은 트리거되지도 집계되지도 않습니다.",
"qr_print_title": "QR 코드 인쇄",
"qr_print_desc": "잘라서 장비에 붙일 수 있는 QR 코드 인쇄 페이지를 생성합니다.",
@@ -683,8 +698,14 @@
"doc_delete_confirm": "\"{name}\"을(를) 삭제할까요?",
"doc_too_large": "파일이 너무 큽니다 (최대 25 MB).",
"doc_upload_failed": "업로드에 실패했습니다.",
"completion_photo_optional": "완료 사진 (선택)",
"add_photo": "사진 추가",
"completion_photos_optional": "완료 사진 (선택)",
"completion_photos": "완료 사진",
"choose_photos": "사진 선택",
"choose_photo": "사진 선택",
"photos_android_hint": "Android 앱은 선택할 때마다 사진 한 장씩 전달합니다. 선택할 때마다 추가됩니다.",
"add_photos": "사진 추가",
"photos_limit": "완료당 최대 {max}장",
"history_edit_photos_hint": "여기서 제거한 사진은 대상의 문서에 그대로 남습니다.",
"uploading": "업로드 중…",
"remove": "제거",
"doc_deduped": "이미 다른 곳에 저장됨 — 공유되어 추가 공간을 쓰지 않습니다.",
@@ -739,6 +760,14 @@
"reading_unit_help": "이 작업을 완료할 때 기록 값 옆에 표시됩니다.",
"reading_value_label": "검침 값",
"reading_label": "검침",
"readings_section": "검침 항목",
"readings_hint": "완료당 여러 개의 이름 있는 값 — 계량기마다 한 줄. 단일 값만 기록하려면 비워 두세요.",
"reading_name_label": "검침 항목 이름",
"reading_unit_short": "단위",
"reading_add": "검침 항목 추가",
"reading_duplicate_name": "이미 사용 중인 이름입니다",
"reading_last": "지난번: {value}",
"reading_below_last": "지난 검침값({value})보다 작습니다",
"settings_templates_label": "템플릿 갤러리",
"settings_templates_hint": "필요 없는 템플릿의 체크를 해제하세요 — \"템플릿에서\" 선택 목록(패널 및 설정 흐름)에서 사라집니다. 다른 변화는 없으며 언제든 다시 활성화할 수 있습니다.",
"worksheet": "작업 시트",
@@ -818,6 +847,12 @@
"settings_notify_scope": "특정 보기만 알림",
"settings_notify_scope_all": "모든 작업",
"settings_notify_scope_hint": "선택한 저장된 보기의 라벨/사용자 필터와 일치하는 작업만 알림을 보냅니다. 보기의 상태·정렬·그룹화는 여기서 무시됩니다.",
"settings_notify_rule": "나만의 알림 규칙",
"settings_notify_rule_hint": "모든 알림은 maintenance_supporter_notification 이벤트도 발생시킵니다(종류, 상태, 대상, 작업, 참조 번호, 우선순위, 딥 링크, 페이로드). 자동화로 라우팅하세요 — 예: Ticker, Telegram, Pushover.",
"settings_notify_event_only": "이벤트만 발생시키고 직접 보내지 않음",
"settings_notify_event_only_hint": "전달은 전적으로 자동화가 담당합니다. 반복 간격, 방해 금지 시간, 일일 한도는 여전히 이벤트 발생 시점에 적용됩니다.",
"settings_notify_extra_data": "추가 알림 데이터(템플릿)",
"settings_notify_extra_data_hint": "JSON 또는 YAML, Jinja 사용 가능. notify 호출의 data에 병합됩니다(사용자 키 우선). 변수: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. 테스트 보내기 버튼으로 확인하세요.",
"card_saved_view": "저장된 보기",
"card_saved_view_none": "없음",
"card_saved_view_help": "위 필터에 더해 보기의 상태·사용자·라벨 필터를 적용합니다. 보기의 정렬과 그룹화는 패널 표시 설정이며 카드에는 적용되지 않습니다.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "모두 교체됨으로 표시",
"battery_fleet_mark_one": "이 배터리를 교체됨으로 표시",
"battery_fleet_offline": "오프라인",
"battery_fleet_no_sensor": "센서 없음",
"battery_fleet_trigger_lost": "이 작업의 센서 트리거가 사라졌습니다 — 트리거도 자동 완료도 되지 않습니다.",
"battery_fleet_repair": "복구",
"battery_fleet_exclude": "플릿에서 제외",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "자동 검색이 놓친 배터리 센서를 선택하면 즉시 목록에 추가됩니다.",
"battery_fleet_track_self": "자체 충전 배터리 추적",
"battery_fleet_track_self_hint": "휴대폰, 청소기 등 스스로 충전되는 기기는 충전식으로 표시됩니다. 잔량이 낮으면 충전을 요청하며 새 전지를 요구하지 않습니다.",
"battery_fleet_due_without_sensor": "센서가 없는 배터리는 예측일이 지나면 교체 시기로 간주",
"battery_fleet_due_without_sensor_hint": "일부 기기는 배터리 잔량을 전혀 보고하지 않습니다. Battery Notes는 종류와 마지막 교체일만 알고 있습니다. 일반적인 수명이 지나면 그 배터리는 부족한 것으로 간주되어 배터리 관리 작업이 알려 줍니다. 교체됨으로 표시하면 예측이 초기화됩니다.",
"battery_fleet_status_low": "부족",
"battery_fleet_status_soon": "곧",
"battery_fleet_status_ok": "정상",
"battery_fleet_status_due": "교체 시기",
"battery_fleet_predicted_on": "{date}쯤 예상",
"battery_fleet_predicted_trend": "이 배터리의 방전 추세로 예측: 약 {date} ({confidence})",
"battery_fleet_rechargeable": "충전식: 교체 대신 충전 — 쇼핑 목록에 오르지 않습니다",
@@ -917,6 +956,24 @@
"service_record_print": "정비 기록(PDF)",
"date": "날짜",
"service_record_entries": "건",
"ref_number": "참조 번호",
"print_options_title": "정비 기록 인쇄",
"print_layout": "레이아웃",
"print_layout_chronological": "시간순",
"print_layout_by_task": "작업별",
"print_include": "포함",
"print_inc_readings": "측정값",
"print_inc_parts": "사용 부품",
"print_inc_photos": "사진",
"print_inc_documents": "연결된 문서",
"print_inc_checklist": "체크리스트",
"print_inc_notes": "메모",
"print_inc_costs": "비용 및 소요 시간",
"print_inc_person": "완료자",
"print_inc_refs": "참조 번호",
"print_inc_qr": "작업별 QR 코드",
"print_button": "인쇄",
"report_scan_hint": "스캔하여 작업 열기",
"completed_by": "수행자",
"date_from": "시작",
"date_to": "종료",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: ugyldig verdi",
"feat_schedule_time": "Planlegging med klokkeslett",
"feat_schedule_time_desc": "Oppgaver forfaller på et bestemt klokkeslett i stedet for ved midnatt.",
"schedule_time_optional": "Forfaller kl. (valgfritt)",
"schedule_time_toggle": "Forfaller på et bestemt klokkeslett",
"schedule_time_help": "Tom = midnatt (standard). HA-tidssone.",
"at_time": "kl.",
"notes_optional": "Notater (valgfritt)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Ingen brikke —",
"dashboard": "Dashbord",
"tab_today": "I dag",
"palette_placeholder": "Søk objekter og oppgaver…",
"palette_placeholder": "Søk objekter, oppgaver, reservedeler, dokumenter, notater…",
"palette_no_results": "Ingen treff",
"palette_hint": "↑↓ naviger · Enter åpne · Esc lukk",
"palette_hint": "↑↓ naviger · Enter åpne · Esc lukk · / åpner søket overalt",
"search_open": "Søk",
"search_group_parts": "Reservedeler",
"search_group_content": "I dokumenter",
"search_group_history": "Historikknotater",
"search_page": "Side {page}",
"search_empty_hint": "Skriv for å søke i objekter, oppgaver, reservedeler, dokumenter og notater",
"search_searching": "Søker i dokumenter og notater…",
"search_index_status": "Fulltekstsøk: {indexed} av {total} filer indeksert · {no_text} uten tekstlag · {pending} venter",
"today_all_caught_up": "Alt er ajour! Ingenting forfaller denne uken.",
"today_overdue": "Forfalt",
"today_due_today": "Forfaller i dag",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "Terskel „forbruksvare lav“ (%)",
"settings_battery_low_percent": "Terskel „batteri lavt“ (%)",
"settings_thresholds_hint": "Standardverdier for nye Foreslåtte oppsett (gjenværende prosent) og for flåtebatterier uten egen Battery Notes-terskel. Eksisterende oppgaver beholder verdien sin.",
"settings_value_out_of_range": "Verdien må være mellom {min} og {max}",
"bn_summary": "{name}: {pct} % for {n} batterier",
"bn_floor_decides": "Din terskel på {floor} % avgjør for alle; per batteri gjelder den høyeste av de to tersklene.",
"bn_above_floor": "Det er over din terskel på {floor} %, så {name} avgjør for alle noterte batterier.",
@@ -481,6 +490,11 @@
"test_notification_success": "Testvarsel sendt",
"test_notification_failed": "Testvarsel mislyktes",
"notify_per_person": "Levering per person",
"member_avatars": "Medlemmenes avatarer",
"member_avatars_hint": "Vises ved siden av tildelte oppgaver — initialer i medlemmets farge. Standard kommer fra navnet; angi egne initialer eller velg en farge for å skille medlemmer.",
"member_initials": "Initialer",
"member_color": "Farge",
"member_avatar_reset": "Tilbakestill",
"notify_no_own_device": "Ingen egen enhet — bruker husstandens tjeneste",
"settings_notify_due_soon": "Varsle når noe forfaller snart",
"settings_notify_overdue": "Varsle når noe er forfalt",
@@ -540,6 +554,7 @@
"group_by_label": "Grupper etter",
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
"target_changes_help": "Antall samsvarende overganger før utløseren aktiveres (standard: 1).",
"state_latch_help": "Med 1 overgang er utløseren en lås: den tilbakestilles når entiteten forlater Til-tilstanden — eller, hvis bare en Fra-tilstand er satt, når den går tilbake til den.",
"for_minutes_state_help": "0 teller hver endring umiddelbart. Med minutter må den nye tilstanden først holde seg — korte blink verken utløser eller telles da.",
"qr_print_title": "Skriv ut QR-koder",
"qr_print_desc": "Generer en utskrivbar side med QR-koder som kan klippes ut og festes på utstyret ditt.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Slette \"{name}\"?",
"doc_too_large": "Filen er for stor (maks. 25 MB).",
"doc_upload_failed": "Opplasting mislyktes.",
"completion_photo_optional": "Fullføringsbilde (valgfritt)",
"add_photo": "Legg til bilde",
"completion_photos_optional": "Bilder ved fullføring (valgfritt)",
"completion_photos": "Bilder ved fullføring",
"choose_photos": "Velg bilder",
"choose_photo": "Velg bilde",
"photos_android_hint": "Android-appen leverer ett bilde per valg — hvert valg legges til.",
"add_photos": "Legg til bilder",
"photos_limit": "Inntil {max} bilder per fullføring",
"history_edit_photos_hint": "Et bilde som fjernes her, blir liggende i objektets dokumenter.",
"uploading": "Laster opp…",
"remove": "Fjern",
"doc_deduped": "Allerede lagret et annet sted — delt, ingen ekstra plass.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Vises ved siden av den registrerte verdien når oppgaven fullføres.",
"reading_value_label": "Avlest verdi",
"reading_label": "Avlesning",
"readings_section": "Avlesninger",
"readings_hint": "Flere navngitte verdier per fullføring — én rad per måler. La stå tomt for én enkelt verdi.",
"reading_name_label": "Avlesningens navn",
"reading_unit_short": "Enhet",
"reading_add": "Legg til avlesning",
"reading_duplicate_name": "Navnet er allerede i bruk",
"reading_last": "sist: {value}",
"reading_below_last": "Lavere enn forrige avlesning ({value})",
"settings_templates_label": "Malgalleri",
"settings_templates_hint": "Fjern haken for maler du aldri trenger — de forsvinner fra \"Fra mal\"-velgerne (panel og config flow). Ellers endres ingenting; kan aktiveres igjen når som helst.",
"worksheet": "Arbeidsark",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Varsle kun for visning",
"settings_notify_scope_all": "Alle oppgaver",
"settings_notify_scope_hint": "Bare oppgaver som matcher etikett-/brukerfiltrene til valgt visning sender påminnelser. Visningens status, sortering og gruppering ignoreres her.",
"settings_notify_rule": "Din egen varslingsregel",
"settings_notify_rule_hint": "Hvert varsel utløser også hendelsen maintenance_supporter_notification (type, status, objekt, oppgave, referansenumre, prioritet, lenke, nyttelast). Rut den med en automasjon — f.eks. til Ticker, Telegram eller Pushover.",
"settings_notify_event_only": "Bare utløs hendelsen, send ingenting selv",
"settings_notify_event_only_hint": "Automasjonen din tar hele leveransen. Gjentakelsesintervaller, stille timer og dagstaket avgjør fortsatt når hendelsen utløses.",
"settings_notify_extra_data": "Ekstra varslingsdata (mal)",
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillatt; flettes inn i notify-kallets data (dine nøkler vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test med knappen Send test.",
"card_saved_view": "Lagret visning",
"card_saved_view_none": "Ingen",
"card_saved_view_help": "Bruker visningens status-, bruker- og etikettfiltre i tillegg til filtrene over. Visningens sortering og gruppering er panelvisningsinnstillinger og brukes ikke på kortet.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Merk alle byttet",
"battery_fleet_mark_one": "Merk dette batteriet byttet",
"battery_fleet_offline": "frakoblet",
"battery_fleet_no_sensor": "Ingen sensor",
"battery_fleet_trigger_lost": "Oppgavens sensorutløser er tapt — den utløses ikke og fullføres ikke automatisk.",
"battery_fleet_repair": "Reparer",
"battery_fleet_exclude": "Utelukk fra flåten",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Velg en batterisensor som den automatiske gjenkjenningen overså — den blir med i listen umiddelbart.",
"battery_fleet_track_self": "Spor selvladende batterier",
"battery_fleet_track_self_hint": "Telefoner, støvsugere og andre selvladende enheter vises som oppladbare — ved lavt nivå bes det om lading, aldri om nye batterier.",
"battery_fleet_due_without_sensor": "Behandle en passert prognose som forfalt når et batteri mangler sensor",
"battery_fleet_due_without_sensor_hint": "Noen enheter rapporterer ikke batterinivå i det hele tatt — Battery Notes kjenner bare typen og siste bytte. Når den typiske levetiden er passert, regnes et slikt batteri som lavt og flåteoppgaven minner deg på det; å merke det som byttet nullstiller prognosen.",
"battery_fleet_status_low": "Lav",
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I orden",
"battery_fleet_status_due": "Forfalt",
"battery_fleet_predicted_on": "Forventes rundt {date}",
"battery_fleet_predicted_trend": "Forutsagt ut fra batteriets utladingstrend: rundt {date} ({confidence})",
"battery_fleet_rechargeable": "Oppladbart: lad i stedet for å bytte — aldri på handlelisten",
@@ -917,6 +956,24 @@
"service_record_print": "Servicehefte (PDF)",
"date": "Dato",
"service_record_entries": "oppføringer",
"ref_number": "Referansenummer",
"print_options_title": "Skriv ut servicehefte",
"print_layout": "Oppsett",
"print_layout_chronological": "Kronologisk",
"print_layout_by_task": "Per oppgave",
"print_include": "Ta med",
"print_inc_readings": "Avlesninger",
"print_inc_parts": "Brukte deler",
"print_inc_photos": "Bilder",
"print_inc_documents": "Tilknyttede dokumenter",
"print_inc_checklist": "Sjekkliste",
"print_inc_notes": "Notater",
"print_inc_costs": "Kostnad og varighet",
"print_inc_person": "Utført av",
"print_inc_refs": "Referansenumre",
"print_inc_qr": "QR-kode per oppgave",
"print_button": "Skriv ut",
"report_scan_hint": "Skann for å åpne oppgaven",
"completed_by": "Utført av",
"date_from": "Fra",
"date_to": "Til",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: ongeldige waarde",
"feat_schedule_time": "Tijd-van-dag-planning",
"feat_schedule_time_desc": "Taken vervallen op een specifieke tijd in plaats van middernacht.",
"schedule_time_optional": "Vervaldagstijd (optioneel)",
"schedule_time_toggle": "Vervalt op een vast tijdstip",
"schedule_time_help": "Leeg = middernacht (standaard). HA-tijdzone.",
"at_time": "om",
"notes_optional": "Notities (optioneel)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Geen tag —",
"dashboard": "Dashboard",
"tab_today": "Vandaag",
"palette_placeholder": "Objecten en taken zoeken…",
"palette_placeholder": "Objecten, taken, onderdelen, documenten, notities zoeken…",
"palette_no_results": "Geen resultaten",
"palette_hint": "↑↓ navigeren · Enter openen · Esc sluiten",
"palette_hint": "↑↓ navigeren · Enter openen · Esc sluiten · / opent de zoekfunctie overal",
"search_open": "Zoeken",
"search_group_parts": "Onderdelen",
"search_group_content": "In documenten",
"search_group_history": "Notities in historie",
"search_page": "Pagina {page}",
"search_empty_hint": "Typ om objecten, taken, onderdelen, documenten en notities te doorzoeken",
"search_searching": "Documenten en notities doorzoeken…",
"search_index_status": "Volledige tekst zoeken: {indexed} van {total} bestanden geïndexeerd · {no_text} zonder tekstlaag · {pending} in wachtrij",
"today_all_caught_up": "Helemaal bij! Niets te doen deze week.",
"today_overdue": "Achterstallig",
"today_due_today": "Vandaag te doen",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Drempel „verbruiksartikel laag“ (%)",
"settings_battery_low_percent": "Drempel „batterij laag“ (%)",
"settings_thresholds_hint": "Standaardwaarden voor nieuwe Voorgestelde installaties (resterend percentage) en voor vlootbatterijen zonder eigen Battery Notes-drempel. Bestaande taken behouden hun waarde.",
"settings_value_out_of_range": "De waarde moet tussen {min} en {max} liggen",
"bn_summary": "{name}: {pct} % voor {n} batterijen",
"bn_floor_decides": "Jouw drempel van {floor} % beslist voor allemaal; per batterij telt de hoogste van de twee drempels.",
"bn_above_floor": "Dat ligt boven jouw drempel van {floor} %, dus {name} beslist voor alle genoteerde batterijen.",
@@ -479,6 +488,11 @@
"test_notification_success": "Testmelding verzonden",
"test_notification_failed": "Testmelding mislukt",
"notify_per_person": "Bezorging per persoon",
"member_avatars": "Avatars van leden",
"member_avatars_hint": "Wordt naast toegewezen taken getoond — initialen in de kleur van het lid. Standaard uit de naam; kies eigen initialen of een kleur om leden uit elkaar te houden.",
"member_initials": "Initialen",
"member_color": "Kleur",
"member_avatar_reset": "Herstellen",
"notify_no_own_device": "Geen eigen apparaat — gebruikt de huishoudelijke dienst",
"settings_notify_due_soon": "Melding bij bijna verlopen",
"settings_notify_overdue": "Melding bij achterstallig",
@@ -538,6 +552,7 @@
"group_by_label": "Groeperen op",
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
"target_changes_help": "Aantal overeenkomende overgangen voordat de trigger wordt geactiveerd (standaard: 1).",
"state_latch_help": "Bij 1 overgang werkt de trigger als vergrendeling: hij herstelt zodra de entiteit de Naar-status verlaat — of, als alleen een Van-status is ingesteld, zodra ze daarnaar terugkeert.",
"for_minutes_state_help": "0 telt elke wijziging direct. Met minuten moet de nieuwe status eerst zo lang aanhouden — kort flikkeren triggert dan niet en telt niet mee.",
"qr_print_title": "QR-codes afdrukken",
"qr_print_desc": "Genereer een afdrukpagina met QR-codes om uit te knippen en op je apparaten te plakken.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "\"{name}\" verwijderen?",
"doc_too_large": "Bestand is te groot (max. 25 MB).",
"doc_upload_failed": "Uploaden mislukt.",
"completion_photo_optional": "Voltooiingsfoto (optioneel)",
"add_photo": "Foto toevoegen",
"completion_photos_optional": "Foto's bij afronding (optioneel)",
"completion_photos": "Foto's bij afronding",
"choose_photos": "Foto's kiezen",
"choose_photo": "Foto kiezen",
"photos_android_hint": "De Android-app geeft één foto per keuze door — elke keuze wordt toegevoegd.",
"add_photos": "Foto's toevoegen",
"photos_limit": "Maximaal {max} foto's per afronding",
"history_edit_photos_hint": "Een hier verwijderde foto blijft bewaard in de documenten van het object.",
"uploading": "Uploaden…",
"remove": "Weghalen",
"doc_deduped": "Al elders opgeslagen — gedeeld, geen extra ruimte.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Wordt naast de vastgelegde waarde getoond bij het afronden van deze taak.",
"reading_value_label": "Meterstand",
"reading_label": "Meting",
"readings_section": "Meterstanden",
"readings_hint": "Meerdere benoemde waarden per afronding — één regel per meter. Leeg laten voor één enkele waarde.",
"reading_name_label": "Naam meterstand",
"reading_unit_short": "Eenheid",
"reading_add": "Meterstand toevoegen",
"reading_duplicate_name": "Naam al in gebruik",
"reading_last": "laatst: {value}",
"reading_below_last": "Lager dan de vorige meterstand ({value})",
"settings_templates_label": "Sjabloongalerij",
"settings_templates_hint": "Vink sjablonen uit die je nooit nodig hebt — ze verdwijnen uit de \"Vanuit sjabloon\"-keuzes (paneel en config flow). Verder verandert er niets; altijd weer in te schakelen.",
"worksheet": "Werkblad",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Alleen melden voor weergave",
"settings_notify_scope_all": "Alle taken",
"settings_notify_scope_hint": "Alleen taken die overeenkomen met de label-/gebruikersfilters van de gekozen weergave sturen herinneringen. Status, sortering en groepering van de weergave worden hier genegeerd.",
"settings_notify_rule": "Eigen meldingsregel",
"settings_notify_rule_hint": "Elke melding vuurt ook het event maintenance_supporter_notification af (soort, status, object, taak, referentienummers, prioriteit, deep link, payload). Stuur het door met een automatisering — bijv. naar Ticker, Telegram of Pushover.",
"settings_notify_event_only": "Alleen het event afvuren, zelf niets verzenden",
"settings_notify_event_only_hint": "Je automatisering verzorgt de volledige bezorging. Herhaalintervallen, stille uren en het daglimiet bepalen nog steeds wanneer het event afgaat.",
"settings_notify_extra_data": "Extra meldingsgegevens (sjabloon)",
"settings_notify_extra_data_hint": "JSON of YAML, Jinja toegestaan; wordt samengevoegd met de data van de notify-aanroep (jouw sleutels winnen). Variabelen: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test met de knop Test verzenden.",
"card_saved_view": "Opgeslagen weergave",
"card_saved_view_none": "Geen",
"card_saved_view_help": "Past de status-, gebruikers- en labelfilters van de weergave toe bovenop de bovenstaande filters. Sortering en groepering van de weergave zijn paneelweergave-instellingen en gelden niet voor de kaart.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Alles gemarkeerd als vervangen",
"battery_fleet_mark_one": "Deze batterij als vervangen markeren",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Geen sensor",
"battery_fleet_trigger_lost": "De sensortrigger van deze taak is verloren gegaan — deze wordt niet geactiveerd of automatisch voltooid.",
"battery_fleet_repair": "Repareren",
"battery_fleet_exclude": "Uitsluiten van de vloot",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Kies een batterijsensor die de automatische detectie heeft gemist — hij verschijnt direct in de lijst.",
"battery_fleet_track_self": "Zelfopladende batterijen volgen",
"battery_fleet_track_self_hint": "Telefoons, stofzuigers en andere zelfopladende apparaten verschijnen als oplaadbaar — bij een lage stand wordt om opladen gevraagd, nooit om nieuwe batterijen.",
"battery_fleet_due_without_sensor": "Een verstreken prognose als aan de beurt beschouwen als een batterij geen sensor heeft",
"battery_fleet_due_without_sensor_hint": "Sommige apparaten melden helemaal geen batterijniveau — Battery Notes kent alleen het type en de laatste vervanging. Zodra de gebruikelijke levensduur is verstreken, telt zo'n batterij als leeg en herinnert de vloottaak je eraan; als vervangen markeren zet de prognose terug.",
"battery_fleet_status_low": "Laag",
"battery_fleet_status_soon": "Binnenkort",
"battery_fleet_status_ok": "In orde",
"battery_fleet_status_due": "Aan de beurt",
"battery_fleet_predicted_on": "Verwacht rond {date}",
"battery_fleet_predicted_trend": "Voorspeld uit de ontladingstrend van deze batterij: rond {date} ({confidence})",
"battery_fleet_rechargeable": "Oplaadbaar: opladen in plaats van vervangen — nooit op het boodschappenlijstje",
@@ -917,6 +956,24 @@
"service_record_print": "Onderhoudsboekje (PDF)",
"date": "Datum",
"service_record_entries": "items",
"ref_number": "Referentienummer",
"print_options_title": "Serviceoverzicht afdrukken",
"print_layout": "Indeling",
"print_layout_chronological": "Chronologisch",
"print_layout_by_task": "Per taak",
"print_include": "Opnemen",
"print_inc_readings": "Meterstanden",
"print_inc_parts": "Gebruikte onderdelen",
"print_inc_photos": "Foto's",
"print_inc_documents": "Gekoppelde documenten",
"print_inc_checklist": "Controlelijst",
"print_inc_notes": "Notities",
"print_inc_costs": "Kosten en duur",
"print_inc_person": "Uitgevoerd door",
"print_inc_refs": "Referentienummers",
"print_inc_qr": "QR-code per taak",
"print_button": "Afdrukken",
"report_scan_hint": "Scan om de taak te openen",
"completed_by": "Voltooid door",
"date_from": "Van",
"date_to": "Tot",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: nieprawidłowa wartość",
"feat_schedule_time": "Harmonogram według pory dnia",
"feat_schedule_time_desc": "Zadania stają się zaległe o określonej porze dnia zamiast o północy.",
"schedule_time_optional": "Termin o godzinie (opcjonalne)",
"schedule_time_toggle": "Termin o określonej godzinie",
"schedule_time_help": "Puste = północ (domyślnie). Strefa czasowa HA.",
"at_time": "o",
"notes_optional": "Notatki (opcjonalne)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Brak tagu —",
"dashboard": "Pulpit",
"tab_today": "Dziś",
"palette_placeholder": "Szukaj obiektów i zadań…",
"palette_placeholder": "Szukaj obiektów, zadań, części, dokumentów, notatek…",
"palette_no_results": "Brak wyników",
"palette_hint": "↑↓ nawigacja · Enter otwórz · Esc zamknij",
"palette_hint": "↑↓ nawigacja · Enter otwórz · Esc zamknij · / otwiera wyszukiwanie wszędzie",
"search_open": "Szukaj",
"search_group_parts": "Części",
"search_group_content": "W dokumentach",
"search_group_history": "Notatki z historii",
"search_page": "Strona {page}",
"search_empty_hint": "Wpisz tekst, aby przeszukać obiekty, zadania, części, dokumenty i notatki",
"search_searching": "Przeszukiwanie dokumentów i notatek…",
"search_index_status": "Wyszukiwanie pełnotekstowe: {indexed} z {total} plików zindeksowanych · {no_text} bez warstwy tekstu · {pending} oczekujących",
"today_all_caught_up": "Wszystko zrobione! Nic w tym tygodniu.",
"today_overdue": "Zaległe",
"today_due_today": "Na dziś",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Próg „materiał eksploatacyjny niski” (%)",
"settings_battery_low_percent": "Próg „bateria słaba” (%)",
"settings_thresholds_hint": "Wartości domyślne dla nowych Sugerowanych konfiguracji (procent pozostały) i dla baterii floty bez własnego progu Battery Notes. Istniejące zadania zachowują swoją wartość.",
"settings_value_out_of_range": "Wartość musi mieścić się w zakresie od {min} do {max}",
"bn_summary": "{name}: {pct} % dla {n} baterii",
"bn_floor_decides": "Twój próg {floor} % decyduje dla wszystkich; dla każdej baterii liczy się wyższy z dwóch progów.",
"bn_above_floor": "To powyżej twojego progu {floor} %, więc o wszystkich odnotowanych bateriach decyduje {name}.",
@@ -479,6 +488,11 @@
"test_notification_success": "Powiadomienie testowe wysłane",
"test_notification_failed": "Powiadomienie testowe nie powiodło się",
"notify_per_person": "Dostarczanie dla każdej osoby",
"member_avatars": "Awatary domowników",
"member_avatars_hint": "Wyświetlane obok przypisanych zadań — inicjały w kolorze domownika. Domyślnie z imienia; ustaw własne inicjały lub wybierz kolor, aby odróżnić domowników.",
"member_initials": "Inicjały",
"member_color": "Kolor",
"member_avatar_reset": "Resetuj",
"notify_no_own_device": "Brak własnego urządzenia — używa usługi domowej",
"settings_notify_due_soon": "Powiadom gdy wkrótce",
"settings_notify_overdue": "Powiadom gdy zaległe",
@@ -538,6 +552,7 @@
"group_by_label": "Grupuj wg",
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
"target_changes_help": "Liczba pasujących przejść przed wyzwoleniem (domyślnie: 1).",
"state_latch_help": "Przy 1 przejściu wyzwalacz działa jak zatrzask: resetuje się, gdy encja opuści stan docelowy — lub, gdy ustawiono tylko stan początkowy, gdy do niego wróci.",
"for_minutes_state_help": "0 liczy każdą zmianę od razu. Z minutami nowy stan musi się najpierw utrzymać — krótkie mignięcia wtedy nie wyzwalają i nie liczą się.",
"qr_print_title": "Drukuj kody QR",
"qr_print_desc": "Wygeneruj stronę do druku z kodami QR do wycięcia i naklejenia na sprzęcie.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Usunąć „{name}”?",
"doc_too_large": "Plik jest za duży (maks. 25 MB).",
"doc_upload_failed": "Przesyłanie nie powiodło się.",
"completion_photo_optional": "Zdjęcie ukończenia (opcjonalnie)",
"add_photo": "Dodaj zdjęcie",
"completion_photos_optional": "Zdjęcia z wykonania (opcjonalnie)",
"completion_photos": "Zdjęcia z wykonania",
"choose_photos": "Wybierz zdjęcia",
"choose_photo": "Wybierz zdjęcie",
"photos_android_hint": "Aplikacja na Androida przekazuje jedno zdjęcie na wybór — każdy wybór jest dodawany.",
"add_photos": "Dodaj zdjęcia",
"photos_limit": "Maksymalnie {max} zdjęć na wykonanie",
"history_edit_photos_hint": "Zdjęcie usunięte tutaj pozostaje w dokumentach obiektu.",
"uploading": "Przesyłanie…",
"remove": "Usuń",
"doc_deduped": "Już zapisany gdzie indziej — współdzielony, bez dodatkowego miejsca.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Wyświetlana obok zarejestrowanej wartości przy ukończeniu tego zadania.",
"reading_value_label": "Wartość odczytu",
"reading_label": "Odczyt",
"readings_section": "Odczyty",
"readings_hint": "Kilka nazwanych wartości na wykonanie — jeden wiersz na licznik. Zostaw puste, aby zapisać jedną wartość.",
"reading_name_label": "Nazwa odczytu",
"reading_unit_short": "Jednostka",
"reading_add": "Dodaj odczyt",
"reading_duplicate_name": "Nazwa już użyta",
"reading_last": "ostatnio: {value}",
"reading_below_last": "Mniej niż ostatni odczyt ({value})",
"settings_templates_label": "Galeria szablonów",
"settings_templates_hint": "Odznacz szablony, których nigdy nie użyjesz — znikną z wyboru „Z szablonu\" (panel i config flow). Nic więcej się nie zmienia; można je włączyć ponownie w każdej chwili.",
"worksheet": "Karta pracy",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Powiadamiaj tylko dla widoku",
"settings_notify_scope_all": "Wszystkie zadania",
"settings_notify_scope_hint": "Przypomnienia wysyłają tylko zadania pasujące do filtrów etykiety/użytkownika wybranego widoku. Status, sortowanie i grupowanie widoku są tu ignorowane.",
"settings_notify_rule": "Własna reguła powiadomień",
"settings_notify_rule_hint": "Każde powiadomienie wyzwala dodatkowo zdarzenie maintenance_supporter_notification (rodzaj, status, obiekt, zadanie, numery referencyjne, priorytet, link, dane). Przekieruj je automatyzacją — np. do Tickera, Telegrama lub Pushover.",
"settings_notify_event_only": "Tylko zdarzenie, nic nie wysyłaj",
"settings_notify_event_only_hint": "Twoja automatyzacja odpowiada za całe dostarczanie. Odstępy powtórzeń, ciche godziny i dzienny limit nadal decydują, kiedy zdarzenie się wyzwala.",
"settings_notify_extra_data": "Dodatkowe dane powiadomienia (szablon)",
"settings_notify_extra_data_hint": "JSON lub YAML, Jinja dozwolone; scalane z data wywołania notify (Twoje klucze wygrywają). Zmienne: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Sprawdź przyciskiem Wyślij test.",
"card_saved_view": "Zapisany widok",
"card_saved_view_none": "Brak",
"card_saved_view_help": "Stosuje filtry statusu, użytkownika i etykiety widoku oprócz powyższych filtrów. Sortowanie i grupowanie widoku to ustawienia wyświetlania panelu i nie są stosowane na karcie.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Oznacz wszystkie jako wymienione",
"battery_fleet_mark_one": "Oznacz tę baterię jako wymienioną",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Brak czujnika",
"battery_fleet_trigger_lost": "Wyzwalacz czujnika tego zadania został utracony — nie uruchomi się ani nie zakończy automatycznie.",
"battery_fleet_repair": "Napraw",
"battery_fleet_exclude": "Wyklucz z floty",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Wybierz czujnik baterii pominięty przez automatyczne wykrywanie — od razu trafi na listę.",
"battery_fleet_track_self": "Śledź baterie samoładujące",
"battery_fleet_track_self_hint": "Telefony, odkurzacze i inne samoładujące się urządzenia pojawiają się jako akumulatory — przy niskim poziomie proszą o ładowanie, nigdy o nowe baterie.",
"battery_fleet_due_without_sensor": "Traktuj przekroczoną prognozę jako termin wymiany, gdy bateria nie ma czujnika",
"battery_fleet_due_without_sensor_hint": "Niektóre urządzenia w ogóle nie zgłaszają poziomu baterii — Battery Notes zna tylko typ i ostatnią wymianę. Po upływie typowej żywotności taka bateria liczy się jako słaba i zadanie floty przypomni ci o tym; oznaczenie jako wymienionej resetuje prognozę.",
"battery_fleet_status_low": "Niski",
"battery_fleet_status_soon": "Wkrótce",
"battery_fleet_status_ok": "W porządku",
"battery_fleet_status_due": "Do wymiany",
"battery_fleet_predicted_on": "Przewidywane około {date}",
"battery_fleet_predicted_trend": "Prognoza na podstawie trendu rozładowania tej baterii: około {date} ({confidence})",
"battery_fleet_rechargeable": "Akumulator: ładowanie zamiast wymiany — nigdy na liście zakupów",
@@ -917,6 +956,24 @@
"service_record_print": "Książka serwisowa (PDF)",
"date": "Data",
"service_record_entries": "wpisów",
"ref_number": "Numer referencyjny",
"print_options_title": "Drukuj książkę serwisową",
"print_layout": "Układ",
"print_layout_chronological": "Chronologicznie",
"print_layout_by_task": "Według zadania",
"print_include": "Uwzględnij",
"print_inc_readings": "Odczyty",
"print_inc_parts": "Zużyte części",
"print_inc_photos": "Zdjęcia",
"print_inc_documents": "Powiązane dokumenty",
"print_inc_checklist": "Lista kontrolna",
"print_inc_notes": "Notatki",
"print_inc_costs": "Koszt i czas",
"print_inc_person": "Wykonał",
"print_inc_refs": "Numery referencyjne",
"print_inc_qr": "Kod QR dla każdego zadania",
"print_button": "Drukuj",
"report_scan_hint": "Zeskanuj, aby otworzyć zadanie",
"completed_by": "Wykonane przez",
"date_from": "Od",
"date_to": "Do",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: valor inválido",
"feat_schedule_time": "Agendamento por horário",
"feat_schedule_time_desc": "As tarefas ficam atrasadas em um horário específico do dia, em vez de à meia-noite.",
"schedule_time_optional": "Vence às (opcional)",
"schedule_time_toggle": "Vence em um horário específico",
"schedule_time_help": "Vazio = meia-noite (padrão). Fuso horário do HA.",
"at_time": "às",
"notes_optional": "Observações (opcional)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Sem tag —",
"dashboard": "Painel",
"tab_today": "Hoje",
"palette_placeholder": "Pesquisar objetos e tarefas…",
"palette_placeholder": "Pesquisar objetos, tarefas, peças, documentos, notas…",
"palette_no_results": "Nenhum resultado",
"palette_hint": "↑↓ para navegar · Enter para abrir · Esc para fechar",
"palette_hint": "↑↓ navegar · Enter abrir · Esc fechar · / abre a pesquisa em qualquer lugar",
"search_open": "Pesquisar",
"search_group_parts": "Peças",
"search_group_content": "Nos documentos",
"search_group_history": "Notas do histórico",
"search_page": "Página {page}",
"search_empty_hint": "Digite para pesquisar objetos, tarefas, peças, documentos e notas",
"search_searching": "Pesquisando documentos e notas…",
"search_index_status": "Busca de texto completo: {indexed} de {total} arquivos indexados · {no_text} sem camada de texto · {pending} pendentes",
"today_all_caught_up": "Tudo em dia! Nada vence esta semana.",
"today_overdue": "Atrasadas",
"today_due_today": "Vencem hoje",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "Limite „consumível baixo“ (%)",
"settings_battery_low_percent": "Limite „pilha fraca“ (%)",
"settings_thresholds_hint": "Padrões para novas Configurações sugeridas (porcentagem restante) e para pilhas da frota sem limite próprio do Battery Notes. As tarefas existentes mantêm seu valor.",
"settings_value_out_of_range": "O valor deve estar entre {min} e {max}",
"bn_summary": "{name}: {pct} % para {n} pilhas",
"bn_floor_decides": "Seu limite de {floor} % decide para todas; por pilha vale o maior dos dois limites.",
"bn_above_floor": "Está acima do seu limite de {floor} %, então o {name} decide para todas as pilhas anotadas.",
@@ -481,6 +490,11 @@
"test_notification_success": "Notificação de teste enviada",
"test_notification_failed": "Falha na notificação de teste",
"notify_per_person": "Entrega por pessoa",
"member_avatars": "Avatares dos membros",
"member_avatars_hint": "Exibido ao lado das tarefas atribuídas — iniciais na cor do membro. O padrão vem do nome; defina iniciais próprias ou escolha uma cor para distinguir os membros.",
"member_initials": "Iniciais",
"member_color": "Cor",
"member_avatar_reset": "Redefinir",
"notify_no_own_device": "Sem dispositivo próprio — usa o serviço da casa",
"settings_notify_due_soon": "Notificar quando vencer em breve",
"settings_notify_overdue": "Notificar quando atrasada",
@@ -546,6 +560,7 @@
"group_by_label": "Agrupar por",
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
"target_changes_help": "Número de transições correspondentes antes de o gatilho disparar (padrão: 1).",
"state_latch_help": "Com 1 transição o gatilho é uma trava: ele se recupera quando a entidade sai do estado de destino — ou, se houver apenas um estado de origem, quando volta a ele.",
"for_minutes_state_help": "0 conta cada mudança imediatamente. Com minutos, o novo estado deve se manter primeiro — uma oscilação breve então não dispara nem conta.",
"qr_print_title": "Imprimir códigos QR",
"qr_print_desc": "Gere uma página imprimível de códigos QR para recortar e colar nos seus equipamentos.",
@@ -683,8 +698,14 @@
"doc_delete_confirm": "Excluir \"{name}\"?",
"doc_too_large": "O arquivo é muito grande (máx. 25 MB).",
"doc_upload_failed": "Falha no envio.",
"completion_photo_optional": "Foto da conclusão (opcional)",
"add_photo": "Adicionar foto",
"completion_photos_optional": "Fotos de conclusão (opcional)",
"completion_photos": "Fotos de conclusão",
"choose_photos": "Escolher fotos",
"choose_photo": "Escolher foto",
"photos_android_hint": "O app Android entrega uma foto por seleção — cada seleção é adicionada.",
"add_photos": "Adicionar fotos",
"photos_limit": "Até {max} fotos por conclusão",
"history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.",
"uploading": "Enviando…",
"remove": "Remover",
"doc_deduped": "Já armazenado em outro lugar — compartilhado, sem uso de espaço extra.",
@@ -739,6 +760,14 @@
"reading_unit_help": "Exibida ao lado do valor registrado ao concluir esta tarefa.",
"reading_value_label": "Valor da leitura",
"reading_label": "Leitura",
"readings_section": "Leituras",
"readings_hint": "Vários valores nomeados por conclusão — uma linha por medidor. Deixe vazio para um único valor.",
"reading_name_label": "Nome da leitura",
"reading_unit_short": "Unidade",
"reading_add": "Adicionar leitura",
"reading_duplicate_name": "Nome já em uso",
"reading_last": "último: {value}",
"reading_below_last": "Menor que a última leitura ({value})",
"settings_templates_label": "Galeria de modelos",
"settings_templates_hint": "Desmarque os modelos de que você nunca precisará — eles somem dos seletores \"A partir de modelo\" (painel e fluxo de configuração). Nada mais muda; você pode reativá-los a qualquer momento.",
"worksheet": "Folha de trabalho",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Notificar apenas para a visualização",
"settings_notify_scope_all": "Todas as tarefas",
"settings_notify_scope_hint": "Apenas tarefas que correspondem aos filtros de etiqueta/usuário da visualização salva selecionada enviam lembretes. Status, ordenação e agrupamento da visualização são ignorados aqui.",
"settings_notify_rule": "Sua regra de notificação",
"settings_notify_rule_hint": "Cada notificação também dispara o evento maintenance_supporter_notification (tipo, status, objeto, tarefa, números de referência, prioridade, link, payload). Roteie-o com uma automação — por ex. para Ticker, Telegram ou Pushover.",
"settings_notify_event_only": "Apenas disparar o evento, não enviar nada",
"settings_notify_event_only_hint": "Sua automação cuida de toda a entrega. Intervalos de repetição, horário silencioso e o limite diário continuam valendo para quando o evento dispara.",
"settings_notify_extra_data": "Dados extras de notificação (modelo)",
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; mesclado nos data da chamada notify (suas chaves vencem). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.",
"card_saved_view": "Visualização salva",
"card_saved_view_none": "Nenhuma",
"card_saved_view_help": "Aplica os filtros de status, usuário e etiqueta da visualização por cima dos filtros acima. A ordenação e o agrupamento da visualização são configurações de exibição do painel e não se aplicam ao cartão.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Marcar todas como trocadas",
"battery_fleet_mark_one": "Marcar esta bateria como trocada",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Sem sensor",
"battery_fleet_trigger_lost": "O gatilho do sensor desta tarefa foi perdido — ela não será acionada nem concluída automaticamente.",
"battery_fleet_repair": "Reparar",
"battery_fleet_exclude": "Remover da frota",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Escolha um sensor de bateria que a detecção automática não encontrou — ele entra na lista imediatamente.",
"battery_fleet_track_self": "Acompanhar baterias de carregamento automático",
"battery_fleet_track_self_hint": "Telefones, aspiradores e outros dispositivos que se recarregam sozinhos aparecem como recarregáveis — com carga baixa pedem uma recarga, nunca pilhas novas.",
"battery_fleet_due_without_sensor": "Tratar uma previsão vencida como pendente quando uma bateria não tem sensor",
"battery_fleet_due_without_sensor_hint": "Alguns dispositivos não informam nenhum nível de bateria — o Battery Notes só conhece o tipo e a última troca. Passada a vida útil típica, essa bateria conta como fraca e a tarefa da frota lembra você; marcá-la como trocada reinicia a previsão.",
"battery_fleet_status_low": "Fraca",
"battery_fleet_status_soon": "Em breve",
"battery_fleet_status_ok": "Em bom estado",
"battery_fleet_status_due": "Vencida",
"battery_fleet_predicted_on": "Previsto por volta de {date}",
"battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta bateria: por volta de {date} ({confidence})",
"battery_fleet_rechargeable": "Recarregável: recarregue em vez de substituir — nunca na lista de compras",
@@ -917,6 +956,24 @@
"service_record_print": "Registro de manutenção (PDF)",
"date": "Data",
"service_record_entries": "registros",
"ref_number": "Número de referência",
"print_options_title": "Imprimir o registro de serviço",
"print_layout": "Disposição",
"print_layout_chronological": "Cronológico",
"print_layout_by_task": "Por tarefa",
"print_include": "Incluir",
"print_inc_readings": "Leituras",
"print_inc_parts": "Peças usadas",
"print_inc_photos": "Fotos",
"print_inc_documents": "Documentos vinculados",
"print_inc_checklist": "Lista de verificação",
"print_inc_notes": "Notas",
"print_inc_costs": "Custo e duração",
"print_inc_person": "Concluído por",
"print_inc_refs": "Números de referência",
"print_inc_qr": "Código QR por tarefa",
"print_button": "Imprimir",
"report_scan_hint": "Escaneie para abrir a tarefa",
"completed_by": "Concluído por",
"date_from": "De",
"date_to": "Até",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: valor inválido",
"feat_schedule_time": "Agendamento por hora",
"feat_schedule_time_desc": "Tarefas vencem em um horário específico em vez de meia-noite.",
"schedule_time_optional": "Vence às (opcional)",
"schedule_time_toggle": "Vence a uma hora específica",
"schedule_time_help": "Vazio = meia-noite (padrão). Fuso horário HA.",
"at_time": "às",
"notes_optional": "Notas (opcional)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Sem etiqueta —",
"dashboard": "Painel",
"tab_today": "Hoje",
"palette_placeholder": "Pesquisar objetos e tarefas…",
"palette_placeholder": "Pesquisar objetos, tarefas, peças, documentos, notas…",
"palette_no_results": "Sem resultados",
"palette_hint": "↑↓ navegar · Enter abrir · Esc fechar",
"palette_hint": "↑↓ navegar · Enter abrir · Esc fechar · / abre a pesquisa em qualquer lado",
"search_open": "Pesquisar",
"search_group_parts": "Peças",
"search_group_content": "Nos documentos",
"search_group_history": "Notas do histórico",
"search_page": "Página {page}",
"search_empty_hint": "Escreva para pesquisar objetos, tarefas, peças, documentos e notas",
"search_searching": "A pesquisar documentos e notas…",
"search_index_status": "Pesquisa de texto integral: {indexed} de {total} ficheiros indexados · {no_text} sem camada de texto · {pending} pendentes",
"today_all_caught_up": "Tudo em dia! Nada para esta semana.",
"today_overdue": "Atrasadas",
"today_due_today": "Vence hoje",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Limiar „consumível baixo“ (%)",
"settings_battery_low_percent": "Limiar „pilha fraca“ (%)",
"settings_thresholds_hint": "Predefinições para novas Configurações sugeridas (percentagem restante) e para pilhas da frota sem limiar Battery Notes próprio. As tarefas existentes mantêm o seu valor.",
"settings_value_out_of_range": "O valor tem de estar entre {min} e {max}",
"bn_summary": "{name}: {pct} % para {n} pilhas",
"bn_floor_decides": "O teu limiar de {floor} % decide para todas; por pilha conta o mais alto dos dois limiares.",
"bn_above_floor": "Está acima do teu limiar de {floor} %, por isso o {name} decide para todas as pilhas anotadas.",
@@ -479,6 +488,11 @@
"test_notification_success": "Notificação de teste enviada",
"test_notification_failed": "Falha na notificação de teste",
"notify_per_person": "Entrega por pessoa",
"member_avatars": "Avatares dos membros",
"member_avatars_hint": "Mostrado junto às tarefas atribuídas — iniciais na cor do membro. Por defeito vêm do nome; defina iniciais próprias ou escolha uma cor para distinguir os membros.",
"member_initials": "Iniciais",
"member_color": "Cor",
"member_avatar_reset": "Repor",
"notify_no_own_device": "Sem dispositivo próprio — usa o serviço da casa",
"settings_notify_due_soon": "Notificar quando próxima",
"settings_notify_overdue": "Notificar quando atrasada",
@@ -533,6 +547,7 @@
"group_by_label": "Agrupar por",
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
"target_changes_help": "Número de transições correspondentes antes do trigger disparar (predefinido: 1).",
"state_latch_help": "Com 1 transição o gatilho é um trinco: recupera quando a entidade sai do estado de destino — ou, se só houver um estado de origem, quando volta a ele.",
"for_minutes_state_help": "0 conta cada mudança de imediato. Com minutos, o novo estado tem de se manter primeiro — uma oscilação breve então não dispara nem conta.",
"qr_print_title": "Imprimir códigos QR",
"qr_print_desc": "Gera uma página imprimível de códigos QR para recortar e colar nos equipamentos.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Eliminar «{name}»?",
"doc_too_large": "Ficheiro demasiado grande (máx. 25 MB).",
"doc_upload_failed": "Falha no carregamento.",
"completion_photo_optional": "Foto de conclusão (opcional)",
"add_photo": "Adicionar foto",
"completion_photos_optional": "Fotos de conclusão (opcional)",
"completion_photos": "Fotos de conclusão",
"choose_photos": "Escolher fotos",
"choose_photo": "Escolher foto",
"photos_android_hint": "A app Android entrega uma foto por seleção — cada seleção é adicionada.",
"add_photos": "Adicionar fotos",
"photos_limit": "Até {max} fotos por conclusão",
"history_edit_photos_hint": "Uma foto removida aqui continua nos documentos do objeto.",
"uploading": "A enviar…",
"remove": "Remover",
"doc_deduped": "Já armazenado noutro local — partilhado, sem espaço extra.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Mostrada ao lado do valor registado ao concluir esta tarefa.",
"reading_value_label": "Valor de leitura",
"reading_label": "Leitura",
"readings_section": "Leituras",
"readings_hint": "Vários valores nomeados por conclusão — uma linha por contador. Deixe vazio para um único valor.",
"reading_name_label": "Nome da leitura",
"reading_unit_short": "Unidade",
"reading_add": "Adicionar leitura",
"reading_duplicate_name": "Nome já utilizado",
"reading_last": "último: {value}",
"reading_below_last": "Inferior à última leitura ({value})",
"settings_templates_label": "Galeria de modelos",
"settings_templates_hint": "Desmarque modelos de que nunca precisará — desaparecem dos seletores \"A partir de modelo\" (painel e config flow). Nada mais muda; reativáveis a qualquer momento.",
"worksheet": "Folha de trabalho",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Notificar apenas para a vista",
"settings_notify_scope_all": "Todas as tarefas",
"settings_notify_scope_hint": "Apenas as tarefas que correspondem aos filtros de etiqueta/utilizador da vista escolhida enviam lembretes. O estado, a ordenação e o agrupamento da vista são ignorados aqui.",
"settings_notify_rule": "A sua regra de notificação",
"settings_notify_rule_hint": "Cada notificação dispara também o evento maintenance_supporter_notification (tipo, estado, objeto, tarefa, números de referência, prioridade, ligação, conteúdo). Encaminhe-o com uma automação — p. ex. para o Ticker, Telegram ou Pushover.",
"settings_notify_event_only": "Apenas disparar o evento, não enviar nada",
"settings_notify_event_only_hint": "A sua automação trata de toda a entrega. Intervalos de repetição, horas de silêncio e o limite diário continuam a decidir quando o evento dispara.",
"settings_notify_extra_data": "Dados de notificação adicionais (modelo)",
"settings_notify_extra_data_hint": "JSON ou YAML, Jinja permitido; é fundido nos data da chamada notify (as suas chaves ganham). Variáveis: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Teste com o botão Enviar teste.",
"card_saved_view": "Vista guardada",
"card_saved_view_none": "Nenhuma",
"card_saved_view_help": "Aplica os filtros de estado, utilizador e etiqueta da vista além dos filtros acima. A ordenação e o agrupamento da vista são definições de apresentação do painel e não se aplicam ao cartão.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Marcar todas substituídas",
"battery_fleet_mark_one": "Marcar esta bateria substituída",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Sem sensor",
"battery_fleet_trigger_lost": "O gatilho de sensor desta tarefa foi perdido — ela não será acionada nem concluída automaticamente.",
"battery_fleet_repair": "Reparar",
"battery_fleet_exclude": "Excluir da frota",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Escolha um sensor de bateria que a deteção automática não encontrou — entra de imediato na lista.",
"battery_fleet_track_self": "Acompanhar baterias de carregamento automático",
"battery_fleet_track_self_hint": "Telefones, aspiradores e outros dispositivos que se recarregam sozinhos aparecem como recarregáveis — com carga baixa pedem um carregamento, nunca pilhas novas.",
"battery_fleet_due_without_sensor": "Tratar uma previsão ultrapassada como a substituir quando uma pilha não tem sensor",
"battery_fleet_due_without_sensor_hint": "Alguns dispositivos não reportam qualquer nível de pilha — o Battery Notes só conhece o tipo e a última substituição. Passada a vida útil típica, essa pilha conta como fraca e a tarefa da frota lembra-o; marcá-la como substituída repõe a previsão.",
"battery_fleet_status_low": "Fraca",
"battery_fleet_status_soon": "Em breve",
"battery_fleet_status_ok": "Em bom estado",
"battery_fleet_status_due": "A substituir",
"battery_fleet_predicted_on": "Previsto por volta de {date}",
"battery_fleet_predicted_trend": "Previsto pela tendência de descarga desta pilha: por volta de {date} ({confidence})",
"battery_fleet_rechargeable": "Recarregável: carrega-se em vez de se substituir — nunca na lista de compras",
@@ -917,6 +956,24 @@
"service_record_print": "Registo de manutenção (PDF)",
"date": "Data",
"service_record_entries": "registos",
"ref_number": "Número de referência",
"print_options_title": "Imprimir o registo de serviço",
"print_layout": "Disposição",
"print_layout_chronological": "Cronológico",
"print_layout_by_task": "Por tarefa",
"print_include": "Incluir",
"print_inc_readings": "Leituras",
"print_inc_parts": "Peças usadas",
"print_inc_photos": "Fotografias",
"print_inc_documents": "Documentos associados",
"print_inc_checklist": "Lista de verificação",
"print_inc_notes": "Notas",
"print_inc_costs": "Custo e duração",
"print_inc_person": "Concluído por",
"print_inc_refs": "Números de referência",
"print_inc_qr": "Código QR por tarefa",
"print_button": "Imprimir",
"report_scan_hint": "Digitalize para abrir a tarefa",
"completed_by": "Concluído por",
"date_from": "De",
"date_to": "Até",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: неверное значение",
"feat_schedule_time": "Планирование по времени дня",
"feat_schedule_time_desc": "Задачи становятся просроченными в определённое время дня, а не в полночь.",
"schedule_time_optional": "Срок (необязательно)",
"schedule_time_toggle": "Срок в определённое время",
"schedule_time_help": "Пусто = полночь (по умолчанию). Часовой пояс HA.",
"at_time": "в",
"notes_optional": "Примечания (опционально)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Нет метки —",
"dashboard": "Панель",
"tab_today": "Сегодня",
"palette_placeholder": "Поиск объектов и задач…",
"palette_placeholder": "Поиск объектов, задач, запчастей, документов, заметок…",
"palette_no_results": "Нет совпадений",
"palette_hint": "↑↓ выбор · Enter открыть · Esc закрыть",
"palette_hint": "↑↓ навигация · Enter открыть · Esc закрыть · / открывает поиск где угодно",
"search_open": "Поиск",
"search_group_parts": "Запчасти",
"search_group_content": "В документах",
"search_group_history": "Заметки в истории",
"search_page": "Стр. {page}",
"search_empty_hint": "Введите текст для поиска по объектам, задачам, запчастям, документам и заметкам",
"search_searching": "Поиск по документам и заметкам…",
"search_index_status": "Полнотекстовый поиск: проиндексировано {indexed} из {total} файлов · {no_text} без текстового слоя · {pending} в очереди",
"today_all_caught_up": "Всё сделано! На этой неделе ничего нет.",
"today_overdue": "Просрочено",
"today_due_today": "Сегодня",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Порог «расходник заканчивается» (%)",
"settings_battery_low_percent": "Порог «батарея разряжена» (%)",
"settings_thresholds_hint": "Значения по умолчанию для новых «Предлагаемых настроек» (остаток в процентах) и для батарей парка без собственного порога Battery Notes. Существующие задачи сохраняют своё значение.",
"settings_value_out_of_range": "Значение должно быть от {min} до {max}",
"bn_summary": "{name}: {pct} % для {n} батарей",
"bn_floor_decides": "Ваш порог {floor} % действует для всех; для каждой батареи учитывается больший из двух порогов.",
"bn_above_floor": "Это выше вашего порога {floor} %, поэтому для всех отмеченных батарей решает {name}.",
@@ -479,6 +488,11 @@
"test_notification_success": "Тестовое уведомление отправлено",
"test_notification_failed": "Не удалось отправить тестовое уведомление",
"notify_per_person": "Доставка по пользователям",
"member_avatars": "Аватары участников",
"member_avatars_hint": "Показывается рядом с назначенными задачами — инициалы в цвете участника. По умолчанию берутся из имени; задайте свои инициалы или выберите цвет, чтобы различать участников.",
"member_initials": "Инициалы",
"member_color": "Цвет",
"member_avatar_reset": "Сбросить",
"notify_no_own_device": "Нет своего устройства — используется общий сервис",
"settings_notify_due_soon": "Уведомлять, когда срок скоро истекает",
"settings_notify_overdue": "Уведомлять при просрочке",
@@ -538,6 +552,7 @@
"group_by_label": "Группировать по",
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
"target_changes_help": "Количество совпадающих переходов, после которых срабатывает триггер (по умолчанию: 1).",
"state_latch_help": "При 1 переходе триггер работает как защёлка: он сбрасывается, когда объект покидает состояние «в» — или, если задано только состояние «из», когда он в него возвращается.",
"for_minutes_state_help": "0 — каждое изменение засчитывается сразу. С минутами новое состояние сначала должно удержаться — короткие сбои тогда не срабатывают и не считаются.",
"qr_print_title": "Печать QR-кодов",
"qr_print_desc": "Создай страницу для печати с QR-кодами для вырезания и наклеивания на оборудование.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Удалить «{name}»?",
"doc_too_large": "Файл слишком большой (макс. 25 МБ).",
"doc_upload_failed": "Не удалось загрузить.",
"completion_photo_optional": "Фото выполнения (необязательно)",
"add_photo": "Добавить фото",
"completion_photos_optional": "Фото выполнения (необязательно)",
"completion_photos": "Фото выполнения",
"choose_photos": "Выбрать фото",
"choose_photo": "Выбрать фото",
"photos_android_hint": "Приложение для Android передаёт по одному фото за выбор — каждый выбор добавляется.",
"add_photos": "Добавить фото",
"photos_limit": "До {max} фото на одно выполнение",
"history_edit_photos_hint": "Удалённое здесь фото остаётся в документах объекта.",
"uploading": "Загрузка…",
"remove": "Убрать",
"doc_deduped": "Уже сохранён в другом месте — общий, без доп. места.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Отображается рядом с записанным значением при выполнении этой задачи.",
"reading_value_label": "Показание",
"reading_label": "Показание",
"readings_section": "Показания",
"readings_hint": "Несколько именованных значений на одно выполнение — по строке на счётчик. Оставьте пустым для одного значения.",
"reading_name_label": "Название показания",
"reading_unit_short": "Единица",
"reading_add": "Добавить показание",
"reading_duplicate_name": "Название уже используется",
"reading_last": "последнее: {value}",
"reading_below_last": "Меньше предыдущего показания ({value})",
"settings_templates_label": "Галерея шаблонов",
"settings_templates_hint": "Снимите отметку с шаблонов, которые вам не нужны — они исчезнут из выбора «Из шаблона» (панель и config flow). Больше ничего не меняется; можно включить обратно в любой момент.",
"worksheet": "Рабочий лист",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Уведомлять только для представления",
"settings_notify_scope_all": "Все задачи",
"settings_notify_scope_hint": "Напоминания отправляют только задачи, совпадающие с фильтрами метки/пользователя выбранного представления. Статус, сортировка и группировка представления здесь игнорируются.",
"settings_notify_rule": "Своё правило уведомлений",
"settings_notify_rule_hint": "Каждое уведомление дополнительно вызывает событие maintenance_supporter_notification (вид, статус, объект, задача, номера ссылок, приоритет, ссылка, данные). Направьте его автоматизацией — например в Ticker, Telegram или Pushover.",
"settings_notify_event_only": "Только событие, ничего не отправлять",
"settings_notify_event_only_hint": "Доставку целиком выполняет ваша автоматизация. Интервалы повтора, тихие часы и дневной лимит по-прежнему определяют, когда срабатывает событие.",
"settings_notify_extra_data": "Дополнительные данные уведомления (шаблон)",
"settings_notify_extra_data_hint": "JSON или YAML, Jinja разрешён; добавляется в data вызова notify (ваши ключи важнее). Переменные: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Проверьте кнопкой «Отправить тест».",
"card_saved_view": "Сохранённое представление",
"card_saved_view_none": "Нет",
"card_saved_view_help": "Применяет фильтры статуса, пользователя и метки представления в дополнение к фильтрам выше. Сортировка и группировка представления — настройки отображения панели и на карточке не применяются.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Отметить все заменёнными",
"battery_fleet_mark_one": "Отметить эту батарею заменённой",
"battery_fleet_offline": "не в сети",
"battery_fleet_no_sensor": "Без датчика",
"battery_fleet_trigger_lost": "Сенсорный триггер этой задачи утерян — она не сработает и не завершится автоматически.",
"battery_fleet_repair": "Восстановить",
"battery_fleet_exclude": "Исключить из парка",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Выберите датчик батареи, который автоматическое обнаружение пропустило — он сразу появится в списке.",
"battery_fleet_track_self": "Отслеживать самозаряжающиеся батареи",
"battery_fleet_track_self_hint": "Телефоны, пылесосы и другие самозаряжающиеся устройства отображаются как аккумуляторы — при низком заряде нужна зарядка, а не новые батарейки.",
"battery_fleet_due_without_sensor": "Считать истёкший прогноз поводом для замены, если у батареи нет датчика",
"battery_fleet_due_without_sensor_hint": "Некоторые устройства вообще не сообщают уровень заряда — Battery Notes знает только тип и дату последней замены. Когда типичный срок службы истёк, такая батарея считается разряженной, и задача парка напомнит; отметка «заменена» сбрасывает прогноз.",
"battery_fleet_status_low": "Низкий",
"battery_fleet_status_soon": "Скоро",
"battery_fleet_status_ok": "В норме",
"battery_fleet_status_due": "Пора менять",
"battery_fleet_predicted_on": "Ожидается примерно {date}",
"battery_fleet_predicted_trend": "Прогноз по тренду разряда этой батареи: примерно {date} ({confidence})",
"battery_fleet_rechargeable": "Аккумулятор: заряжается, а не заменяется — никогда не попадает в список покупок",
@@ -917,6 +956,24 @@
"service_record_print": "Сервисная книжка (PDF)",
"date": "Дата",
"service_record_entries": "записей",
"ref_number": "Номер ссылки",
"print_options_title": "Печать сервисной книжки",
"print_layout": "Структура",
"print_layout_chronological": "Хронологически",
"print_layout_by_task": "По задачам",
"print_include": "Включить",
"print_inc_readings": "Показания",
"print_inc_parts": "Использованные детали",
"print_inc_photos": "Фото",
"print_inc_documents": "Связанные документы",
"print_inc_checklist": "Чек-лист",
"print_inc_notes": "Заметки",
"print_inc_costs": "Стоимость и длительность",
"print_inc_person": "Выполнил",
"print_inc_refs": "Номера ссылок",
"print_inc_qr": "QR-код для каждой задачи",
"print_button": "Печать",
"report_scan_hint": "Отсканируйте, чтобы открыть задачу",
"completed_by": "Выполнил",
"date_from": "С",
"date_to": "По",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: ogiltigt värde",
"feat_schedule_time": "Schemaläggning per tid på dygnet",
"feat_schedule_time_desc": "Uppgifter blir försenade vid en specifik tid på dygnet istället för midnatt.",
"schedule_time_optional": "Förfaller kl. (valfritt)",
"schedule_time_toggle": "Förfaller vid en viss tid",
"schedule_time_help": "Tomt = midnatt (standard). HA-tidszon.",
"at_time": "kl.",
"notes_optional": "Anteckningar (valfritt)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Ingen tagg —",
"dashboard": "Översikt",
"tab_today": "Idag",
"palette_placeholder": "Sök objekt och uppgifter…",
"palette_placeholder": "Sök objekt, uppgifter, reservdelar, dokument, anteckningar…",
"palette_no_results": "Inga träffar",
"palette_hint": "↑↓ navigera · Enter öppna · Esc stäng",
"palette_hint": "↑↓ navigera · Enter öppna · Esc stäng · / öppnar sökningen överallt",
"search_open": "Sök",
"search_group_parts": "Reservdelar",
"search_group_content": "I dokument",
"search_group_history": "Historikanteckningar",
"search_page": "Sida {page}",
"search_empty_hint": "Skriv för att söka bland objekt, uppgifter, reservdelar, dokument och anteckningar",
"search_searching": "Söker i dokument och anteckningar…",
"search_index_status": "Fulltextsökning: {indexed} av {total} filer indexerade · {no_text} utan textlager · {pending} väntar",
"today_all_caught_up": "Allt klart! Inget den här veckan.",
"today_overdue": "Försenade",
"today_due_today": "Förfaller idag",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Tröskel „förbrukningsartikel låg“ (%)",
"settings_battery_low_percent": "Tröskel „batteri lågt“ (%)",
"settings_thresholds_hint": "Standardvärden för nya Föreslagna inställningar (återstående procent) och för flottans batterier utan egen Battery Notes-tröskel. Befintliga uppgifter behåller sitt värde.",
"settings_value_out_of_range": "Värdet måste vara mellan {min} och {max}",
"bn_summary": "{name}: {pct} % för {n} batterier",
"bn_floor_decides": "Din tröskel på {floor} % avgör för alla; per batteri gäller den högre av de två trösklarna.",
"bn_above_floor": "Det är över din tröskel på {floor} %, så {name} avgör för alla noterade batterier.",
@@ -479,6 +488,11 @@
"test_notification_success": "Testnotifikation skickad",
"test_notification_failed": "Testnotifikation misslyckades",
"notify_per_person": "Leverans per person",
"member_avatars": "Medlemmarnas avatarer",
"member_avatars_hint": "Visas bredvid tilldelade uppgifter — initialer i medlemmens färg. Standard hämtas från namnet; ange egna initialer eller välj en färg för att skilja medlemmar åt.",
"member_initials": "Initialer",
"member_color": "Färg",
"member_avatar_reset": "Återställ",
"notify_no_own_device": "Ingen egen enhet — använder hushållets tjänst",
"settings_notify_due_soon": "Notifiera när snart förfallande",
"settings_notify_overdue": "Notifiera när försenad",
@@ -538,6 +552,7 @@
"group_by_label": "Gruppera efter",
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
"target_changes_help": "Antal matchande övergångar innan utlösaren aktiveras (standard: 1).",
"state_latch_help": "Med 1 övergång är utlösaren en spärr: den återställs när entiteten lämnar Till-tillståndet — eller, om bara ett Från-tillstånd är satt, när den återgår till det.",
"for_minutes_state_help": "0 räknar varje ändring direkt. Med minuter måste det nya tillståndet först hålla i sig — korta blinkningar varken utlöser eller räknas då.",
"qr_print_title": "Skriv ut QR-koder",
"qr_print_desc": "Skapa en utskriftssida med QR-koder att klippa ut och sätta på din utrustning.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Ta bort \"{name}\"?",
"doc_too_large": "Filen är för stor (max 25 MB).",
"doc_upload_failed": "Uppladdningen misslyckades.",
"completion_photo_optional": "Slutförandefoto (valfritt)",
"add_photo": "Lägg till foto",
"completion_photos_optional": "Foton vid slutförande (valfritt)",
"completion_photos": "Foton vid slutförande",
"choose_photos": "Välj foton",
"choose_photo": "Välj foto",
"photos_android_hint": "Android-appen lämnar över ett foto per val — varje val läggs till.",
"add_photos": "Lägg till foton",
"photos_limit": "Högst {max} foton per slutförande",
"history_edit_photos_hint": "Ett foto som tas bort här finns kvar bland objektets dokument.",
"uploading": "Laddar upp…",
"remove": "Ta bort",
"doc_deduped": "Redan lagrad någon annanstans — delad, inget extra utrymme.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Visas bredvid det registrerade värdet när uppgiften slutförs.",
"reading_value_label": "Avläst värde",
"reading_label": "Avläsning",
"readings_section": "Avläsningar",
"readings_hint": "Flera namngivna värden per slutförande — en rad per mätare. Lämna tomt för ett enda värde.",
"reading_name_label": "Avläsningens namn",
"reading_unit_short": "Enhet",
"reading_add": "Lägg till avläsning",
"reading_duplicate_name": "Namnet används redan",
"reading_last": "senast: {value}",
"reading_below_last": "Lägre än senaste avläsningen ({value})",
"settings_templates_label": "Mallgalleri",
"settings_templates_hint": "Avmarkera mallar du aldrig behöver — de försvinner från \"Från mall\"-väljarna (panel och config flow). Inget annat ändras; kan återaktiveras när som helst.",
"worksheet": "Arbetsblad",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Avisera endast för vy",
"settings_notify_scope_all": "Alla uppgifter",
"settings_notify_scope_hint": "Endast uppgifter som matchar den valda vyns etikett-/användarfilter skickar påminnelser. Vyns status, sortering och gruppering ignoreras här.",
"settings_notify_rule": "Din egen aviseringsregel",
"settings_notify_rule_hint": "Varje avisering utlöser även händelsen maintenance_supporter_notification (typ, status, objekt, uppgift, referensnummer, prioritet, länk, nyttolast). Styr den med en automation — t.ex. till Ticker, Telegram eller Pushover.",
"settings_notify_event_only": "Utlös bara händelsen, skicka inget själv",
"settings_notify_event_only_hint": "Din automation sköter hela leveransen. Upprepningsintervall, tysta timmar och dagstaket avgör fortfarande när händelsen utlöses.",
"settings_notify_extra_data": "Extra aviseringsdata (mall)",
"settings_notify_extra_data_hint": "JSON eller YAML, Jinja tillåtet; slås ihop med notify-anropets data (dina nycklar vinner). Variabler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Testa med knappen Skicka test.",
"card_saved_view": "Sparad vy",
"card_saved_view_none": "Ingen",
"card_saved_view_help": "Tillämpar vyns status-, användar- och etikettfilter utöver filtren ovan. Vyns sortering och gruppering är panelvisningsinställningar och tillämpas inte på kortet.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Markera alla bytta",
"battery_fleet_mark_one": "Markera detta batteri bytt",
"battery_fleet_offline": "offline",
"battery_fleet_no_sensor": "Ingen sensor",
"battery_fleet_trigger_lost": "Uppgiftens sensorutlösare har gått förlorad — den utlöses inte och slutförs inte automatiskt.",
"battery_fleet_repair": "Reparera",
"battery_fleet_exclude": "Uteslut från flottan",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Välj en batterisensor som den automatiska identifieringen missade — den läggs till i listan direkt.",
"battery_fleet_track_self": "Spåra självladdande batterier",
"battery_fleet_track_self_hint": "Telefoner, dammsugare och andra självladdande enheter visas som laddningsbara — vid låg nivå begärs laddning, aldrig nya batterier.",
"battery_fleet_due_without_sensor": "Behandla en passerad prognos som dags när ett batteri saknar sensor",
"battery_fleet_due_without_sensor_hint": "Vissa enheter rapporterar ingen batterinivå alls — Battery Notes känner bara till typ och senaste byte. När den typiska livslängden har passerat räknas ett sådant batteri som lågt och flottuppgiften påminner dig; att markera det som bytt nollställer prognosen.",
"battery_fleet_status_low": "Låg",
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I ordning",
"battery_fleet_status_due": "Dags",
"battery_fleet_predicted_on": "Väntas omkring {date}",
"battery_fleet_predicted_trend": "Förutspått utifrån batteriets urladdningstrend: omkring {date} ({confidence})",
"battery_fleet_rechargeable": "Uppladdningsbart: ladda i stället för att byta — aldrig på inköpslistan",
@@ -917,6 +956,24 @@
"service_record_print": "Servicebok (PDF)",
"date": "Datum",
"service_record_entries": "poster",
"ref_number": "Referensnummer",
"print_options_title": "Skriv ut servicebok",
"print_layout": "Upplägg",
"print_layout_chronological": "Kronologiskt",
"print_layout_by_task": "Per uppgift",
"print_include": "Ta med",
"print_inc_readings": "Avläsningar",
"print_inc_parts": "Använda delar",
"print_inc_photos": "Foton",
"print_inc_documents": "Länkade dokument",
"print_inc_checklist": "Checklista",
"print_inc_notes": "Anteckningar",
"print_inc_costs": "Kostnad och tid",
"print_inc_person": "Utförd av",
"print_inc_refs": "Referensnummer",
"print_inc_qr": "QR-kod per uppgift",
"print_button": "Skriv ut",
"report_scan_hint": "Skanna för att öppna uppgiften",
"completed_by": "Utförd av",
"date_from": "Från",
"date_to": "Till",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: geçersiz değer",
"feat_schedule_time": "Gün içi saat zamanlaması",
"feat_schedule_time_desc": "Görevler gece yarısı yerine günün belirli bir saatinde gecikmiş olur.",
"schedule_time_optional": "Vade saati (isteğe bağlı)",
"schedule_time_toggle": "Belirli bir saatte vadesi dolar",
"schedule_time_help": "Boş = gece yarısı (varsayılan). HA saat dilimi.",
"at_time": "saat",
"notes_optional": "Notlar (isteğe bağlı)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— Etiket yok —",
"dashboard": "Gösterge Paneli",
"tab_today": "Bugün",
"palette_placeholder": "Nesne ve görevlerde ara…",
"palette_placeholder": "Nesne, görev, yedek parça, belge, not ara…",
"palette_no_results": "Eşleşme yok",
"palette_hint": "↑↓ gezinme · Enter açma · Esc kapatma",
"palette_hint": "↑↓ gezin · Enter aç · Esc kapat · / her yerde aramayı açar",
"search_open": "Ara",
"search_group_parts": "Yedek parçalar",
"search_group_content": "Belgelerde",
"search_group_history": "Geçmiş notları",
"search_page": "Sayfa {page}",
"search_empty_hint": "Nesneler, görevler, yedek parçalar, belgeler ve notlarda aramak için yazın",
"search_searching": "Belgeler ve notlar aranıyor…",
"search_index_status": "Tam metin arama: {total} dosyadan {indexed} tanesi dizinlendi · {no_text} metin katmanı yok · {pending} bekliyor",
"today_all_caught_up": "Her şey tamam! Bu hafta vadesi gelen yok.",
"today_overdue": "Gecikmiş",
"today_due_today": "Bugün vadeli",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "„Sarf malzemesi düşük“ eşiği (%)",
"settings_battery_low_percent": "„Pil zayıf“ eşiği (%)",
"settings_thresholds_hint": "Yeni Önerilen kurulumlar (kalan yüzde) ve kendi Battery Notes eşiği olmayan filo pilleri için varsayılanlar. Mevcut görevler değerlerini korur.",
"settings_value_out_of_range": "Değer {min} ile {max} arasında olmalıdır",
"bn_summary": "{name}: {n} pil için % {pct}",
"bn_floor_decides": "Senin % {floor} eşiğin hepsi için belirleyici; her pil için iki eşikten yüksek olanı geçerli.",
"bn_above_floor": "Bu, senin % {floor} eşiğinin üzerinde; bu yüzden kayıtlı tüm piller için {name} belirleyici.",
@@ -481,6 +490,11 @@
"test_notification_success": "Test bildirimi gönderildi",
"test_notification_failed": "Test bildirimi başarısız oldu",
"notify_per_person": "Kişi bazında iletim",
"member_avatars": "Üye avatarları",
"member_avatars_hint": "Atanmış görevlerin yanında gösterilir — üyenin renginde baş harfler. Varsayılan addan gelir; üyeleri ayırt etmek için kendi baş harflerinizi belirleyin veya bir renk seçin.",
"member_initials": "Baş harfler",
"member_color": "Renk",
"member_avatar_reset": "Sıfırla",
"notify_no_own_device": "Kendi cihazı yok — ev servisini kullanır",
"settings_notify_due_soon": "Vade yaklaştığında bildir",
"settings_notify_overdue": "Geciktiğinde bildir",
@@ -546,6 +560,7 @@
"group_by_label": "Grupla",
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
"target_changes_help": "Tetikleyici çalışmadan önceki eşleşen geçiş sayısı (varsayılan: 1).",
"state_latch_help": "1 geçişte tetikleyici bir mandal gibi çalışır: varlık Hedef durumdan çıkınca sıfırlanır — ya da yalnızca Kaynak durum ayarlıysa ona geri döndüğünde.",
"for_minutes_state_help": "0 her değişikliği hemen sayar. Dakika ayarlanırsa yeni durumun önce o kadar sürmesi gerekir — kısa titremeler o zaman ne tetikler ne de sayılır.",
"qr_print_title": "QR kodlarını yazdır",
"qr_print_desc": "Kesip ekipmanınıza yapıştırmak için yazdırılabilir bir QR kodu sayfası oluşturun.",
@@ -683,8 +698,14 @@
"doc_delete_confirm": "\"{name}\" silinsin mi?",
"doc_too_large": "Dosya çok büyük (en fazla 25 MB).",
"doc_upload_failed": "Yükleme başarısız oldu.",
"completion_photo_optional": "Tamamlama fotoğrafı (isteğe bağlı)",
"add_photo": "Fotoğraf ekle",
"completion_photos_optional": "Tamamlama fotoğrafları (isteğe bağlı)",
"completion_photos": "Tamamlama fotoğrafları",
"choose_photos": "Fotoğraf seç",
"choose_photo": "Fotoğraf seç",
"photos_android_hint": "Android uygulaması her seçimde bir fotoğraf iletir — her seçim eklenir.",
"add_photos": "Fotoğraf ekle",
"photos_limit": "Tamamlama başına en fazla {max} fotoğraf",
"history_edit_photos_hint": "Burada kaldırılan fotoğraf nesnenin belgelerinde kalır.",
"uploading": "Yükleniyor…",
"remove": "Kaldır",
"doc_deduped": "Zaten başka bir yerde saklı — paylaşıldı, ek alan kullanılmadı.",
@@ -739,6 +760,14 @@
"reading_unit_help": "Bu görevi tamamlarken kaydedilen değerin yanında gösterilir.",
"reading_value_label": "Okuma değeri",
"reading_label": "Okuma",
"readings_section": "Sayaç okumaları",
"readings_hint": "Tamamlama başına birden fazla adlandırılmış değer — her sayaç için bir satır. Tek bir değer için boş bırakın.",
"reading_name_label": "Okuma adı",
"reading_unit_short": "Birim",
"reading_add": "Okuma ekle",
"reading_duplicate_name": "Bu ad zaten kullanılıyor",
"reading_last": "son: {value}",
"reading_below_last": "Son okumadan ({value}) düşük",
"settings_templates_label": "Şablon galerisi",
"settings_templates_hint": "Asla ihtiyaç duymayacağınız şablonların işaretini kaldırın — \"Şablondan\" seçicilerinden (panel ve yapılandırma akışı) kaybolurlar. Başka hiçbir şey değişmez; istediğiniz zaman yeniden etkinleştirebilirsiniz.",
"worksheet": "Çalışma sayfası",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Yalnızca şu görünüm için bildir",
"settings_notify_scope_all": "Tüm görevler",
"settings_notify_scope_hint": "Yalnızca seçili kayıtlı görünümün etiket/kullanıcı filtreleriyle eşleşen görevler hatırlatma gönderir. Görünümün durum, sıralama ve gruplaması burada dikkate alınmaz.",
"settings_notify_rule": "Kendi bildirim kuralınız",
"settings_notify_rule_hint": "Her bildirim ayrıca maintenance_supporter_notification olayını tetikler (tür, durum, nesne, görev, referans numaraları, öncelik, derin bağlantı, yük). Bir otomasyonla yönlendirin — ör. Ticker, Telegram veya Pushover'a.",
"settings_notify_event_only": "Yalnızca olayı tetikle, kendin gönderme",
"settings_notify_event_only_hint": "Teslimatın tamamını otomasyonunuz yapar. Tekrar aralıkları, sessiz saatler ve günlük sınır, olayın ne zaman tetikleneceği için geçerli kalır.",
"settings_notify_extra_data": "Ek bildirim verileri (şablon)",
"settings_notify_extra_data_hint": "JSON veya YAML, Jinja kullanılabilir; notify çağrısının data alanına birleştirilir (sizin anahtarlarınız öncelikli). Değişkenler: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Test gönder düğmesiyle deneyin.",
"card_saved_view": "Kayıtlı görünüm",
"card_saved_view_none": "Yok",
"card_saved_view_help": "Görünümün durum, kullanıcı ve etiket filtrelerini yukarıdaki filtrelerin üzerine uygular. Görünümün sıralaması ve gruplaması panel görüntüleme ayarlarıdır ve kartta uygulanmaz.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Tümünü değiştirildi olarak işaretle",
"battery_fleet_mark_one": "Bu pili değiştirildi olarak işaretle",
"battery_fleet_offline": "çevrimdışı",
"battery_fleet_no_sensor": "Sensör yok",
"battery_fleet_trigger_lost": "Bu görevin sensör tetikleyicisi kayboldu — tetiklenmeyecek ve otomatik tamamlanmayacak.",
"battery_fleet_repair": "Onar",
"battery_fleet_exclude": "Filodan hariç tut",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Otomatik keşfin atladığı bir pil sensörü seçin — hemen listeye katılır.",
"battery_fleet_track_self": "Kendi kendine şarj olan pilleri izle",
"battery_fleet_track_self_hint": "Telefonlar, süpürgeler ve kendi kendine şarj olan diğer cihazlar şarj edilebilir olarak görünür — seviye düşükken şarj istenir, asla yeni pil istenmez.",
"battery_fleet_due_without_sensor": "Sensörü olmayan bir pilde geçmiş tahmini süresi dolmuş say",
"battery_fleet_due_without_sensor_hint": "Bazı cihazlar hiç pil seviyesi bildirmez — Battery Notes yalnızca türü ve son değişimi bilir. Tipik ömür geçtiğinde böyle bir pil zayıf sayılır ve filo görevi size hatırlatır; değiştirildi olarak işaretlemek tahmini sıfırlar.",
"battery_fleet_status_low": "Düşük",
"battery_fleet_status_soon": "Yakında",
"battery_fleet_status_ok": "İyi durumda",
"battery_fleet_status_due": "Süresi doldu",
"battery_fleet_predicted_on": "Yaklaşık {date} tarihinde bekleniyor",
"battery_fleet_predicted_trend": "Bu pilin deşarj eğiliminden tahmin edildi: yaklaşık {date} ({confidence})",
"battery_fleet_rechargeable": "Şarj edilebilir: değiştirmek yerine şarj edin — alışveriş listesine asla girmez",
@@ -917,6 +956,24 @@
"service_record_print": "Servis kaydı (PDF)",
"date": "Tarih",
"service_record_entries": "kayıt",
"ref_number": "Referans numarası",
"print_options_title": "Servis defterini yazdır",
"print_layout": "Düzen",
"print_layout_chronological": "Kronolojik",
"print_layout_by_task": "Göreve göre",
"print_include": "Dahil et",
"print_inc_readings": "Okumalar",
"print_inc_parts": "Kullanılan parçalar",
"print_inc_photos": "Fotoğraflar",
"print_inc_documents": "Bağlı belgeler",
"print_inc_checklist": "Kontrol listesi",
"print_inc_notes": "Notlar",
"print_inc_costs": "Maliyet ve süre",
"print_inc_person": "Tamamlayan",
"print_inc_refs": "Referans numaraları",
"print_inc_qr": "Görev başına QR kodu",
"print_button": "Yazdır",
"report_scan_hint": "Görevi açmak için tarayın",
"completed_by": "Tamamlayan",
"date_from": "Başlangıç",
"date_to": "Bitiş",
@@ -122,7 +122,7 @@
"err_invalid_value": "{field}: невірне значення",
"feat_schedule_time": "Планування за часом доби",
"feat_schedule_time_desc": "Задачі стають простроченими у певний час доби, а не опівночі.",
"schedule_time_optional": "Час прострочення (необов'язково)",
"schedule_time_toggle": "Термін у визначений час",
"schedule_time_help": "Порожньо = опівночі (за замовчуванням). Часовий пояс HA.",
"at_time": "о",
"notes_optional": "Примітки (необов'язково)",
@@ -425,9 +425,17 @@
"no_nfc_tag": "— Без тега —",
"dashboard": "Дашборд",
"tab_today": "Сьогодні",
"palette_placeholder": "Пошук об'єктів і завдань…",
"palette_placeholder": "Пошук об'єктів, завдань, запчастин, документів, нотаток…",
"palette_no_results": "Немає збігів",
"palette_hint": "↑↓ навігація · Enter відкрити · Esc закрити",
"palette_hint": "↑↓ навігація · Enter відкрити · Esc закрити · / відкриває пошук будь-де",
"search_open": "Пошук",
"search_group_parts": "Запчастини",
"search_group_content": "У документах",
"search_group_history": "Нотатки в історії",
"search_page": "Стор. {page}",
"search_empty_hint": "Введіть текст для пошуку об'єктів, завдань, запчастин, документів і нотаток",
"search_searching": "Пошук у документах і нотатках…",
"search_index_status": "Повнотекстовий пошук: проіндексовано {indexed} із {total} файлів · {no_text} без текстового шару · {pending} у черзі",
"today_all_caught_up": "Усе зроблено! Цього тижня нічого немає.",
"today_overdue": "Прострочені",
"today_due_today": "Сьогодні",
@@ -454,6 +462,7 @@
"settings_consumable_threshold": "Поріг «витратний матеріал закінчується» (%)",
"settings_battery_low_percent": "Поріг «батарея розряджена» (%)",
"settings_thresholds_hint": "Значення за замовчуванням для нових «Запропонованих налаштувань» (залишок у відсотках) і для батарей парку без власного порогу Battery Notes. Наявні завдання зберігають своє значення.",
"settings_value_out_of_range": "Значення має бути від {min} до {max}",
"bn_summary": "{name}: {pct} % для {n} батарей",
"bn_floor_decides": "Ваш поріг {floor} % діє для всіх; для кожної батареї враховується більший із двох порогів.",
"bn_above_floor": "Це вище за ваш поріг {floor} %, тому для всіх занотованих батарей вирішує {name}.",
@@ -479,6 +488,11 @@
"test_notification_success": "Тестове сповіщення надіслано",
"test_notification_failed": "Не вдалося надіслати тестове сповіщення",
"notify_per_person": "Доставка за особами",
"member_avatars": "Аватари учасників",
"member_avatars_hint": "Показується поруч із призначеними завданнями — ініціали в кольорі учасника. Типово беруться з імені; задайте власні ініціали або оберіть колір, щоб розрізняти учасників.",
"member_initials": "Ініціали",
"member_color": "Колір",
"member_avatar_reset": "Скинути",
"notify_no_own_device": "Немає власного пристрою — використовується загальна служба",
"settings_notify_due_soon": "Сповіщати, коли термін наближається",
"settings_notify_overdue": "Сповіщати про прострочення",
@@ -538,6 +552,7 @@
"group_by_label": "Групувати за",
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
"target_changes_help": "Кількість відповідних переходів, після яких тригер спрацює (за замовчуванням: 1).",
"state_latch_help": "При 1 переході тригер працює як засувка: він скидається, коли сутність залишає стан «до» — або, якщо задано лише стан «з», коли вона до нього повертається.",
"for_minutes_state_help": "0 — кожна зміна зараховується одразу. З хвилинами новий стан спершу має утриматися — короткі збої тоді не спрацьовують і не рахуються.",
"qr_print_title": "Друкувати QR-коди",
"qr_print_desc": "Створи сторінку для друку з QR-кодами, які можна вирізати та наклеїти на обладнання.",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "Видалити «{name}»?",
"doc_too_large": "Файл завеликий (макс. 25 МБ).",
"doc_upload_failed": "Не вдалося завантажити.",
"completion_photo_optional": "Фото виконання (необов'язково)",
"add_photo": "Додати фото",
"completion_photos_optional": "Фото виконання (необов'язково)",
"completion_photos": "Фото виконання",
"choose_photos": "Вибрати фото",
"choose_photo": "Вибрати фото",
"photos_android_hint": "Застосунок для Android передає по одному фото за вибір — кожен вибір додається.",
"add_photos": "Додати фото",
"photos_limit": "До {max} фото на одне виконання",
"history_edit_photos_hint": "Видалене тут фото залишається в документах об'єкта.",
"uploading": "Завантаження…",
"remove": "Прибрати",
"doc_deduped": "Уже збережено в іншому місці — спільний, без додаткового місця.",
@@ -733,6 +754,14 @@
"reading_unit_help": "Показується поруч із записаним значенням під час виконання цієї задачі.",
"reading_value_label": "Показання",
"reading_label": "Показання",
"readings_section": "Показання",
"readings_hint": "Кілька іменованих значень на одне виконання — по рядку на лічильник. Залиште порожнім для одного значення.",
"reading_name_label": "Назва показання",
"reading_unit_short": "Одиниця",
"reading_add": "Додати показання",
"reading_duplicate_name": "Назва вже використовується",
"reading_last": "останнє: {value}",
"reading_below_last": "Менше за попереднє показання ({value})",
"settings_templates_label": "Галерея шаблонів",
"settings_templates_hint": "Зніміть позначку з шаблонів, які вам не потрібні — вони зникнуть із вибору «З шаблону» (панель і config flow). Більше нічого не змінюється; можна ввімкнути знову будь-коли.",
"worksheet": "Робочий аркуш",
@@ -818,6 +847,12 @@
"settings_notify_scope": "Сповіщати лише для подання",
"settings_notify_scope_all": "Усі завдання",
"settings_notify_scope_hint": "Нагадування надсилають лише завдання, що відповідають фільтрам мітки/користувача обраного подання. Статус, сортування та групування подання тут ігноруються.",
"settings_notify_rule": "Власне правило сповіщень",
"settings_notify_rule_hint": "Кожне сповіщення додатково викликає подію maintenance_supporter_notification (вид, статус, об'єкт, завдання, номери посилань, пріоритет, посилання, дані). Спрямуйте її автоматизацією — наприклад у Ticker, Telegram чи Pushover.",
"settings_notify_event_only": "Лише подія, нічого не надсилати",
"settings_notify_event_only_hint": "Доставку повністю виконує ваша автоматизація. Інтервали повтору, тихі години та денний ліміт і далі визначають, коли спрацьовує подія.",
"settings_notify_extra_data": "Додаткові дані сповіщення (шаблон)",
"settings_notify_extra_data_hint": "JSON або YAML, Jinja дозволено; додається до data виклику notify (ваші ключі мають перевагу). Змінні: kind, status, priority, object_name, task_name, object_ref, task_ref, days_until_due, next_due, responsible_user_id, url, target, title, message. Перевірте кнопкою «Надіслати тест».",
"card_saved_view": "Збережене подання",
"card_saved_view_none": "Немає",
"card_saved_view_help": "Застосовує фільтри статусу, користувача та мітки подання додатково до фільтрів вище. Сортування та групування подання — налаштування відображення панелі й на картці не застосовуються.",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "Позначити всі заміненими",
"battery_fleet_mark_one": "Позначити цю батарею заміненою",
"battery_fleet_offline": "не в мережі",
"battery_fleet_no_sensor": "Без датчика",
"battery_fleet_trigger_lost": "Сенсорний тригер цього завдання втрачено — воно не спрацює і не завершиться автоматично.",
"battery_fleet_repair": "Відновити",
"battery_fleet_exclude": "Виключити з парку",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "Виберіть датчик батареї, який автоматичне виявлення пропустило — він одразу з'явиться у списку.",
"battery_fleet_track_self": "Відстежувати самозарядні батареї",
"battery_fleet_track_self_hint": "Телефони, пилососи та інші самозарядні пристрої відображаються як акумулятори — за низького заряду потрібна зарядка, а не нові батарейки.",
"battery_fleet_due_without_sensor": "Вважати прострочений прогноз приводом для заміни, якщо батарея не має датчика",
"battery_fleet_due_without_sensor_hint": "Деякі пристрої взагалі не повідомляють рівень заряду — Battery Notes знає лише тип і дату останньої заміни. Коли типовий термін служби минув, така батарея вважається розрядженою, і завдання парку нагадає; позначка «замінено» скидає прогноз.",
"battery_fleet_status_low": "Низький",
"battery_fleet_status_soon": "Незабаром",
"battery_fleet_status_ok": "У нормі",
"battery_fleet_status_due": "Час замінити",
"battery_fleet_predicted_on": "Очікується приблизно {date}",
"battery_fleet_predicted_trend": "Прогноз за трендом розряду цієї батареї: приблизно {date} ({confidence})",
"battery_fleet_rechargeable": "Акумулятор: заряджається, а не замінюється — ніколи не потрапляє до списку покупок",
@@ -917,6 +956,24 @@
"service_record_print": "Сервісна книжка (PDF)",
"date": "Дата",
"service_record_entries": "записів",
"ref_number": "Номер посилання",
"print_options_title": "Друк сервісної книжки",
"print_layout": "Структура",
"print_layout_chronological": "Хронологічно",
"print_layout_by_task": "За завданнями",
"print_include": "Включити",
"print_inc_readings": "Показники",
"print_inc_parts": "Використані деталі",
"print_inc_photos": "Фото",
"print_inc_documents": "Пов'язані документи",
"print_inc_checklist": "Чек-лист",
"print_inc_notes": "Нотатки",
"print_inc_costs": "Вартість і тривалість",
"print_inc_person": "Виконав",
"print_inc_refs": "Номери посилань",
"print_inc_qr": "QR-код для кожного завдання",
"print_button": "Друк",
"report_scan_hint": "Скануйте, щоб відкрити завдання",
"completed_by": "Виконав",
"date_from": "Від",
"date_to": "До",
@@ -124,7 +124,7 @@
"err_invalid_value": "{field}: 无效数值",
"feat_schedule_time": "具体时间调度",
"feat_schedule_time_desc": "任务将在特定时间点变为逾期,而非默认的午夜。",
"schedule_time_optional": "到期时间 (可选)",
"schedule_time_toggle": "在特定时间到期",
"schedule_time_help": "留空 = 午夜 (默认)。基于 HA 时区。",
"at_time": "于",
"notes_optional": "备注 (可选)",
@@ -427,9 +427,17 @@
"no_nfc_tag": "— 无标签 —",
"dashboard": "仪表盘",
"tab_today": "今天",
"palette_placeholder": "搜索对象和任务…",
"palette_placeholder": "搜索对象、任务、备件、文档、备注…",
"palette_no_results": "无匹配",
"palette_hint": "↑↓ 导航 · Enter 打开 · Esc 关闭",
"palette_hint": "↑↓ 导航 · Enter 打开 · Esc 关闭 · / 在任意位置打开搜索",
"search_open": "搜索",
"search_group_parts": "备件",
"search_group_content": "文档内容",
"search_group_history": "历史备注",
"search_page": "第 {page} 页",
"search_empty_hint": "输入以搜索对象、任务、备件、文档和备注",
"search_searching": "正在搜索文档和备注…",
"search_index_status": "全文搜索:已索引 {indexed}/{total} 个文件 · {no_text} 个无文本层 · {pending} 个待处理",
"today_all_caught_up": "全部完成!本周没有待办。",
"today_overdue": "逾期",
"today_due_today": "今天到期",
@@ -456,6 +464,7 @@
"settings_consumable_threshold": "耗材“不足”阈值(%)",
"settings_battery_low_percent": "电池“电量低”阈值(%)",
"settings_thresholds_hint": "用于新的“建议设置”(剩余百分比)以及没有自身 Battery Notes 阈值的电池组电池。现有任务保留其值。",
"settings_value_out_of_range": "数值必须介于 {min} 和 {max} 之间",
"bn_summary": "{name}:{n} 块电池为 {pct}%",
"bn_floor_decides": "你的 {floor}% 阈值对所有电池生效;每块电池取两个阈值中较高者。",
"bn_above_floor": "这高于你的 {floor}% 阈值,因此所有已记录的电池由 {name} 决定。",
@@ -481,6 +490,11 @@
"test_notification_success": "测试通知已发送",
"test_notification_failed": "测试通知发送失败",
"notify_per_person": "按成员分别投递",
"member_avatars": "成员头像",
"member_avatars_hint": "显示在已分配任务旁——以成员颜色显示首字母。默认取自姓名;可自定义首字母或选择颜色以区分成员。",
"member_initials": "首字母",
"member_color": "颜色",
"member_avatar_reset": "重置",
"notify_no_own_device": "无个人设备 — 使用家庭通知服务",
"settings_notify_due_soon": "到期前通知提醒",
"settings_notify_overdue": "超期后通知提醒",
@@ -540,6 +554,7 @@
"group_by_label": "分组依据",
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
"target_changes_help": "触发前需要满足的状态转换次数(默认为 1)。",
"state_latch_help": "设为 1 次转换时触发器为锁存:实体离开“到”状态时恢复;若只设置了“从”状态,则在实体回到该状态时恢复。",
"for_minutes_state_help": "0 表示每次变化立即计数。设置分钟后,新状态需先保持该时长——短暂闪变既不触发也不计数。",
"qr_print_title": "打印二维码",
"qr_print_desc": "生成一张包含多个二维码的可打印页面,方便裁剪并贴在设备上。",
@@ -677,8 +692,14 @@
"doc_delete_confirm": "删除“{name}”?",
"doc_too_large": "文件过大(最大 25 MB)。",
"doc_upload_failed": "上传失败。",
"completion_photo_optional": "完成照片(可选)",
"add_photo": "添加照片",
"completion_photos_optional": "完成照片(可选)",
"completion_photos": "完成照片",
"choose_photos": "选择照片",
"choose_photo": "选择照片",
"photos_android_hint": "Android 应用每次只传一张照片——每次选择都会添加。",
"add_photos": "添加照片",
"photos_limit": "每次完成最多 {max} 张照片",
"history_edit_photos_hint": "在此移除的照片仍保留在对象的文档中。",
"uploading": "上传中…",
"remove": "移除",
"doc_deduped": "已在别处存储——共享,不占用额外空间。",
@@ -733,6 +754,14 @@
"reading_unit_help": "完成此任务时显示在记录值旁边。",
"reading_value_label": "读数值",
"reading_label": "读数",
"readings_section": "读数",
"readings_hint": "每次完成可记录多个命名值——每个表计一行。留空则只记录单个值。",
"reading_name_label": "读数名称",
"reading_unit_short": "单位",
"reading_add": "添加读数",
"reading_duplicate_name": "名称已被使用",
"reading_last": "上次:{value}",
"reading_below_last": "低于上次读数({value})",
"settings_templates_label": "模板库",
"settings_templates_hint": "取消勾选你永远不需要的模板——它们会从\"从模板\"选择器(面板和配置流程)中消失。其他一切不变;可随时重新启用。",
"worksheet": "工作单",
@@ -818,6 +847,12 @@
"settings_notify_scope": "仅为该视图发送通知",
"settings_notify_scope_all": "所有任务",
"settings_notify_scope_hint": "只有匹配所选视图的标签/用户筛选的任务才会发送提醒。视图的状态、排序和分组在此处被忽略。",
"settings_notify_rule": "自定义通知规则",
"settings_notify_rule_hint": "每条通知还会触发事件 maintenance_supporter_notification(类型、状态、对象、任务、参考编号、优先级、深层链接、载荷)。用自动化转发它——例如到 Ticker、Telegram 或 Pushover。",
"settings_notify_event_only": "仅触发事件,自身不发送",
"settings_notify_event_only_hint": "由你的自动化完成全部发送。重复间隔、免打扰时段和每日上限仍决定事件何时触发。",
"settings_notify_extra_data": "附加通知数据(模板)",
"settings_notify_extra_data_hint": "JSON 或 YAML,可用 Jinja;合并进 notify 调用的 data(你的键优先)。变量:kind、status、priority、object_name、task_name、object_ref、task_ref、days_until_due、next_due、responsible_user_id、url、target、title、message。用“发送测试”按钮验证。",
"card_saved_view": "已保存的视图",
"card_saved_view_none": "无",
"card_saved_view_help": "在上述筛选之外再应用该视图的状态、用户和标签筛选。视图的排序和分组属于面板显示设置,不会应用到卡片上。",
@@ -859,6 +894,7 @@
"battery_fleet_mark_all": "全部标记为已更换",
"battery_fleet_mark_one": "标记此电池为已更换",
"battery_fleet_offline": "离线",
"battery_fleet_no_sensor": "无传感器",
"battery_fleet_trigger_lost": "此任务的传感器触发器已丢失——它不会触发,也不会自动完成。",
"battery_fleet_repair": "修复",
"battery_fleet_exclude": "从电池群中排除",
@@ -870,9 +906,12 @@
"battery_fleet_add_hint": "选择自动发现遗漏的电池传感器——它会立即加入列表。",
"battery_fleet_track_self": "跟踪自充电电池",
"battery_fleet_track_self_hint": "手机、吸尘器等自充电设备显示为可充电电池——电量低时提示充电,而不是更换电池。",
"battery_fleet_due_without_sensor": "电池没有传感器时,将已过期的预测视为已到期",
"battery_fleet_due_without_sensor_hint": "有些设备完全不报告电池电量——Battery Notes 只知道类型和上次更换时间。一旦超过典型寿命,这样的电池就会被视为电量低,电池群任务会提醒你;标记为已更换会重置预测。",
"battery_fleet_status_low": "电量低",
"battery_fleet_status_soon": "即将",
"battery_fleet_status_ok": "正常",
"battery_fleet_status_due": "已到期",
"battery_fleet_predicted_on": "预计在 {date} 前后",
"battery_fleet_predicted_trend": "根据此电池的放电趋势预测:约 {date}({confidence})",
"battery_fleet_rechargeable": "可充电电池:充电即可,无需更换——不会出现在购物清单中",
@@ -917,6 +956,24 @@
"service_record_print": "维护记录(PDF)",
"date": "日期",
"service_record_entries": "条记录",
"ref_number": "参考编号",
"print_options_title": "打印保养记录",
"print_layout": "版式",
"print_layout_chronological": "按时间顺序",
"print_layout_by_task": "按任务",
"print_include": "包含",
"print_inc_readings": "读数",
"print_inc_parts": "使用的零件",
"print_inc_photos": "照片",
"print_inc_documents": "关联文档",
"print_inc_checklist": "检查清单",
"print_inc_notes": "备注",
"print_inc_costs": "费用和用时",
"print_inc_person": "完成者",
"print_inc_refs": "参考编号",
"print_inc_qr": "每个任务的二维码",
"print_button": "打印",
"report_scan_hint": "扫描以打开任务",
"completed_by": "完成人",
"date_from": "从",
"date_to": "至",
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.74.0 */
var v="2.74.0";var f="maintenance-supporter",A=`ll-strategy-dashboard-${f}`,L="hui-maintenance-supporter-strategy-editor",D=["maintenance-supporter-section","maintenance-supporter-vacation","maintenance-supporter-budget","maintenance-supporter-groups"],I=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${v}`,w=null;function h(){return w||(w=import(I)),w}async function M(){let e=await h();if(!e.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return e.MaintenanceDashboardStrategy}async function B(e){let i=(await h()).SECTION_STRATEGIES?.[e];if(!i)throw new Error(`[maintenance-supporter] strategy bundle loaded but has no section strategy "${e}"`);return i}function O(e){return class extends HTMLElement{static async generate(i,l){return(await B(e)).generate(i,l)}}}var y=class extends HTMLElement{static getCreateSuggestions(s){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await h(),document.createElement(L)}static async generate(s,i){return(await M()).generate(s,i)}};function P(){try{customElements.define(A,y)}catch{}for(let e of D)try{customElements.define(`ll-strategy-section-${e}`,O(e))}catch{}}P();var g=window;g.customStrategies=g.customStrategies||[];var U=[{type:f,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"},{type:"maintenance-supporter-section",strategyType:"section",name:"Maintenance Supporter \u2014 Section",description:"Embed maintenance tasks (filterable by area, status, due date) as a section in any dashboard view.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#section-strategy"},{type:"maintenance-supporter-vacation",strategyType:"section",name:"Maintenance Supporter \u2014 Vacation Status",description:"Compact vacation-mode status banner. Tap to open settings in the panel."},{type:"maintenance-supporter-budget",strategyType:"section",name:"Maintenance Supporter \u2014 Budget Status",description:"Monthly + yearly maintenance budget overview. Tap for details."},{type:"maintenance-supporter-groups",strategyType:"section",name:"Maintenance Supporter \u2014 Groups",description:"List of configured task groups with member counts."}];for(let e of U)g.customStrategies.some(s=>s.type===e.type&&s.strategyType===e.strategyType)||g.customStrategies.push(e);(()=>{let e=window;if(e.__msStrategyHealActive)return;e.__msStrategyHealActive=!0;let s=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,i=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,l=`custom:${f}`;function S(r){let t=[document.documentElement],a=0;for(;t.length&&a<9e3;){let o=t.pop();if(a++,!o)continue;let n=o;if(n.nodeType===1&&n.tagName&&n.tagName.toLowerCase()===r)return n;n.shadowRoot&&t.push(n.shadowRoot);let u=o.children;if(u)for(let p of Array.from(u))t.push(p)}return null}function R(r){let t=r?.views;if(!Array.isArray(t)||!t.length)return null;let a=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(u=>u?.path===a);if(o)return o;let n=Number(a);return Number.isInteger(n)&&t[n]?t[n]:t[0]}function k(){try{let t=S("ha-panel-lovelace")?.lovelace;if(!t)return!1;let a=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(a(o.strategy)===l)return!0;let n=R(o);if(n&&a(n.strategy)===l)return!0}return!1}catch{return!1}}function T(){let r=!1,t=0,a=!1,o=!1,n=[document.documentElement],u=0;for(;n.length&&u<9e3;){let p=n.pop();if(u++,!p)continue;let d=p;if(d.nodeType===1&&d.tagName){let c=d.tagName.toLowerCase();(c==="hui-view"||c==="hui-sections-view")&&(r=!0),(c==="ha-card"||c==="hui-card")&&t++,c==="hui-empty-state-card"&&(o=!0),c==="hui-error-card"&&i.test(d.textContent||"")&&(a=!0)}d.shadowRoot&&n.push(d.shadowRoot);let _=p.children;if(_)for(let c of Array.from(_))n.push(c)}return a?!0:o?!1:r&&t<3&&k()}let N="/maintenance_supporter_strategy_shim.js",E=0,b=0;function C(){let r=Date.now();r-b<5e3||E>=3||(b=r,E+=1,import(`${N}?heal=${r}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function m(){if(s.test(window.location.pathname))return;let r=0,t=Date.now(),a=window.setInterval(()=>{r++;try{if(Date.now()-t<6e3)return;if(s.test(window.location.pathname)){window.clearInterval(a);return}T()?C():window.clearInterval(a),r>=30&&window.clearInterval(a)}catch{window.clearInterval(a)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",m):m(),wLine truncated
/*! maintenance_supporter frontend 2.80.0 */
var v="2.80.0";var f="maintenance-supporter",A=`ll-strategy-dashboard-${f}`,L="hui-maintenance-supporter-strategy-editor",D=["maintenance-supporter-section","maintenance-supporter-vacation","maintenance-supporter-budget","maintenance-supporter-groups"],I=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${v}`,w=null;function h(){return w||(w=import(I)),w}async function M(){let e=await h();if(!e.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return e.MaintenanceDashboardStrategy}async function B(e){let i=(await h()).SECTION_STRATEGIES?.[e];if(!i)throw new Error(`[maintenance-supporter] strategy bundle loaded but has no section strategy "${e}"`);return i}function O(e){return class extends HTMLElement{static async generate(i,l){return(await B(e)).generate(i,l)}}}var y=class extends HTMLElement{static getCreateSuggestions(s){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await h(),document.createElement(L)}static async generate(s,i){return(await M()).generate(s,i)}};function P(){try{customElements.define(A,y)}catch{}for(let e of D)try{customElements.define(`ll-strategy-section-${e}`,O(e))}catch{}}P();var g=window;g.customStrategies=g.customStrategies||[];var U=[{type:f,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"},{type:"maintenance-supporter-section",strategyType:"section",name:"Maintenance Supporter \u2014 Section",description:"Embed maintenance tasks (filterable by area, status, due date) as a section in any dashboard view.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#section-strategy"},{type:"maintenance-supporter-vacation",strategyType:"section",name:"Maintenance Supporter \u2014 Vacation Status",description:"Compact vacation-mode status banner. Tap to open settings in the panel."},{type:"maintenance-supporter-budget",strategyType:"section",name:"Maintenance Supporter \u2014 Budget Status",description:"Monthly + yearly maintenance budget overview. Tap for details."},{type:"maintenance-supporter-groups",strategyType:"section",name:"Maintenance Supporter \u2014 Groups",description:"List of configured task groups with member counts."}];for(let e of U)g.customStrategies.some(s=>s.type===e.type&&s.strategyType===e.strategyType)||g.customStrategies.push(e);(()=>{let e=window;if(e.__msStrategyHealActive)return;e.__msStrategyHealActive=!0;let s=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,i=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,l=`custom:${f}`;function S(r){let t=[document.documentElement],a=0;for(;t.length&&a<9e3;){let o=t.pop();if(a++,!o)continue;let n=o;if(n.nodeType===1&&n.tagName&&n.tagName.toLowerCase()===r)return n;n.shadowRoot&&t.push(n.shadowRoot);let u=o.children;if(u)for(let p of Array.from(u))t.push(p)}return null}function R(r){let t=r?.views;if(!Array.isArray(t)||!t.length)return null;let a=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(u=>u?.path===a);if(o)return o;let n=Number(a);return Number.isInteger(n)&&t[n]?t[n]:t[0]}function k(){try{let t=S("ha-panel-lovelace")?.lovelace;if(!t)return!1;let a=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(a(o.strategy)===l)return!0;let n=R(o);if(n&&a(n.strategy)===l)return!0}return!1}catch{return!1}}function T(){let r=!1,t=0,a=!1,o=!1,n=[document.documentElement],u=0;for(;n.length&&u<9e3;){let p=n.pop();if(u++,!p)continue;let d=p;if(d.nodeType===1&&d.tagName){let c=d.tagName.toLowerCase();(c==="hui-view"||c==="hui-sections-view")&&(r=!0),(c==="ha-card"||c==="hui-card")&&t++,c==="hui-empty-state-card"&&(o=!0),c==="hui-error-card"&&i.test(d.textContent||"")&&(a=!0)}d.shadowRoot&&n.push(d.shadowRoot);let _=p.children;if(_)for(let c of Array.from(_))n.push(c)}return a?!0:o?!1:r&&t<3&&k()}let N="/maintenance_supporter_strategy_shim.js",E=0,b=0;function C(){let r=Date.now();r-b<5e3||E>=3||(b=r,E+=1,import(`${N}?heal=${r}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function m(){if(s.test(window.location.pathname))return;let r=0,t=Date.now(),a=window.setInterval(()=>{r++;try{if(Date.now()-t<6e3)return;if(s.test(window.location.pathname)){window.clearInterval(a);return}T()?C():window.clearInterval(a),r>=30&&window.clearInterval(a)}catch{window.clearInterval(a)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",m):m(),wLine truncated
@@ -0,0 +1,250 @@
/*! 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`
<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};
@@ -0,0 +1,64 @@
/*! 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);
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{q as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.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};
@@ -0,0 +1,79 @@
/*! 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`
<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);
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.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,153 @@
/*! 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};
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,2 @@
/*! 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};
@@ -0,0 +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};
@@ -0,0 +1,393 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as b,g as E,l as S}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XMX3E3HG.js";import{a as A}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import{K as P,a as o,b as y,c as a,f as c,h as x,l as n,m as p,q as r,w as $,x as w}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";function I(){if(window.externalApp!==void 0)return!0;let h=typeof navigator<"u"&&navigator.userAgent||"";return/Home Assistant\//.test(h)&&/Android/.test(h)}function N(g){if(!g)return[];let h=[],e=g.photo_doc_id;typeof e=="string"&&h.push(e);let t=g.photo_doc_ids;Array.isArray(t)&&h.push(...t);let i=[];for(let l of h){if(typeof l!="string")continue;let d=l.trim();if(!(!d||i.includes(d))&&(i.push(d),i.length>=10))break}return i}async function q(g,h,e){let t=new FormData;t.append("entry_id",h),t.append("tags","photo"),t.append("file",e,e.name);let i=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${g.auth?.data?.access_token??""}`},body:t});if(i.status===413)throw new Error("doc_too_large");if(!i.ok)throw new Error("doc_upload_failed");let l=await i.json();if(!l.id)throw new Error("doc_upload_failed");return l.id}async function v(g,h){await Promise.all(h.map(e=>g.connection.sendMessagePromise({type:"maintenance_supporter/documents/delete",doc_id:e}).catch(()=>{})))}var s=class extends x{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.readings=[];this.readingHistory=[];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._photos=[];this._uploadedIds=[];this._photoUploading=!1;this._readingValue="";this._readingValues={};this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={};this._singlePick=I()}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,i)=>[String(i),!!this.checklistPrefill[t]]).filter(([,t])=>t)),this._feedback="needed",this._photos.forEach(t=>URL.revokeObjectURL(t.preview)),this._photos=[],this._uploadedIds=[],this._photoUploading=!1,this._readingValue="",this._readingValues={},this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(t=>[b(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,i=Array.from(t.files??[]);if(t.value="",i.length===0)return;let l=10-this._photos.length,d=i.slice(0,Math.max(l,0));this._photoUploading=!0,this._error="";try{for(let u of d){let f=await q(this.hass,this.entryId,u);this._uploadedIds=[...this._uploadedIds,f],this._photos=[...this._photos,{id:f,preview:URL.createObjectURL(u)}]}i.length>d.length&&(this._error=r("photos_limit",this.lang).replace("{max}",String(10)))}catch(u){let f=u instanceof Error&&u.message==="doc_too_large"?"doc_too_large":"doc_upload_failed";this._error=r(f,this.lang)}finally{this._photoUploading=!1}}_removePhoto(e){let t=this._photos.find(i=>i.id===e);t&&URL.revokeObjectURL(t.preview),this._photos=this._photos.filter(i=>i.id!==e),this._uploadedIds.includes(e)&&(this._uploadedIds=this._uploadedIds.filter(i=>i!==e),v(this.hass,[e]))}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._photos.length>0&&(e.photo_doc_ids=this._photos.map(t=>t.id)),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.readings.length>0){let t={};for(let i of this.readings){let l=(this._readingValues[i.id]??"").trim();if(l==="")continue;let d=parseFloat(l.replace(",","."));isNaN(d)||(t[i.id]=d)}Object.keys(t).length>0&&(e.reading_values=t)}else if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restoLine truncated
<label class="field reading-field">
<span class="field-label">${e.name}${d?` (${d})`:""}</span>
<input type="text" inputmode="decimal" class="field-input"
placeholder=${l!==void 0?r("reading_last",t).replace("{value}",k):""}
.value=${this._readingValues[e.id]??""}
@input=${T=>{this._readingValues={...this._readingValues,[e.id]:T.target.value}}} />
${_?a`<span class="reading-warn">${r("reading_below_last",t).replace("{value}",k)}</span>`:c}
</label>`}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photos.length>0,user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a`<span class="req-mark" aria-hidden="true">*</span>`:c}_partsCostSuggestion(){if(this.restockDefault!==null){let i=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(i)||i<=0?null:Math.round(this.restockUnitCost*i*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let i of Object.values(this._usedParts)){let l=this.parts.find(d=>b({part_id:d.id,entry_id:d.entry_id})===b(i));l?.cost!=null&&(e+=l.cost*(i.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return c;let t=this._partsCostSuggestion();if(t==null||t<=0)return c;let i=w(t,this.currencySymbol,e);return a`<button
type="button"
class="cost-suggestion"
@click=${()=>this._cost=String(Math.round(t*100)/100)}
>${r("cost_from_parts",e).replace("{amount}",i)}</button>`}_close(){if(this._open=!1,this._uploadedIds.length>0){let e=this._uploadedIds;this._uploadedIds=[],v(this.hass,e)}}_pickCompletedAt(){let e=new Date,t=i=>String(i).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 a``;let e=this.lang||this.hass?.language||"en";return a`
<ha-dialog open @closed=${this._close}>
<div class="dialog-title">${r("complete_title",e)}${this.taskName}</div>
${this.phaseLabel?a`<div class="phase-line">${r("phase_current",e)}: ${this.phaseLabel}</div>`:c}
${this.requireTagScan&&!this.viaTagScan?a`<div class="scan-required-note">${r("require_tag_scan_hint",e)}</div>`:c}
<div class="content">
${this._error?a`<div class="error">${this._error}</div>`:c}
${this.checklist.length>0?a`
<div class="checklist-section">
<label class="checklist-label">${r("checklist",e)}</label>
${this.checklist.map((t,i)=>a`
<label class="checklist-item" @click=${()=>this._toggleCheck(i)}>
<input type="checkbox" .checked=${!!this._checklistState[String(i)]} />
<span>${t}</span>
</label>
`)}
</div>
`:c}
${this.readings.length>0?a`<div class="readings-block">
<span class="field-label">${r("readings_section",e)}</span>
${this.readings.map(t=>this._renderReadingField(t,e))}
</div>`:this.taskType==="reading"?a`
<label class="field">
<span class="field-label">${r("reading_value_label",e)}${this.readingUnit?` (${this.readingUnit})`:""}</span>
<input type="number" step="any" class="field-input"
.value=${this._readingValue}
@input=${t=>this._readingValue=t.target.value} />
</label>`:c}
${this.parts.length?a`<div class="used-parts">
<span class="field-label">${r("complete_parts_used",e)}</span>
${this.parts.map(t=>{let i=b({part_id:t.id,entry_id:t.entry_id}),l=this._usedParts[i],d=l!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return a`<div class="used-part-row">
<label class="used-part-check">
<input type="checkbox" .checked=${d}
@change=${f=>{let _={...this._usedParts};f.target.checked?_[i]=_[i]||u:delete _[i],this._usedParts=_}} />
<span
>${t.name}${t.owner_name?a`<span class="used-part-owner"> (${t.owner_name})</span>`:c}${t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:""}</span
>
</label>
${d?a`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(l.quantity)}
@input=${f=>{let _=parseFloat(f.target.value);this._usedParts={...this._usedParts,[i]:{...u,quantity:Number.isFinite(_)&&_>=.01?_:1}}}} />`:c}
</div>`})}
</div>`:this.consumesInfo.length?a`<div class="consumes-hint">
${this.consumesInfo.map(t=>a`<div>${t}</div>`)}
</div>`:c}
${this.restockDefault!==null?a`
<label class="field">
<span class="field-label">${r("restock_quantity_label",e)}</span>
<input type="number" step="0.01" min="0.01" class="field-input"
.value=${this._restockQty}
@input=${t=>this._restockQty=t.target.value} />
</label>`:c}
<!-- 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?a`<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>`:a`<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_photos_optional",e)}${this._req("photo")}</span>
${this._photos.length>0?a`<div class="photo-strip">
${this._photos.map(t=>a`
<div class="photo-preview">
<img src=${t.preview} alt="" />
<button type="button" class="photo-remove" @click=${()=>this._removePhoto(t.id)}
title="${r("remove",e)}">✕</button>
</div>`)}
</div>`:c}
${this._photos.length<10?a`<div class="photo-pickers">
<label class="photo-pick photo-pick-camera">
<ha-icon icon="mdi:camera"></ha-icon>
<span>${this._photoUploading?r("uploading",e):r("doc_camera",e)}</span>
<input type="file" accept="image/*" capture="environment"
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>
<label class="photo-pick photo-pick-gallery">
<ha-icon icon="mdi:image-multiple"></ha-icon>
<span>${r(this._singlePick?"choose_photo":"choose_photos",e)}</span>
<input type="file" accept="image/*" ?multiple=${!this._singlePick}
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>
</div>
${this._singlePick?a`<div class="photo-limit photo-android-hint">${r("photos_android_hint",e)}</div>`:c}`:a`<div class="photo-limit">${r("photos_limit",e).replace("{max}",String(10))}</div>`}
</div>
${this.adaptiveEnabled?a`
<div class="feedback-section">
<label class="feedback-label">${r("was_maintenance_needed",e)}</label>
<div class="feedback-buttons">
<button
class="feedback-btn ${this._feedback==="needed"?"selected":""}"
@click=${()=>this._setFeedback("needed")}
>${r("feedback_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_needed"?"selected":""}"
@click=${()=>this._setFeedback("not_needed")}
>${r("feedback_not_needed",e)}</button>
<button
class="feedback-btn ${this._feedback==="not_sure"?"selected":""}"
@click=${()=>this._setFeedback("not_sure")}
>${r("feedback_not_sure",e)}</button>
</div>
</div>
`:c}
</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(S[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};s.styles=[P,y`
.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 */
/* #161 phase 2: the per-slot reading fields */
.readings-block { display: flex; flex-direction: column; gap: 8px; }
.readings-block > .field-label { margin-bottom: -4px; }
.reading-warn {
font-size: 12px;
color: var(--warning-color, #ff9800);
}
.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; }
/* #161: several photos per completion — tiles wrap into a strip,
the two pickers (camera / gallery) sit underneath while there is
room left under the cap. */
.photo-pickers {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.photo-strip {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 8px 0; /* room for the remove badges above the tiles */
}
.photo-limit {
font-size: 12px;
color: var(--secondary-text-color);
}
.photo-android-hint { margin-top: 4px; }
.photo-preview {
position: relative;
width: fit-content;
}
/* Uniform tiles: a tiny or portrait shot must not collapse the strip. */
.photo-preview img {
width: 96px;
height: 96px;
object-fit: cover;
border-radius: 8px;
display: block;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
}
.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);
}
`],o([n({attribute:!1})],s.prototype,"hass",2),o([n()],s.prototype,"entryId",2),o([n()],s.prototype,"taskId",2),o([n()],s.prototype,"taskName",2),o([n()],s.prototype,"lang",2),o([n({type:Array})],s.prototype,"checklist",2),o([n({type:Boolean})],s.prototype,"adaptiveEnabled",2),o([n()],s.prototype,"taskType",2),o([n()],s.prototype,"readingUnit",2),o([n({attribute:!1})],s.prototype,"readings",2),o([n({attribute:!1})],s.prototype,"readingHistory",2),o([n({attribute:!1})],s.prototype,"restockDefault",2),o([n({attribute:!1})],s.prototype,"restockUnitCost",2),o([n()],s.prototype,"currencySymbol",2),o([n({attribute:!1})],s.prototype,"parts",2),o([n({attribute:!1})],s.prototype,"consumesParts",2),o([n({type:Array})],s.prototype,"consumesInfo",2),o([n({type:Array})],s.prototype,"requiredFields",2),o([n()],s.prototype,"phaseLabel",2),o([n({type:Boolean})],s.prototype,"requireTagScan",2),o([n({type:Boolean})],s.prototype,"viaTagScan",2),o([p()],s.prototype,"_open",2),o([p()],s.prototype,"_notes",2),o([p()],s.prototype,"_cost",2),o([p()],s.prototype,"_duration",2),o([p()],s.prototype,"_loading",2),o([p()],s.prototype,"_error",2),o([p()],s.prototype,"_checklistState",2),o([p()],s.prototype,"_feedback",2),o([p()],s.prototype,"_photos",2),o([p()],s.prototype,"_photoUploading",2),o([p()],s.prototype,"_readingValue",2),o([p()],s.prototype,"_readingValues",2),o([p()],s.prototype,"_restockQty",2),o([p()],s.prototype,"_completedAt",2),o([p()],s.prototype,"_usedParts",2),o([n({attribute:!1})],s.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",s);export{N as a,q as b,v as c,s as d};
@@ -0,0 +1,939 @@
/*! maintenance_supporter frontend 2.80.0 */
import{b as ut,f as B,g as yt}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-YQMBLRSW.js";import{a as Q,b as gt,c as X}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VRRFDTZU.js";import{b as ht,c as _t,e as J,f as mt,h as ft}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-XMX3E3HG.js";import{a as T}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import{A as ot,C as lt,H as Z,I as dt,J as ct,M as pt,a as h,b as C,c as l,d as z,f as u,h as H,l as P,m,n as U,q as a,s as j,t as it,u as st,v as at,w as x,y as rt,z as N}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";var R=class extends H{constructor(){super(...arguments);this.docId="";this._url="";this._failed=!1;this._signedFor=""}updated(){this.hass&&this.docId&&this._signedFor!==this.docId&&(this._signedFor=this.docId,this._url="",this._failed=!1,this._sign())}async _sign(){try{this._url=await ut(this.hass,this.docId)}catch{this._failed=!0}}render(){return this._failed||!this.docId?u:this._url?l`
<a href=${this._url} target="_blank" rel="noopener" class="wrap">
<img src=${this._url} alt="" loading="lazy"
@error=${()=>this._failed=!0} />
</a>`:l`<div class="ph"></div>`}};R.styles=C`
.wrap { display: inline-block; margin-top: 4px; }
/* #161: uniform 96px tiles — several photos sit in a strip, so a
tiny or portrait shot must not collapse its slot. */
img {
width: 96px;
height: 96px;
object-fit: cover;
border-radius: 6px;
display: block;
border: 1px solid var(--divider-color);
box-sizing: border-box;
}
.ph {
width: 96px;
height: 96px;
border-radius: 6px;
background: var(--secondary-background-color, rgba(0,0,0,0.06));
margin-top: 4px;
}
`,h([P({attribute:!1})],R.prototype,"hass",2),h([P()],R.prototype,"docId",2),h([m()],R.prototype,"_url",2),h([m()],R.prototype,"_failed",2);customElements.get("maintenance-history-photo")||customElements.define("maintenance-history-photo",R);var w=class extends H{constructor(){super(...arguments);this._open=!1;this._saving=!1;this._error="";this._draft=null;this._originalSnapshot=null;this._partOptions=null;this._partQty={};this._partQtyOriginal="";this._photos=[];this._photosOriginal="";this._readingRows=[];this._readingText={};this._readingsOriginal="";this._uploadedIds=[];this._photoUploading=!1}get _lang(){return j(this.hass)}openEdit(t){this._draft={...t},this._originalSnapshot={...t},this._error="",this._open=!0,this._partOptions=null,this._partQty={},this._partQtyOriginal="",this._photos=[...t.photo_doc_ids??[]],this._photosOriginal=JSON.stringify(this._photos),this._uploadedIds=[],this._photoUploading=!1,this._seedReadings(t),this._loadPartOptions()}_seedReadings(t){let n=[],s=new Set;for(let o of t.readings??[])s.has(o.id)||(s.add(o.id),n.push({id:o.id,name:o.name,unit:o.unit??null}));for(let o of t.reading_values??[])s.has(o.id)||(s.add(o.id),n.push({id:o.id,name:o.name,unit:o.unit??null}));this._readingRows=n;let r={};for(let o of t.reading_values??[])r[o.id]=String(o.value);this._readingText=r,this._readingsOriginal=JSON.stringify(this._readingNumbers())}_readingNumbers(){let t={};for(let n of this._readingRows){let s=(this._readingText[n.id]??"").trim();if(s==="")continue;let r=parseFloat(s.replace(",","."));isNaN(r)||(t[n.id]=r)}return t}async _onPhotoInput(t){let n=t.target,s=Array.from(n.files??[]);n.value="";let r=this._draft;if(s.length===0||!r)return;let o=10-this._photos.length,d=s.slice(0,Math.max(o,0));this._photoUploading=!0,this._error="";try{for(let c of d){let p=await gt(this.hass,r.entry_id,c);this._uploadedIds=[...this._uploadedIds,p],this._photos=[...this._photos,p]}s.length>d.length&&(this._error=a("photos_limit",this._lang).replace("{max}",String(10)))}catch(c){let p=c instanceof Error&&c.message==="doc_too_large"?"doc_too_large":"doc_upload_failed";this._error=a(p,this._lang)}finally{this._photoUploading=!1}}_removePhoto(t){this._photos=this._photos.filter(n=>n!==t),this._uploadedIds.includes(t)&&(this._uploadedIds=this._uploadedIds.filter(n=>n!==t),X(this.hass,[t]))}async _loadPartOptions(){let t=this._draft;if(t)try{let n=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/parts/overview"}),s=[];for(let o of n.parts||[]){let d=o.entry_id===t.entry_id,c=o.consumers.some(p=>p.entry_id===t.entry_id&&p.task_id===t.task_id);!d&&!c||s.push({part_id:o.part_id,name:o.name,entry_id:o.entry_id,foreign:!d,object_name:o.object_name})}for(let o of t.used_parts||[]){let d=o.entry_id||t.entry_id;s.some(c=>c.part_id===o.part_id&&c.entry_id===d)||s.push({part_id:o.part_id,name:o.name||o.part_id,entry_id:d,foreign:d!==t.entry_id,object_name:null})}let r={};for(let o of t.used_parts||[])r[`${o.entry_id||t.entry_id}:${o.part_id}`]=o.quantity??1;this._partOptions=s,this._partQty=r,this._partQtyOriginal=this._partSelectionKey()}catch{this._partOptions=[]}}_partSelectionKey(){return JSON.stringify(Object.entries(this._partQty).filter(([,t])=>t>0).sort(([t],[n])=>t.localeCompare(n)))}close(){if(this._open=!1,this._error="",this._draft=null,this._originalSnapshot=null,this._uploadedIds.length>0){let t=this._uploadedIds;this._uploadedIds=[],X(this.hass,t)}}_set(t,n){this._draft&&(this._draft={...this._draft,[t]:n})}async _save(){if(!(!this._draft||!this._originalSnapshot)){this._saving=!0,this._error="";try{let t={type:"maintenance_supporter/task/history/update",entry_id:this._draft.entry_id,task_id:this._draft.task_id,original_timestamp:this._originalSnapshot.original_timestamp};if(this._draft.timestamp!==this._originalSnapshot.timestamp&&(t.timestamp=this._draft.timestamp),this._draft.notes!==this._originalSnapshot.notes&&(t.notes=this._draft.notes),this._draft.cost!==this._originalSnapshot.cost&&(t.cost=this._draft.cost),this._draft.duration!==this._originalSnapshot.duration&&(t.duration=this._draft.duration),this._draft.completed_by!==this._originalSnapshot.completed_by&&(t.completed_by=this._draft.completed_by),this._partOptions!==null&&this._partSelectionKey()!==this._partQtyOriginal&&(t.used_parts=(this._partOptions||[]).filter(s=>(this._partQty[`${s.entry_id}:${s.part_id}`]||0)>0).map(s=>({part_id:s.part_id,quantity:this._partQty[`${s.entry_id}:${s.part_id}`],...s.foreign?{entry_id:s.entry_id}:{}}))),this._draft.reading_value!==this._originalSnapshot.reading_value&&(t.reading_value=this._draft.reading_value??null),this._readingRows.length>0&&JSON.stringify(this._readingNumbers())!==this._readingsOriginal){let s=this._readingNumbers(),r={};for(let o of this._readingRows)r[o.id]=s[o.id]??null;t.reading_values=r}if(JSON.stringify(this._photos)!==this._photosOriginal&&(t.photo_doc_ids=[...this._photos]),Object.keys(t).filter(s=>!["type","entry_id","task_id","original_ti Line truncated
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
<h2>${a("history_edit_title",t)||"Edit history entry"}</h2>
<div class="entry-type">
<ha-icon icon="mdi:tag-outline"></ha-icon>
<span>${a(n.type,t)||n.type}</span>
</div>
<ms-date-field
kind="datetime"
required
.hass=${this.hass}
.lang=${t}
.label=${a("history_edit_timestamp",t)||"Timestamp"}
.value=${n.timestamp.slice(0,19)}
@value-changed=${s=>{let r=s.detail.value;r&&this._set("timestamp",r)}}
></ms-date-field>
<label>
<span>${a("notes_label",t)}</span>
<textarea
rows="3"
@input=${s=>{let r=s.target.value;this._set("notes",r||null)}}
.value=${n.notes??""}></textarea>
</label>
<div class="row">
<label>
<span>${a("cost",t)||"Cost"}</span>
<input type="number" min="0" step="0.01"
.value=${n.cost!=null?String(n.cost):""}
@input=${s=>{let r=s.target.value;this._set("cost",r?Number(r):null)}} />
</label>
<label>
<span>${a("duration",t)||"Duration (min)"}</span>
<input type="number" min="0"
.value=${n.duration!=null?String(n.duration):""}
@input=${s=>{let r=s.target.value;this._set("duration",r?Number(r):null)}} />
</label>
</div>
${this._renderReadings(n,t)}
${this._partOptions&&this._partOptions.length>0?l`
<div class="parts-block">
<span class="parts-title">${a("complete_parts_used",t)}</span>
${this._partOptions.map(s=>{let r=`${s.entry_id}:${s.part_id}`,o=this._partQty[r]||0;return l`
<label class="part-row-edit">
<input type="checkbox" .checked=${o>0}
@change=${d=>{let c=d.target.checked;this._partQty={...this._partQty,[r]:c?1:0}}} />
<span class="part-label">${s.name}${s.foreign&&s.object_name?` (${s.object_name})`:""}</span>
${o>0?l`
<input class="part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(o)}
@input=${d=>{let c=parseFloat(d.target.value);!isNaN(c)&&c>0&&(this._partQty={...this._partQty,[r]:c})}} />
`:u}
</label>
`})}
</div>
`:u}
<div class="photos-block">
<span class="parts-title">${a("completion_photos",t)}</span>
${this._photos.length>0?l`
<div class="photo-strip">
${this._photos.map(s=>l`
<div class="photo-tile">
<maintenance-history-photo .hass=${this.hass} .docId=${s}></maintenance-history-photo>
<button type="button" class="photo-remove" title=${a("remove",t)}
@click=${()=>this._removePhoto(s)}>✕</button>
</div>`)}
</div>`:u}
${this._photos.length<10?l`
<label class="photo-add">
<ha-icon icon="mdi:image-plus"></ha-icon>
<span>${this._photoUploading?a("uploading",t):a("add_photos",t)}</span>
<input type="file" accept="image/*" multiple
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>`:l`<span class="photos-hint">${a("photos_limit",t).replace("{max}",String(10))}</span>`}
<span class="photos-hint">${a("history_edit_photos_hint",t)}</span>
</div>
${this._error?l`<div class="error">${this._error}</div>`:u}
<div class="actions">
<button class="cancel" @click=${this.close} ?disabled=${this._saving}>
${a("cancel",t)||"Cancel"}
</button>
<button class="save" @click=${this._save} ?disabled=${this._saving}>
${this._saving?a("saving",t)||"Saving\u2026":a("save",t)||"Save"}
</button>
</div>
</div>
`}_renderReadings(t,n){return this._readingRows.length>0?l`
<div class="readings-block">
<span class="parts-title">${a("readings_section",n)}</span>
${this._readingRows.map(s=>l`
<label class="reading-row-edit">
<span class="reading-row-name">${s.name}${s.unit?` (${s.unit})`:""}</span>
<input type="text" inputmode="decimal" class="reading-row-input"
.value=${this._readingText[s.id]??""}
@input=${r=>{this._readingText={...this._readingText,[s.id]:r.target.value}}} />
</label>
`)}
</div>`:t.reading_value==null&&t.task_type!=="reading"?u:l`
<label>
<span>${a("reading_value_label",n)}${t.reading_unit?` (${t.reading_unit})`:""}</span>
<input type="number" step="any"
.value=${t.reading_value!=null?String(t.reading_value):""}
@input=${s=>{let r=s.target.value,o=r===""?NaN:Number(r);this._set("reading_value",isNaN(o)?null:o)}} />
</label>`}};w.styles=C`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0;
background: rgba(0,0,0,0.5);
z-index: 100;
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: 480px;
background: var(--card-background-color, var(--ha-card-background, #1c1c1c));
color: var(--primary-text-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
padding: 20px;
display: flex; flex-direction: column; gap: 12px;
z-index: 101;
max-height: 90vh; overflow: auto;
}
h2 { margin: 0; font-size: 18px; }
.entry-type {
display: flex; align-items: center; gap: 6px;
color: var(--secondary-text-color); font-size: 13px;
}
label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; }
label span { color: var(--secondary-text-color); }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
input, textarea {
padding: 8px; font-size: 14px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color, #444);
border-radius: 6px;
width: 100%; box-sizing: border-box;
font-family: inherit;
}
.actions {
display: flex; gap: 8px; justify-content: flex-end;
margin-top: 8px;
}
button {
padding: 8px 16px; font-size: 14px;
border-radius: 6px; cursor: pointer;
border: none; font-weight: 500;
}
button.cancel {
background: transparent;
color: var(--primary-text-color);
border: 1px solid var(--divider-color);
}
button.save {
background: var(--primary-color);
color: var(--text-primary-color, white);
}
button[disabled] { opacity: 0.5; cursor: wait; }
.error {
color: var(--error-color, #d32f2f);
font-size: 13px; padding: 8px;
background: rgba(211,47,47,0.1);
border-radius: 6px;
}
/* #130: parts on the entry */
.parts-block {
display: flex; flex-direction: column; gap: 6px;
border: 1px solid var(--divider-color, #444);
border-radius: 6px; padding: 8px;
}
.parts-title { color: var(--secondary-text-color); font-size: 13px; }
.part-row-edit {
display: flex; flex-direction: row; align-items: center; gap: 8px;
font-size: 14px;
}
.part-row-edit input[type="checkbox"] { width: auto; }
.part-label { flex: 1; color: var(--primary-text-color); }
.part-qty { width: 76px; }
/* #161 phase 2: readings on the entry */
.readings-block {
display: flex; flex-direction: column; gap: 6px;
border: 1px solid var(--divider-color, #444);
border-radius: 6px; padding: 8px;
}
.reading-row-edit {
display: flex; flex-direction: row; align-items: center; gap: 8px;
font-size: 14px;
}
.reading-row-name { flex: 1; color: var(--primary-text-color); min-width: 0; }
.reading-row-input { width: 140px; font-variant-numeric: tabular-nums; }
/* #161: photos on the entry */
.photos-block {
display: flex; flex-direction: column; gap: 6px;
border: 1px solid var(--divider-color, #444);
border-radius: 6px; padding: 8px;
}
.photo-strip { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 6px; }
.photo-tile { position: relative; width: fit-content; }
.photo-remove {
position: absolute; top: -4px; right: -8px;
width: 22px; height: 22px; border-radius: 50%; border: none;
background: var(--error-color, #db4437); color: #fff;
cursor: pointer; font-size: 11px; line-height: 1; padding: 0;
}
.photo-add {
display: inline-flex; flex-direction: row; align-items: center; gap: 8px;
width: fit-content; padding: 6px 10px;
border: 1px dashed var(--divider-color, #444); border-radius: 8px;
cursor: pointer; font-size: 13px; color: var(--secondary-text-color);
--mdc-icon-size: 18px;
}
.photo-add:hover { border-color: var(--primary-color); }
.photo-add input[type="file"] { display: none; }
.photos-hint { font-size: 12px; color: var(--secondary-text-color); }
`,h([P({attribute:!1})],w.prototype,"hass",2),h([m()],w.prototype,"_open",2),h([m()],w.prototype,"_saving",2),h([m()],w.prototype,"_error",2),h([m()],w.prototype,"_draft",2),h([m()],w.prototype,"_partOptions",2),h([m()],w.prototype,"_partQty",2),h([m()],w.prototype,"_photos",2),h([m()],w.prototype,"_readingRows",2),h([m()],w.prototype,"_readingText",2),h([m()],w.prototype,"_photoUploading",2);customElements.get("maintenance-history-edit-dialog")||customElements.define("maintenance-history-edit-dialog",w);var Ot={days:1,weeks:7,months:30.4368,years:365.25};function tt(e,i){return!e||e<=0?0:e*(Ot[i||"days"]??1)}function ye(e,i,t){let n=tt(e,t);if(n<=0||i==null)return{pct:0,overflow:!1};let s=(n-i)/n*100;return{pct:Math.max(0,Math.min(100,s)),overflow:s>100}}var vt=5;function q(e){let i=e.getFullYear(),t=String(e.getMonth()+1).padStart(2,"0"),n=String(e.getDate()).padStart(2,"0");return`${i}-${t}-${n}`}function zt(e,i){let t=[];for(let n=0;n<i;n++){let s=new Date(e);s.setDate(s.getDate()+n),s.setHours(0,0,0,0),t.push(q(s))}return t}function G(e,i){let[t,n,s]=e.split("-").map(Number),r=new Date(t,n-1,s);return r.setDate(r.getDate()+i),q(r)}function Nt(e){if(!e||e.length===0)return null;let i=e.map(t=>t.cost).filter(t=>typeof t=="number");return i.length===0?null:i.reduce((t,n)=>t+n,0)/i.length}function qt(e){let{windowStart:i,windowEnd:t,task:n,entryId:s,objectName:r}=e,o=[],d=(f,v)=>({date:f,entry_id:s,task_id:n.id,task_name:n.name,object_name:r,status:v&&(n.status==="overdue"||n.status==="triggered")?"ok":n.status,days_until_due:v?null:n.days_until_due??null,projected:v,schedule_type:n.schedule_type,interval_days:n.interval_days??null,interval_unit:n.interval_unit??null,responsible_user_id:n.responsible_user_id??null,avg_cost:Nt(n.history),adaptive_enabled:!!n.adaptive_config?.enabled,prediction_confidence:n.threshold_prediction_confidence??null}),c=Math.max(1,Math.round(tt(n.interval_days,n.interval_unit)));if(n.status==="overdue"||n.status==="triggered"){if(o.push(d(i,!1)),n.schedule_type==="time_based"&&n.interval_days&&n.interval_days>0){let f=G(i,c),v=1;for(;f<=t&&v<vt;)o.push(d(f,!0)),v++,f=G(f,c)}return o}let p=n.next_due;if(typeof p!="string"||!p)return o;let _=p.slice(0,10);if(_>=i&&_<=t)o.push(d(_,!1));else if(_>t)return o;if(n.schedule_type==="time_based"&&n.interval_days&&n.interval_days>0){let f=G(_,c),v=o.length;for(;f<=t&&v<vt;)f>=i&&(o.push(d(f,!0)),v++),f=G(f,c)}return o}var bt={overdue:0,triggered:1,due_soon:2,ok:3};function xe(e,i,t,n=null){let s=zt(i,t),r=s[0],o=s[s.length-1],d=[];for(let p of e){let _=p.object?.name||"",f=p.entry_id,v=p.tasks||[];for(let g of v){if(n&&g.responsible_user_id!==n||g.enabled===!1)continue;let k=qt({windowStart:r,windowEnd:o,task:g,entryId:f,objectName:_});d.push(...k)}}let c=new Map;for(let p of s)c.set(p,[]);for(let p of d){let _=c.get(p.date);_&&_.push(p)}for(let[,p]of c)p.sort((_,f)=>{let v=bt[_.status]??99,g=bt[f.status]??99;if(v!==g)return v-g;if(_.projected!==f.projected)return _.projected?1:-1;let k=_.object_name.localeCompare(f.object_name);return k!==0?k:_.task_name.localeCompare(f.task_name)});return s.map(p=>({date:p,events:c.get(p)??[]}))}var Vt={completed:"ok",reset:"ok",skipped:"due_soon",missed:"overdue",triggered:"triggered",trigger_replaced:"triggered",trigger_removed:"ok"};function Ft(e,i){let t=[];for(let n=i-1;n>=0;n--){let s=new Date(e);s.setDate(s.getDate()-n),s.setHours(0,0,0,0),t.push(q(s))}return t}function $e(e,i,t,n=null){let s=Ft(i,t),r=s[0],o=s[s.length-1],d=new Map;for(let p of s)d.set(p,[]);for(let p of e){let _=p.object?.name||"",f=p.entry_id,v=p.tasks||[];for(let g of v){if(n&&g.responsible_user_id!==n)continue;let k=g.history||[];for(let b of k){if(typeof b?.timestamp!="string")continue;let D=b.timestamp.slice(0,10);if(D<r||D>o)continue;let O=d.get(D);if(!O)continue;let M=b.type??"completed";O.push({date:D,entry_id:f,task_id:g.id,task_name:g.name,object_name:_,status:Vt[M]??"ok",days_until_due:null,projected:!1,schedule_type:g.schedule_type,interval_days:g.interval_days??null,responsible_user_id:g.responsible_user_id??null,avg_cost:typeof b.cost=="number"?b.cost:null,adaptive_enabled:!!g.adaptive_config?.enabled,prediction_confidence:null,history_timestamp:b.timestamp,history_type:M,history_cost:typeof b.cost=="number"?b.cost:null,history_notes:typeof b.notes=="string"?b.notes:null,history_duration:typeof b.duration=="number"?b.duration:null})}}}let c={completed:0,reset:1,skipped:2,triggered:3,trigger_replaced:4};for(let[,p]of d)p.sort((_,f)=>{let v=c[_.history_type??""]??99,g=c[f.history_type??""]??99;if(v!==g)return v-g;let k=_.object_name.localeCompare(f.object_name);return k!==0?k:_.task_name.localeCompare(f.task_name)});return s.map(p=>({date:p,events:d.get(p)??[]}))}function Wt(e,i){if(i<=0)return 0;let t=typeof e=="number"&&Number.isFinite(e)?Math.trunc(e):0;return t<0?0:t%i}function Ut(e){return!!(e?.phases&&e.phase_sequence&&e.phase_sequence.length>0)}function et(e){if(!e||!Ut(e))return null;let i=e.phase_sequence Line truncated
<div class="weibull-section">
<div class="weibull-title">
<ha-svg-icon aria-hidden="true" path="M3,14L3.5,14.07L8.07,9.5C7.89,8.85 8.06,8.11 8.59,7.59C9.37,6.8 10.63,6.8 11.41,7.59C11.94,8.11 12.11,8.85 11.93,9.5L14.5,12.07L15,12C15.18,12 15.35,12 15.5,12.07L19.07,8.5C19,8.35 19,8.18 19,8A2,2 0 0,1 21,6A2,2 0 0,1 23,8A2,2 0 0,1 21,10C20.82,10 20.65,10 20.5,9.93L16.93,13.5C17,13.65 17,13.82 17,14A2,2 0 0,1 15,16A2,2 0 0,1 13,14L13.07,13.5L10.5,10.93C10.18,11 9.82,11 9.5,10.93L4.93,15.5L5,16A2,2 0 0,1 3,18A2,2 0 0,1 1,16A2,2 0 0,1 3,14Z"></ha-svg-icon>
${a("weibull_reliability_curve",i)}
${Bt(n,i)}
</div>
${Kt(n,s,r,o,i)}
${Gt(t,i)}
${t?.confidence_interval_low!=null?Yt(t,e,i):u}
</div>
`}function Bt(e,i){let t,n,s;return e<.8?(t="early_failures",n="M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",s="beta_early_failures"):e<=1.2?(t="random_failures",n="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,17H11V15H13V17M13,13H11V7H13V13Z",s="beta_random_failures"):e<=3.5?(t="wear_out",n="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12H12V6Z",s="beta_wear_out"):(t="highly_predictable",n="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z",s="beta_highly_predictable"),l`
<span class="beta-badge ${t}">
<ha-svg-icon path="${n}"></ha-svg-icon>
${a(s,i)} (\u03B2=${x(e,i,2)})
</span>
`}function Kt(e,i,t,n,s){let g=Math.max(t,n,i,1)*1.3,k=50,b=[];for(let A=0;A<=k;A++){let I=A/k*g,Ht=1-Math.exp(-Math.pow(I/i,e)),jt=32+I/g*260,Rt=136-Ht*128;b.push([jt,Rt])}let D=b.map(([A,I])=>`${y(A)},${y(I)}`).join(" "),O="M32,136 "+b.map(([A,I])=>`L${y(A)},${y(I)}`).join(" ")+` L${y(b[k][0])},136 Z`,M=32+t/g*260,F=1-Math.exp(-Math.pow(t/i,e)),W=136-F*128,St=x((1-F)*100,s,0),nt=32+n/g*260,Ct=[0,.25,.5,.75,1];return l`
<div class="weibull-chart">
<svg viewBox="0 0 ${300} ${160}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${a("chart_weibull",s)}">
${Ct.map(A=>{let I=136-A*128;return z`
<line x1="${32}" y1="${y(I)}" x2="${292}" y2="${y(I)}"
stroke="var(--divider-color)" stroke-width="0.5" stroke-dasharray="${A===.5?"4,3":u}" />
<text x="${28}" y="${y(I+3)}" fill="var(--secondary-text-color)"
font-size="8" text-anchor="end">${x(A*100,s,0)}%</text>
`})}
<text x="${32}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">0</text>
<text x="${324/2}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(g/2)}</text>
<text x="${292}" y="${156}" fill="var(--secondary-text-color)" font-size="8" text-anchor="middle">${Math.round(g)}</text>
<path d="${O}" fill="var(--primary-color, #03a9f4)" opacity="0.08" />
<polyline points="${D}" fill="none"
stroke="var(--primary-color, #03a9f4)" stroke-width="2" />
${t>0?z`
<line x1="${y(M)}" y1="${8}" x2="${y(M)}" y2="${y(136)}"
stroke="var(--primary-color, #03a9f4)" stroke-width="1.5" stroke-dasharray="4,3" />
<circle cx="${y(M)}" cy="${y(W)}" r="3"
fill="var(--primary-color, #03a9f4)" />
<text x="${y(M+4)}" y="${y(W-6)}" fill="var(--primary-color, #03a9f4)"
font-size="9" font-weight="600">R=${St}%</text>
`:u}
${n>0&&n!==t?z`
<line x1="${y(nt)}" y1="${8}" x2="${y(nt)}" y2="${y(136)}"
stroke="var(--success-color, #4caf50)" stroke-width="1.5" stroke-dasharray="4,3" />
`:u}
<line x1="${32}" y1="${8}" x2="${32}" y2="${136}"
stroke="var(--secondary-text-color)" stroke-width="1" />
<line x1="${32}" y1="${136}" x2="${292}" y2="${136}"
stroke="var(--secondary-text-color)" stroke-width="1" />
</svg>
</div>
<div class="chart-legend">
<span class="legend-item"><span class="legend-swatch" style="background:var(--primary-color, #03a9f4)"></span> ${a("weibull_failure_probability",s)}</span>
${t>0?l`<span class="legend-item"><span class="legend-swatch" style="background:var(--primary-color, #03a9f4); opacity:0.5"></span> ${a("current_interval_marker",s)}</span>`:u}
${n>0&&n!==t?l`<span class="legend-item"><span class="legend-swatch" style="background:var(--success-color, #4caf50)"></span> ${a("recommended_marker",s)}</span>`:u}
</div>
`}function Gt(e,i){return l`
<div class="weibull-info-row">
<div class="weibull-info-item">
<span>${a("characteristic_life",i)}</span>
<span class="weibull-info-value">${Math.round(e.weibull_eta)} ${a("days",i)}</span>
</div>
${e.weibull_r_squared!=null?l`
<div class="weibull-info-item">
<span>${a("weibull_r_squared",i)}</span>
<span class="weibull-info-value">${x(e.weibull_r_squared,i,3)}</span>
</div>
`:u}
</div>
`}function Yt(e,i,t){let n=e.confidence_interval_low,s=e.confidence_interval_high,r=i.suggested_interval??i.interval_days??0,o=i.interval_days??0,d=Math.max(0,n-5),p=s+5-d,_=(n-d)/p*100,f=(s-n)/p*100,v=(r-d)/p*100,g=o>0?(o-d)/p*100:-1;return l`
<div class="confidence-range">
<div class="confidence-range-title">
${a("confidence_interval",t)}: ${r} ${a("days",t)} (${n}\u2013${s})
</div>
<div class="confidence-bar">
<div class="confidence-fill" style="left:${y(_)}%;width:${y(f)}%"></div>
${g>=0?l`<div class="confidence-marker current" style="left:${y(g)}%"></div>`:u}
<div class="confidence-marker recommended" style="left:${y(v)}%"></div>
</div>
<div class="confidence-labels">
<span class="confidence-text low">${a("confidence_conservative",t)} (${n}${a("days",t).charAt(0)})</span>
<span class="confidence-text high">${a("confidence_aggressive",t)} (${s}${a("days",t).charAt(0)})</span>
</div>
</div>
`}function wt(e,i,t){let n=e.degradation_trend!=null&&e.degradation_trend!=="insufficient_data",s=e.days_until_threshold!=null,r=e.environmental_factor!=null&&e.environmental_factor!==1;if(!n&&!s&&!r)return u;let o=e.degradation_trend==="rising"?"M16,6L18.29,8.29L13.41,13.17L9.41,9.17L2,16.59L3.41,18L9.41,12L13.41,16L19.71,9.71L22,12V6H16Z":e.degradation_trend==="falling"?"M16,18L18.29,15.71L13.41,10.83L9.41,14.83L2,7.41L3.41,6L9.41,12L13.41,8L19.71,14.29L22,12V18H16Z":"M22,12L18,8V11H3V13H18V16L22,12Z";return l`
<div class="prediction-section">
${e.sensor_prediction_urgency?l`
<div class="prediction-urgency-banner">
<ha-svg-icon path="M1,21H23L12,2L1,21M12,18A1,1 0 0,1 11,17A1,1 0 0,1 12,16A1,1 0 0,1 13,17A1,1 0 0,1 12,18M13,15H11V10H13V15Z"></ha-svg-icon>
${a("sensor_prediction_urgency",i).replace("{days}",String(Math.round(e.days_until_threshold||0)))}
</div>
`:u}
<div class="prediction-title">
<ha-svg-icon path="M2,2V4H7V2H2M22,2V4H13V2H22M7,7V9H2V7H7M22,7V9H13V7H22M7,12V14H2V12H7M22,12V14H13V12H22M7,17V19H2V17H7M22,17V19H13V17H22M9,2V19L12,22L15,19V2H9M11,4H13V17.17L12,18.17L11,17.17V4Z"></ha-svg-icon>
${a("sensor_prediction",i)}
</div>
<div class="prediction-grid">
${n?l`
<div class="prediction-item">
<ha-svg-icon path="${o}"></ha-svg-icon>
<span class="prediction-label">${a("degradation_trend",i)}</span>
<span class="prediction-value ${e.degradation_trend}">${a("trend_"+e.degradation_trend,i)}</span>
${e.degradation_rate!=null?l`<span class="prediction-rate">${e.degradation_rate>0?"+":""}${x(e.degradation_rate,i,Math.abs(e.degradation_rate)>=10?0:1)} ${e.trigger_entity_info?.unit_of_measurement||""}/${a("day_short",i)}</span>`:u}
</div>
`:u}
${s?l`
<div class="prediction-item">
<ha-svg-icon path="M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22A9,9 0 0,0 21,13A9,9 0 0,0 12,4M12.5,8H11V14L15.75,16.85L16.5,15.62L12.5,13.25V8M7.88,3.39L6.6,1.86L2,5.71L3.29,7.24L7.88,3.39M22,5.72L17.4,1.86L16.11,3.39L20.71,7.25L22,5.72Z"></ha-svg-icon>
<span class="prediction-label">${a("days_until_threshold",i)}</span>
<span class="prediction-value prediction-days${e.days_until_threshold===0?" exceeded":e.sensor_prediction_urgency?" urgent":""}">${e.days_until_threshold===0?a("threshold_exceeded",i):"~"+Math.round(e.days_until_threshold)+" "+a("days",i)}</span>
${e.threshold_prediction_date?l`<span class="prediction-date">${N(e.threshold_prediction_date,i)}</span>`:u}
${e.threshold_prediction_confidence?l`<span class="confidence-dot ${e.threshold_prediction_confidence}"></span>`:u}
${(e.prediction_cycles??0)>0?l`<span class="prediction-cycles">${a("prediction_cycles",i)}: ${e.prediction_cycles}</span>`:u}
</div>
`:u}
${r&&t.environmental?l`
<div class="prediction-item">
<ha-svg-icon path="M15,13V5A3,3 0 0,0 12,2A3,3 0 0,0 9,5V13A5,5 0 0,0 7,17A5,5 0 0,0 12,22A5,5 0 0,0 17,17A5,5 0 0,0 15,13M12,4A1,1 0 0,1 13,5V8H11V5A1,1 0 0,1 12,4Z"></ha-svg-icon>
<span class="prediction-label">${a("environmental_adjustment",i)}</span>
<span class="prediction-value">${x(e.environmental_factor,i,2)}x</span>
${e.environmental_entity?l`<span class="prediction-entity entity-link" @click=${d=>ct(d,e.environmental_entity)}>${e.environmental_entity}</span>`:u}
</div>
`:u}
</div>
</div>
`}function At(e,i,t,n){let s=Math.max(e||1,i);return l`
<div class="interval-comparison">
<div class="interval-bar">
<div class="interval-label">
${a("current",n)}: ${e??"\u2014"} ${e!=null?a("days",n):""}
</div>
<div class="interval-visual current"
style="width: ${e!=null?Math.min(e/s*100,100):0}%"></div>
</div>
<div class="interval-bar">
<div class="interval-label">
${a("recommended",n)}: ${i} ${a("days",n)}
<span class="confidence-badge ${t}">${a(`confidence_${t}`,n)}</span>
</div>
<div class="interval-visual suggested"
style="width: ${Math.min(i/s*100,100)}%"></div>
</div>
</div>
`}var It=["month_jan","month_feb","month_mar","month_apr","month_may","month_jun","month_jul","month_aug","month_sep","month_oct","month_nov","month_dec"];function Tt(e,i,t){if(!t.seasonal||!e.seasonal_factor||e.seasonal_factor===1)return u;let n=It.map(d=>a(d,i)),s=new Date().getMonth(),r=e.seasonal_factors||e.interval_analysis?.seasonal_factors||null,o=r&&r.length===12?r:n.map((d,c)=>{let p=e.seasonal_factor||1,_=Math.sin((c-6)*Math.PI/6)*.3;return Math.max(.7,Math.min(1.3,p+_))});return l`
<div class="seasonal-card-compact">
<h4>${a("seasonal_awareness",i)}</h4>
<div class="seasonal-mini-chart">
${o.map((d,c)=>{let p=d*40,_=d<.9?"low":d>1.1?"high":"normal";return l`
<div class="seasonal-bar ${_} ${c===s?"current":""}"
style="height: ${p}px"
title="${n[c]}: ${x(d,i,2)}x">
</div>
`})}
</div>
<div class="seasonal-legend">
<span class="legend-item"><span class="dot low"></span> ${a("shorter",i)||"K\xFCrzer"}</span>
<span class="legend-item"><span class="dot normal"></span> ${a("normal",i)||"Normal"}</span>
<span class="legend-item"><span class="dot high"></span> ${a("longer",i)||"L\xE4nger"}</span>
</div>
</div>
`}function Lt(e,i){return Zt(e,i)}function Zt(e,i){let t=e.seasonal_factors??e.interval_analysis?.seasonal_factors;if(!t||t.length!==12)return u;let n=e.interval_analysis?.seasonal_reason,s=new Date().getMonth(),r=300,o=100,d=8,p=o-d-4,_=Math.max(...t,1.5),f=r/12,v=f*.65,g=d+p-1/_*p;return l`
<div class="seasonal-chart">
<div class="seasonal-chart-title">
<ha-svg-icon aria-hidden="true" path="M17.75 4.09L15.22 6.03L16.13 9.09L13.5 7.28L10.87 9.09L11.78 6.03L9.25 4.09L12.44 4L13.5 1L14.56 4L17.75 4.09M21.25 11L19.61 12.25L20.2 14.23L18.5 13.06L16.8 14.23L17.39 12.25L15.75 11L17.81 10.95L18.5 9L19.19 10.95L21.25 11M18.97 15.95C19.8 15.87 20.69 17.05 20.16 17.8C19.84 18.25 19.5 18.67 19.08 19.07C15.17 23 8.84 23 4.94 19.07C1.03 15.17 1.03 8.83 4.94 4.93C5.34 4.53 5.76 4.17 6.21 3.85C6.96 3.32 8.14 4.21 8.06 5.04C7.79 7.9 8.75 10.87 10.95 13.06C13.14 15.26 16.1 16.22 18.97 15.95Z"></ha-svg-icon>
${a("seasonal_chart_title",i)}
${n?l`<span class="source-tag">${n==="learned"?a("seasonal_learned",i):a("seasonal_manual",i)}</span>`:u}
</div>
<svg viewBox="0 0 ${r} ${o}" preserveAspectRatio="xMidYMid meet" role="img" aria-label="${a("chart_seasonal",i)}">
<line x1="0" y1="${y(g)}" x2="${r}" y2="${y(g)}"
stroke="var(--divider-color)" stroke-width="1" stroke-dasharray="4,3" />
${t.map((k,b)=>{let D=k/_*p,O=b*f+(f-v)/2,M=d+p-D,F=b===s,W=k<1?"var(--success-color, #4caf50)":k>1?"var(--warning-color, #ff9800)":"var(--secondary-text-color)";return z`
<rect x="${y(O)}" y="${y(M)}"
width="${y(v)}" height="${y(D)}"
fill="${W}" opacity="${F?1:.5}" rx="2" />
`})}
</svg>
<div class="seasonal-labels">
${It.map((k,b)=>l`<span class="seasonal-label ${b===s?"active-month":""}">${a(k,i)}</span>`)}
</div>
</div>
`}var $=class extends H{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._taskId=null;this._task=null;this._objectName="";this._busy=!1;this._error="";this._showSkip=!1;this._showReset=!1;this._showDetails=!1;this._showAdaptive=!1;this._skipReason="";this._resetDate="";this._features={adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1};this._toast="";this._featuresLoaded=!1;this._currencySymbol=""}get _lang(){return j(this.hass)}async openFor(t,n){this._entryId=t,this._taskId=n,this._error="",this._showSkip=!1,this._showReset=!1,this._showAdaptive=!1,this._skipReason="",this._resetDate=q(new Date),this._open=!0,await Promise.all([this._loadTask(),this._loadFeatures()])}async _loadFeatures(){if(!this._featuresLoaded)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/settings"});t?.features&&(this._features={...this._features,...t.features}),this._currencySymbol=t?.budget?.currency_symbol||"",this._featuresLoaded=!0}catch{}}close(){this._open=!1,this._task=null,this._error=""}async _loadTask(){if(!(!this._entryId||!this._taskId))try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._objectName=t.object?.name||"";let n=(t.tasks||[]).find(s=>s.id===this._taskId);this._task=n??null}catch(t){this._error=T(t,this._lang)}}async _runWs(t){this._busy=!0,this._error="";try{return await this.hass.connection.sendMessagePromise(t),this._busy=!1,!0}catch(n){return this._error=T(n,this._lang),this._busy=!1,!1}}_notifyChanged(t){this.dispatchEvent(new CustomEvent("task-action-fired",{detail:{entry_id:this._entryId,task_id:this._taskId,action:t},bubbles:!0,composed:!0}))}_onComplete(){!this._entryId||!this._taskId||!this._task||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(async({openCompleteDialog:t})=>{let n=this._task,s=[];try{s=(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects",compact:!0})).objects||[]}catch{}t(xt({entryId:this._entryId,taskId:this._taskId,taskName:n.name,task:n,objects:s,lang:this._lang,checklist:n.checklist||[],adaptiveEnabled:!!n.adaptive_config?.enabled,currencySymbol:this._currencySymbol}))&&(this._notifyChanged("complete"),this.close())})}async _onSkipConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/skip",entry_id:this._entryId,task_id:this._taskId,reason:this._skipReason.trim()||null})&&(this._notifyChanged("skip"),this.close())}async _onResetConfirm(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/reset",entry_id:this._entryId,task_id:this._taskId,date:this._resetDate||void 0})&&(this._notifyChanged("reset"),this.close())}_onEdit(){!this._entryId||!this._taskId||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(({openEditTaskDialog:t})=>{t(this._entryId,this._taskId),this.close()})}_onQr(){!this._entryId||!this._taskId||!this._task||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(({openQrDialog:t})=>{t({entry_id:this._entryId,task_id:this._taskId,task_name:this._task.name,object_name:this._objectName}),this.close()})}async _onDelete(){if(!this._entryId||!this._taskId)return;let t=a("delete_task_confirm",this._lang)||`Delete "${this._task?.name}"?`;if(!window.confirm(t))return;await this._runWs({type:"maintenance_supporter/task/delete",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("delete"),this.close())}async _onArchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/archive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("archive"),this.close())}async _onUnarchive(){if(!this._entryId||!this._taskId)return;await this._runWs({type:"maintenance_supporter/task/unarchive",entry_id:this._entryId,task_id:this._taskId})&&(this._notifyChanged("unarchive"),this.close())}_onOpenInPanel(){if(!this._entryId||!this._taskId)return;let t=`/maintenance-supporter?entry_id=${encodeURIComponent(this._entryId)}&task_id=${encodeURIComponent(this._taskId)}`;history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed")),this.close()}async _applySuggestion(){if(!this._entryId||!this._taskId||!this._task?.suggested_interval)return;await this._runWs({type:"maintenance_supporter/task/apply_suggestion",entry_id:this._entryId,task_id:this._taskId,interval:this._task.suggested_interval})&&(this._toast=a("suggestion_applied",this._lang)||"Applied",this._notifyChanged("apply_suggestion"),await this._loadTask(),setTimeout(()=>{this._toast=""},2500))}async _reanalyzeInterval(){if(!(!this._entryId||!this._taskId)){this._busy=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/analyze_interval",entry_id:this._entryId, Line truncated
<div class="recommendation-card">
<h4>${a("suggested_interval",n)}</h4>
${At(t.interval_days,t.suggested_interval,t.interval_confidence||"medium",n)}
<div class="recommendation-actions">
<button class="btn primary"
@click=${this._applySuggestion} ?disabled=${this._busy}>
<ha-icon icon="mdi:check"></ha-icon>
${a("apply_suggestion",n)}
</button>
<button class="btn"
@click=${this._reanalyzeInterval} ?disabled=${this._busy}>
<ha-icon icon="mdi:refresh"></ha-icon>
${a("reanalyze",n)}
</button>
</div>
</div>
`}_renderAdaptive(t){let n=this._lang,s=this._features.adaptive&&t.suggested_interval&&t.suggested_interval!==t.interval_days,r=t.degradation_trend!=null&&t.degradation_trend!=="insufficient_data"||t.days_until_threshold!=null||t.environmental_factor!=null&&t.environmental_factor!==1,o=this._features.adaptive&&t.interval_analysis?.weibull_beta!=null&&t.interval_analysis?.weibull_eta!=null,d=this._features.seasonal&&t.seasonal_factor&&t.seasonal_factor!==1;return!s&&!r&&!o&&!d?l`<div class="adaptive-empty">
${a("adaptive_no_data",n)||"Not enough completion history yet for adaptive analysis."}
</div>`:l`
<div class="adaptive-stack">
${this._toast?l`<div class="toast">${this._toast}</div>`:u}
${s?this._renderRecommendation(t):u}
${r?wt(t,n,this._features):u}
${o?kt(t,n):u}
${d?l`
${Tt(t,n,this._features)}
${t.seasonal_factors?.length===12||t.interval_analysis?.seasonal_factors?.length===12?Lt(t,n):u}
`:u}
</div>
`}_renderHistoryReadings(t,n,s){let r=J(t),o=d=>x(d,s,{maximumFractionDigits:3});if(r.length>0)return l`<div class="history-readings">
${r.map(d=>{let c=ft(n,t,d.id);return l`<span class="history-reading"><span class="history-reading-name">${d.name}</span>
<span class="history-reading-value">${o(d.value)}${d.unit?` ${d.unit}`:""}${c==null?"":` (${c>=0?"+":""}${o(c)})`}</span></span>`})}
</div>`;if(t.reading_value!=null){let d=this._task?.reading_unit?` ${this._task.reading_unit}`:"";return l`<div class="history-readings"><span class="history-reading">
<span class="history-reading-name">${a("reading_label",s)}</span>
<span class="history-reading-value">${o(t.reading_value)}${d}</span></span></div>`}return u}_renderDetails(t){let n=this._lang,s=t.history||[],r=s.filter(c=>c.type==="completed"),o=r.reduce((c,p)=>c+(typeof p.cost=="number"?p.cost:0),0),d=(()=>{let c=r.map(p=>typeof p.duration=="number"?p.duration:null).filter(p=>p!=null);return c.length?Math.round(c.reduce((p,_)=>p+_,0)/c.length):null})();return l`
<div class="details">
<div class="stats-grid">
<div class="stat">
<span class="stat-label">${a("times_performed",n)||"Performed"}</span>
<span class="stat-value">${r.length}</span>
</div>
<div class="stat">
<span class="stat-label">${a("total_cost",n)||"Total cost"}</span>
<span class="stat-value">${x(o,n,2)}</span>
</div>
<div class="stat">
<span class="stat-label">${a("avg_duration",n)||"Avg duration"}</span>
<span class="stat-value">${d!=null?`${d}m`:"\u2014"}</span>
</div>
</div>
<div class="history-header">
<strong>${a("history",n)||"History"}</strong>
<span class="history-count">${s.length}</span>
</div>
${s.length===0?l`<div class="history-empty">${a("history_empty",n)||"No history yet."}</div>`:l`
<div class="history-list">
${[...s].reverse().slice(0,20).map(c=>{let p=["completed","reset","skipped"].includes(c.type);return l`
<div class="history-entry">
<div class="history-line">
<span class="history-type type-${c.type}">${a(c.type,n)}</span>
<span class="history-date">${ot(c.timestamp,n)}</span>
${p?l`<button class="history-edit"
title="${a("history_edit_button",n)||"Edit"}"
@click=${()=>this._onEditHistoryEntry(c)}>
<ha-icon icon="mdi:pencil"></ha-icon>
</button>`:u}
</div>
${c.notes?l`<div class="history-notes">${c.notes}</div>`:u}
${this._renderHistoryReadings(c,s,n)}
${(()=>{let _=Q(c);return _.length?l`<div class="history-photos">
${_.map(f=>l`<maintenance-history-photo .hass=${this.hass} .docId=${f}></maintenance-history-photo>`)}
</div>`:u})()}
${c.cost!=null||c.duration!=null?l`<div class="history-meta">
${c.cost!=null?l`<span>💰 ${x(c.cost,n,2)}</span>`:u}
${c.duration!=null?l`<span>⏱️ ${c.duration}m</span>`:u}
</div>`:u}
</div>
`})}
${s.length>20?l`<div class="history-more">… +${s.length-20} ${a("older_entries",n)||"older"}</div>`:u}
</div>
`}
</div>
`}render(){if(!this._open)return u;let t=this._lang,n=this._task,s=this.hass?.user?.is_admin??!0;return l`
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
${n?l`
<div class="header">
<div class="title">
<span class="status-dot" style="background: ${U[n.status]||"#ccc"}"></span>
<span class="task-name">${n.name}</span>
</div>
<div class="object">
<button class="link-inline" @click=${()=>{this._entryId&&import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(({openObjectQuickActions:r})=>{r(this._entryId),this.close()})}}>${this._objectName}</button>
</div>
<div class="quick-info">
${n.next_due?l`<span><strong>${a("next_due",t)||"Next due"}:</strong> ${N(n.next_due,t)}</span>`:u}
${n.last_performed?l`<span><strong>${a("last_performed",t)||"Last"}:</strong> ${N(n.last_performed,t)}</span>`:u}
${n.schedule?.kind&&!["manual","one_time"].includes(n.schedule.kind)||n.interval_days!=null?l`<span><strong>${a("interval",t)||"Interval"}:</strong> ${dt(n,t)}</span>`:u}
${V(n)?l`<span><strong>${a("phase_current",t)}:</strong> ${V(n)}</span>`:u}
</div>
</div>
${this._error?l`<div class="error">${this._error}</div>`:u}
${this._showSkip?l`
<div class="inline-form">
<label>${a("skip_reason",t)||"Skip reason (optional)"}</label>
<input type="text" .value=${this._skipReason}
@input=${r=>{this._skipReason=r.target.value}} />
<div class="inline-actions">
<button class="btn cancel" @click=${()=>{this._showSkip=!1}} ?disabled=${this._busy}>
${a("cancel",t)||"Cancel"}
</button>
<button class="btn primary" @click=${this._onSkipConfirm} ?disabled=${this._busy}>
${a("skip",t)||"Skip"}
</button>
</div>
</div>
`:this._showReset?l`
<div class="inline-form">
<label>${a("reset_to_date",t)||"Reset last_performed to"}</label>
<ms-date-field
kind="date"
.hass=${this.hass}
.lang=${t}
.value=${this._resetDate}
@value-changed=${r=>{this._resetDate=r.detail.value}}
></ms-date-field>
<div class="inline-actions">
<button class="btn cancel" @click=${()=>{this._showReset=!1}} ?disabled=${this._busy}>
${a("cancel",t)||"Cancel"}
</button>
<button class="btn primary" @click=${this._onResetConfirm} ?disabled=${this._busy}>
${a("reset",t)||"Reset"}
</button>
</div>
</div>
`:l`
<div class="actions primary-row">
<ha-button appearance="accent" variant="success" @click=${this._onComplete} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:check"></ha-icon>
${a("complete",t)||"Complete"}
</ha-button>
${n.allow_skip!==!1?l`
<ha-button appearance="outlined" variant="warning" @click=${()=>{this._showSkip=!0}} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:skip-next"></ha-icon>
${a("skip",t)||"Skip"}
</ha-button>
`:u}
<ha-button appearance="outlined" variant="neutral" @click=${()=>{this._showReset=!0}} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:restart"></ha-icon>
${a("reset",t)||"Reset"}
</ha-button>
</div>
${s?l`
<div class="actions secondary-row">
<ha-button size="small" appearance="outlined" variant="neutral" @click=${this._onEdit} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:pencil"></ha-icon>
${a("edit",t)||"Edit"}
</ha-button>
<ha-button size="small" appearance="outlined" variant="neutral" @click=${this._onQr} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:qrcode"></ha-icon>
${a("qr_code",t)||"QR"}
</ha-button>
<ha-button size="small" appearance="outlined" variant="neutral"
@click=${n.archived?this._onUnarchive:this._onArchive}
.disabled=${this._busy}>
<ha-icon slot="start" icon="${n.archived?"mdi:archive-arrow-up-outline":"mdi:archive-outline"}"></ha-icon>
${n.archived?a("unarchive",t)||"Unarchive":a("archive",t)||"Archive"}
</ha-button>
<ha-button size="small" appearance="outlined" variant="danger" class="danger" @click=${this._onDelete} .disabled=${this._busy}>
<ha-icon slot="start" icon="mdi:delete"></ha-icon>
${a("delete",t)||"Delete"}
</ha-button>
</div>
`:u}
<div class="details-toggle">
<button class="link" @click=${()=>{this._showDetails=!this._showDetails}}>
<ha-icon icon="${this._showDetails?"mdi:chevron-up":"mdi:chevron-down"}"></ha-icon>
${this._showDetails?a("hide_details",t)||"Hide details":a("show_details",t)||"Show history + stats"}
</button>
${this._features.adaptive||this._features.seasonal||this._features.environmental?l`<button class="link" @click=${()=>{this._showAdaptive=!this._showAdaptive}}>
<ha-icon icon="${this._showAdaptive?"mdi:chart-line":"mdi:chart-line-variant"}"></ha-icon>
${this._showAdaptive?a("hide_stats",t)||"Hide stats":a("show_stats",t)||"Show stats + graphs"}
</button>`:u}
</div>
${this._showDetails?this._renderDetails(n):u}
${this._showAdaptive?this._renderAdaptive(n):u}
<div class="footer">
<button class="link" @click=${this._onOpenInPanel}>
<ha-icon icon="mdi:open-in-new"></ha-icon>
${a("open_in_panel",t)||"Open in Maintenance panel"}
</button>
</div>
`}
`:l`<div class="loading">${a("loading",t)||"Loading\u2026"}</div>`}
</div>
`}};$.styles=[pt,C`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0; z-index: 100;
background: rgba(0,0,0,0.5);
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: 460px;
max-height: 92vh; overflow: auto;
background: var(--card-background-color, var(--ha-card-background, #1c1c1c));
color: var(--primary-text-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
padding: 20px;
display: flex; flex-direction: column; gap: 14px;
z-index: 101;
}
.header { display: flex; flex-direction: column; gap: 6px; }
.title { display: flex; align-items: center; gap: 10px; }
.status-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.task-name { font-size: 18px; font-weight: 600; }
.object { font-size: 13px; color: var(--secondary-text-color); }
.link-inline {
background: transparent; border: none; padding: 0; cursor: pointer;
color: var(--primary-color); font-size: inherit; font-family: inherit;
}
.link-inline:hover { text-decoration: underline; }
.quick-info {
display: flex; flex-wrap: wrap; gap: 12px;
font-size: 12px; color: var(--secondary-text-color);
padding-top: 4px; border-top: 1px solid var(--divider-color);
}
.quick-info strong { color: var(--primary-text-color); font-weight: 500; }
.actions { display: flex; gap: 8px; }
.actions.primary-row { gap: 6px; }
.actions.primary-row .btn { flex: 1; }
.actions.primary-row ha-button { flex: 1; }
/* Edit + QR are admin-tools — left-align as a group; Delete is destructive
so it gets pushed to the far right with margin-left:auto for visual
separation. Earlier this row was flex-end which left a strange empty
gap on the left (user feedback). */
.actions.secondary-row {
padding-top: 8px; border-top: 1px solid var(--divider-color);
justify-content: flex-start;
}
.actions.secondary-row .btn.danger,
.actions.secondary-row ha-button.danger {
margin-left: auto;
}
.actions.secondary-row ha-button { --ha-button-font-size: 13px; }
.btn {
padding: 8px 12px; font-size: 14px;
border-radius: 6px; cursor: pointer;
border: 1px solid var(--divider-color);
background: var(--secondary-background-color, transparent);
color: var(--primary-text-color);
font-weight: 500;
display: inline-flex; align-items: center; gap: 6px;
transition: background 0.12s;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: wait; }
.btn.primary {
background: var(--primary-color);
color: var(--text-primary-color, white);
border-color: var(--primary-color);
}
.btn.cancel { background: transparent; }
.btn.ghost { padding: 6px 10px; font-size: 13px; }
.btn.danger { color: var(--error-color); }
.btn ha-icon { --mdc-icon-size: 18px; }
.inline-form { display: flex; flex-direction: column; gap: 8px; }
.inline-form label { font-size: 13px; color: var(--secondary-text-color); }
.inline-form input {
padding: 8px; font-size: 14px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color, #444);
border-radius: 6px;
}
.inline-actions { display: flex; gap: 8px; justify-content: flex-end; }
.footer { display: flex; justify-content: center; padding-top: 4px; }
.link {
background: transparent; border: none; cursor: pointer;
color: var(--primary-color); font-size: 13px;
display: inline-flex; align-items: center; gap: 4px;
}
.link:hover { text-decoration: underline; }
.link ha-icon { --mdc-icon-size: 14px; }
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); }
.error {
padding: 8px; border-radius: 6px;
background: rgba(211,47,47,0.1);
color: var(--error-color, #d32f2f); font-size: 13px;
}
/* Details (expandable Show details section) */
.details-toggle { display: flex; justify-content: center; margin-top: 4px; }
.details {
display: flex; flex-direction: column; gap: 12px;
border-top: 1px solid var(--divider-color);
padding-top: 12px;
}
.stats-grid {
display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px;
}
.stat {
display: flex; flex-direction: column; gap: 2px;
background: var(--secondary-background-color, rgba(255,255,255,0.04));
padding: 8px; border-radius: 6px;
align-items: center;
}
.stat-label { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; letter-spacing: 0.5px; }
.stat-value { font-size: 16px; font-weight: 600; }
.history-header {
display: flex; align-items: baseline; gap: 8px;
font-size: 14px;
}
.history-count {
font-size: 12px; color: var(--secondary-text-color);
background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px;
}
.history-empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; }
.history-list { display: flex; flex-direction: column; gap: 8px; max-height: 280px; overflow: auto; }
.history-entry {
padding: 6px 8px; border-radius: 6px;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
font-size: 13px;
}
.history-line {
display: flex; align-items: center; gap: 8px;
justify-content: space-between;
}
.history-type {
font-weight: 600; font-size: 11px;
padding: 2px 6px; border-radius: 4px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.type-completed { background: rgba(46,125,50,0.2); color: #66bb6a; }
.type-skipped { background: rgba(158,158,158,0.2); color: var(--secondary-text-color); }
.type-reset { background: rgba(33,150,243,0.2); color: #64b5f6; }
.type-triggered { background: rgba(255,87,34,0.2); color: #ff8a65; }
.history-date { font-size: 11px; color: var(--secondary-text-color); flex: 1; text-align: right; }
.history-edit {
background: transparent; border: none; cursor: pointer;
padding: 4px; border-radius: 4px;
color: var(--secondary-text-color);
}
.history-edit:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); color: var(--primary-color); }
.history-edit ha-icon { --mdc-icon-size: 14px; }
.history-notes { margin-top: 4px; color: var(--primary-text-color); }
.history-meta { display: flex; gap: 12px; margin-top: 4px; color: var(--secondary-text-color); font-size: 11px; }
/* #161: readings + photos on the entry (panel-timeline parity) */
.history-readings { display: flex; flex-wrap: wrap; gap: 2px 16px; margin-top: 4px; font-size: 12px; }
.history-reading { display: inline-flex; gap: 6px; }
.history-reading-name { color: var(--secondary-text-color); }
.history-reading-value { font-variant-numeric: tabular-nums; }
.history-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 4px; }
.history-more { padding: 8px; text-align: center; font-size: 12px; color: var(--secondary-text-color); font-style: italic; }
/* Adaptive section — wraps the panel renderers (which assume sharedStyles
are present) and adds dialog-specific layout. */
.adaptive-stack {
display: flex; flex-direction: column; gap: 12px;
border-top: 1px solid var(--divider-color);
padding-top: 12px;
}
.adaptive-empty {
padding: 16px; text-align: center;
color: var(--secondary-text-color);
font-style: italic; font-size: 13px;
border-top: 1px solid var(--divider-color);
}
.toast {
padding: 8px 12px; border-radius: 6px;
background: rgba(76, 175, 80, 0.15);
color: #4caf50; font-size: 13px; font-weight: 500;
}
/* The panel's recommendation-card uses ha-button. We use plain <button>
in this dialog's button styles. Re-style the action row to match. */
.recommendation-actions {
display: flex; gap: 8px; margin-top: 8px;
}
/* Constrain SVG charts so they fit the dialog width even on mobile. */
.weibull-section, .seasonal-card-compact { max-width: 100%; }
.weibull-chart svg { max-width: 100%; height: auto; }
.details-toggle { gap: 12px; flex-wrap: wrap; }
`],h([P({attribute:!1})],$.prototype,"hass",2),h([m()],$.prototype,"_open",2),h([m()],$.prototype,"_entryId",2),h([m()],$.prototype,"_taskId",2),h([m()],$.prototype,"_task",2),h([m()],$.prototype,"_objectName",2),h([m()],$.prototype,"_busy",2),h([m()],$.prototype,"_error",2),h([m()],$.prototype,"_showSkip",2),h([m()],$.prototype,"_showReset",2),h([m()],$.prototype,"_showDetails",2),h([m()],$.prototype,"_showAdaptive",2),h([m()],$.prototype,"_skipReason",2),h([m()],$.prototype,"_resetDate",2),h([m()],$.prototype,"_features",2),h([m()],$.prototype,"_toast",2);customElements.get("maintenance-task-quick-actions-dialog")||customElements.define("maintenance-task-quick-actions-dialog",$);function Dt(e){return!!e&&/^https?:\/\//i.test(e)}function Mt(e){return e?customElements.get("ha-markdown")?l`<ha-markdown class="notes-md" .content=${e} breaks></ha-markdown>`:l`${e}`:u}var L=class extends H{constructor(){super(...arguments);this._open=!1;this._entryId=null;this._data=null;this._busy=!1;this._error=""}get _lang(){return j(this.hass)}async openFor(t){this._entryId=t,this._error="",this._open=!0,await this._load()}close(){this._open=!1,this._data=null,this._error=""}async _load(){if(this._entryId)try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:this._entryId});this._data=t}catch(t){this._error=T(t,this._lang)}}_onEditObject(){!this._entryId||!this._data||import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(({openEditObjectDialog:t})=>{t(this._entryId,this._data.object),this.close()})}_onAddTask(){this._entryId&&import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(({openCreateTaskDialog:t})=>{t(this._entryId),this.close()})}async _onDelete(){if(!this._entryId||!this._data)return;let t=a("delete_object_confirm",this._lang)||`Delete "${this._data.object.name}" and all its tasks?`;if(window.confirm(t)){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/delete",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-deleted",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(n){this._error=T(n,this._lang)}finally{this._busy=!1}}}async _onArchiveObject(){if(!this._entryId||!this._data)return;let t=!!this._data.object.archived;if(!t){let n=a("confirm_archive_object",this._lang)||"Archive this object and its tasks?";if(!window.confirm(n))return}this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:t?"maintenance_supporter/object/unarchive":"maintenance_supporter/object/archive",entry_id:this._entryId}),this.dispatchEvent(new CustomEvent("object-changed",{detail:{entry_id:this._entryId},bubbles:!0,composed:!0})),this.close()}catch(n){this._error=T(n,this._lang)}finally{this._busy=!1}}_onTaskClick(t){this._entryId&&import("/maintenance_supporter_panelfiles/panel-chunks/dialog-mount-ZZSGDAOS.js").then(({openTaskQuickActions:n})=>{n(this._entryId,t)})}render(){if(!this._open)return u;let t=this._lang,n=this._data,s=n?.object,r=n?.tasks||[],o=this.hass?.user?.is_admin??!0;return l`
<div class="backdrop" @click=${this.close}></div>
<div class="dialog" role="dialog" aria-modal="true">
${n&&s?l`
<div class="header">
<div class="title">${s.name}</div>
${this._renderMetaRow(s)}
</div>
${this._error?l`<div class="error">${this._error}</div>`:u}
<div class="tasks-section">
<div class="section-header">
<strong>${a("tasks",t)||"Tasks"}</strong>
<span class="count">${r.length}</span>
</div>
${r.length===0?l`<div class="empty">${a("no_tasks",t)||"No tasks yet."}</div>`:l`
<div class="task-list">
${r.map(d=>l`
<div class="task-row" @click=${()=>this._onTaskClick(d.id)}>
<span class="status-dot" style="background: ${U[d.status]||"#ccc"}"></span>
<span class="task-name">${d.name}</span>
<span class="task-status">${a(d.status||"ok",t)}</span>
</div>
`)}
</div>
`}
</div>
${s.notes?l`
<div class="notes-section">
<strong>${a("object_notes_label",t)}</strong>
<div class="notes-body">${Mt(s.notes)}</div>
</div>
`:u}
${o?l`
<div class="actions">
<button class="btn primary" @click=${this._onAddTask} ?disabled=${this._busy}>
<ha-icon icon="mdi:plus"></ha-icon>
${a("add_task",t)||"Add task"}
</button>
<button class="btn" @click=${this._onEditObject} ?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
${a("edit",t)||"Edit"}
</button>
<button class="btn" @click=${this._onArchiveObject} ?disabled=${this._busy}>
<ha-icon icon="${s.archived?"mdi:archive-arrow-up-outline":"mdi:archive-outline"}"></ha-icon>
${s.archived?a("unarchive_object",t)||"Unarchive object":a("archive_object",t)||"Archive object"}
</button>
<button class="btn danger" @click=${this._onDelete} ?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
${a("delete",t)||"Delete"}
</button>
</div>
`:u}
`:l`<div class="loading">${a("loading",t)||"Loading\u2026"}</div>`}
</div>
`}_renderMetaRow(t){let n=this._lang,s=[];return t.area_id&&s.push([a("area",n),t.area_id]),t.manufacturer&&s.push([a("manufacturer",n),t.manufacturer]),t.model&&s.push([a("model",n),t.model]),t.serial_number&&s.push([a("serial_number_label",n),t.serial_number]),t.installation_date&&s.push([a("installed",n),t.installation_date]),t.warranty_expiry&&s.push([a("warranty",n),t.warranty_expiry]),t.documentation_url&&s.push([a("documentation_url_label",n),t.documentation_url]),s.length===0?u:l`
<div class="meta">
${s.map(([r,o])=>l`
<div class="meta-item">
<span class="meta-label">${r}</span>
<span class="meta-value">${Dt(o)?l`<a href="${o}" target="_blank" rel="noopener noreferrer">${o}</a>`:o}</span>
</div>
`)}
</div>
`}};L.styles=C`
:host { display: contents; }
.backdrop {
position: fixed; inset: 0; z-index: 100; background: rgba(0,0,0,0.5);
}
.dialog {
position: fixed; left: 50%; top: 50%;
transform: translate(-50%, -50%);
width: 95vw; max-width: 480px;
max-height: 92vh; overflow: auto;
background: var(--card-background-color, var(--ha-card-background, #1c1c1c));
color: var(--primary-text-color);
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
padding: 20px; z-index: 101;
display: flex; flex-direction: column; gap: 14px;
}
.header { display: flex; flex-direction: column; gap: 6px; }
.title { font-size: 20px; font-weight: 600; }
.meta { display: flex; flex-direction: column; gap: 4px; padding-top: 4px; border-top: 1px solid var(--divider-color); }
.meta-item { display: flex; gap: 8px; font-size: 12px; }
.meta-label { color: var(--secondary-text-color); min-width: 100px; }
.meta-value { color: var(--primary-text-color); flex: 1; word-break: break-word; }
.meta-value a { color: var(--primary-color); }
.tasks-section, .notes-section { display: flex; flex-direction: column; gap: 6px; }
.section-header { display: flex; align-items: baseline; gap: 8px; }
.count {
font-size: 11px; color: var(--secondary-text-color);
background: var(--secondary-background-color); padding: 2px 8px; border-radius: 999px;
}
.empty { color: var(--secondary-text-color); font-style: italic; font-size: 13px; padding: 8px 0; }
.task-list { display: flex; flex-direction: column; gap: 4px; max-height: 200px; overflow: auto; }
.task-row {
display: flex; align-items: center; gap: 10px;
padding: 8px; border-radius: 6px; cursor: pointer;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
transition: background 0.12s;
}
.task-row:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.status-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.task-name { flex: 1; font-size: 14px; }
.task-status { font-size: 11px; color: var(--secondary-text-color); text-transform: uppercase; }
.notes-body { white-space: pre-wrap; font-size: 13px; padding: 8px; background: var(--secondary-background-color); border-radius: 6px; }
.notes-body ha-markdown { white-space: normal; }
.actions { display: flex; gap: 8px; padding-top: 8px; border-top: 1px solid var(--divider-color); }
.actions .btn { flex: 1; }
.btn {
padding: 8px; font-size: 13px; border-radius: 6px; cursor: pointer;
border: 1px solid var(--divider-color);
background: var(--secondary-background-color, transparent);
color: var(--primary-text-color); font-weight: 500;
display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: wait; }
.btn.primary { background: var(--primary-color); color: var(--text-primary-color, white); border-color: var(--primary-color); }
.btn.danger { color: var(--error-color); }
.btn ha-icon { --mdc-icon-size: 16px; }
.loading { padding: 24px; text-align: center; color: var(--secondary-text-color); }
.error { padding: 8px; border-radius: 6px; background: rgba(211,47,47,0.1); color: var(--error-color); font-size: 13px; }
`,h([P({attribute:!1})],L.prototype,"hass",2),h([m()],L.prototype,"_open",2),h([m()],L.prototype,"_entryId",2),h([m()],L.prototype,"_data",2),h([m()],L.prototype,"_busy",2),h([m()],L.prototype,"_error",2);customElements.get("maintenance-object-quick-actions-dialog")||customElements.define("maintenance-object-quick-actions-dialog",L);var Pt="maintenance-object-dialog",Et="maintenance-task-dialog",Jt="maintenance-history-edit-dialog",Qt="maintenance-complete-dialog",Xt="maintenance-qr-dialog",te="maintenance-task-quick-actions-dialog",ee="maintenance-object-quick-actions-dialog";function Y(){return document.querySelector("home-assistant")?.hass}function ne(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function E(e){let i=ne(),t=i.querySelector(e)??document.body.querySelector(e);return t?t.parentNode!==i&&i.appendChild(t):(t=document.createElement(e),i.appendChild(t)),t}function S(e){let i=Y();if(!i)return!1;e.hass=i;let t=j(i);return it(t)||st(t).then(()=>{e.requestUpdate?.()}),at(i.locale,i.config?.country),!0}function Rn(e){return B(e).then(i=>i.rowActionStyle)}function On(){yt()}function zn(){let e=E(Pt);return S(e)?(e.openCreate(),!0):!1}function Nn(e,i){let t=E(Pt);return S(t)?(t.openEdit(e,i),!0):!1}function qn(e="",i){let t=E(Et);if(!S(t))return!1;let n=Y();return n?((async()=>{let s=await B(n),r=t;r.checklistsEnabled=s.features.checklists,r.scheduleTimeEnabled=s.features.schedule_time,r.completionActionsEnabled=s.features.completion_actions,r.defaultWarningDays=s.defaultWarningDays,r.openCreate(e,i)})(),!0):!1}function Vn(e,i){let t=E(Et);if(!S(t))return!1;let n=Y();return n?((async()=>{try{let[s,r]=await Promise.all([n.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:e}),B(n)]),o=(s.tasks||[]).find(c=>c.id===i);if(!o){console.warn(`openEditTaskDialog: task ${i} not found in entry ${e}`);return}let d=t;d.checklistsEnabled=r.features.checklists,d.scheduleTimeEnabled=r.features.schedule_time,d.completionActionsEnabled=r.features.completion_actions,d.defaultWarningDays=r.defaultWarningDays,await d.openEdit(e,o)}catch(s){console.warn("openEditTaskDialog: failed to load task/features",s)}})(),!0):!1}function Fn(e){let i=E(Jt);return S(i)?(i.openEdit(e),!0):!1}function Wn(e){let i=E(Qt);return S(i)?($t(i,e,Y()?.language||"en"),!0):!1}function Un(e){let i=E(Xt);return S(i)?(i.openForTask(e.entry_id,e.task_id,e.object_name,e.task_name),!0):!1}function Bn(e,i){let t=E(te);return S(t)?(t.openFor(e,i),!0):!1}function Kn(e){let i=E(ee);return S(i)?(i.openFor(e),!0):!1}export{Dt as a,ye as b,Mt as c,Wt as d,Ut as e,et as f,xt as g,$t as h,y as i,Me as j,Pe as k,Ee as l,Se as m,Ce as n,He as o,je as p,q,xe as r,$e as s,kt as t,wt as u,At as v,Tt as w,Lt as x,Rn as y,On as z,zn as A,Nn as B,qn as C,Vn as D,Fn as E,Wn as F,Un as G,Bn as H,Kn as I};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{q as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";function f(r){return`${r.entry_id??""}\0${r.part_id}`}function g(r,n,i,o){let t=!!r.entry_id&&r.entry_id!==n,e=t?r.entry_id:n,s=i.find(l=>l.entry_id===e),a=(s?.parts||[]).find(l=>l.id===r.part_id)||null,u=t&&s?.object?.name||"",d=a?.name||p("shared_part_unknown",o);return{part:a,foreign:t,ownerName:u,label:u?`${d} (${u})`:d}}function _(r,n,i,o){let{part:t,label:e}=g(r,n,i,o),s=t&&t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:"",a=t?.storage_location?` \u2014 ${t.storage_location}`:"";return`${r.quantity}\xD7 ${e}${s}${a}`}function R(r,n,i,o){let e=(i.find(a=>a.entry_id===n)?.parts||[]).map(a=>({...a})),s=new Set(e.map(a=>f({part_id:a.id})));for(let a of r?.consumes_parts||[]){if(!a.entry_id||a.entry_id===n)continue;let u=f(a);if(s.has(u))continue;s.add(u);let{part:d,ownerName:l}=g(a,n,i,o);e.push({id:a.part_id,name:d?.name||p("shared_part_unknown",o),unit:d?.unit,stock:d?.stock??null,storage_location:d?.storage_location,entry_id:a.entry_id,owner_name:l})}return e}function w(){let r=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:n=>n.map(()=>Math.floor(Math.random()*256))}).getRandomValues(r),Array.from(r,n=>n.toString(16).padStart(2,"0")).join("")}function c(r){let n=r?.reading_values;if(!Array.isArray(n))return[];let i=[],o=new Set;for(let t of n){if(!t||typeof t!="object")continue;let e=t;typeof e.id!="string"||!e.id||o.has(e.id)||typeof e.value!="number"||!Number.isFinite(e.value)||(o.add(e.id),i.push({id:e.id,name:typeof e.name=="string"?e.name:e.id,unit:typeof e.unit=="string"?e.unit:null,value:e.value}))}return i}function y(r){return r.filter(n=>n.type==="completed"&&(n.reading_value!=null||c(n).length>0)).sort((n,i)=>n.timestamp.localeCompare(i.timestamp))}function S(r){return y(r??[]).map(n=>({timestamp:n.timestamp,values:c(n)})).filter(n=>n.values.length>0)}function P(r,n,i){let o;for(let t of r){if(i!==void 0){let s=new Date(t.timestamp).getTime();if(!isNaN(s)&&s>=i)break}let e=t.values.find(s=>s.id===n);e&&(o=e)}return o}function b(r,n,i){let o=c(n).find(e=>e.id===i);if(!o)return null;let t=null;for(let e of y(r)){if(e.timestamp>=n.timestamp)break;let s=c(e).find(a=>a.id===i);s&&(t=s.value)}return t==null?null:o.value-t}function E(r){let n=new Set,i=new Set;for(let o of r){let t=(o.name||"").trim().toLowerCase();t&&(n.has(t)?i.add(o.id):n.add(t))}return i}function v(r){let n=[],i=new Set,o=new Set;for(let t of r){let e=(t.name||"").trim();if(!(!e||i.has(t.id)||o.has(e.toLowerCase()))&&(i.add(t.id),o.add(e.toLowerCase()),n.push({id:t.id,name:e,unit:(t.unit||"").trim()||null}),n.length>=20))break}return n}var x=["notes","cost","duration","photo","user"],h={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{f as a,_ as b,R as c,w as d,c as e,S as f,P as g,b as h,E as i,v as j,x as k,h as l};
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,213 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O5PSKBSH.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-NAI3MD3R.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};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{b as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-O5PSKBSH.js";async function l(t,e,n=300){return(await t.connection.sendMessagePromise({type:"auth/sign_path",path:e,expires:n})).path}async function r(t,e,n=300){return l(t,`/api/maintenance_supporter/document/${e}`,n)}async function m(t,e,n=""){let o=window.open("about:blank","_blank");try{let s=await r(t,e);o&&(o.location.href=new URL(s+n,window.location.origin).href)}catch(s){throw o&&o.close(),s}}async function u(t,e,n){a(await r(t,e,30),n)}function g(t){let e=URL.createObjectURL(new Blob([t],{type:"text/html"}));window.open(e,"_blank"),setTimeout(()=>URL.revokeObjectURL(e),6e4)}var i={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7,rowActionStyle:"buttons_compact"};function c(){let t=window;return t.__msSettingsCache??={promise:null}}function d(t){let e=c();if(e.promise)return e.promise;let n=t.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(o=>({features:o.features??i.features,defaultWarningDays:o.general?.default_warning_days??7,rowActionStyle:o.general?.row_action_style??i.rowActionStyle})).catch(()=>(e.promise===n&&(e.promise=null),i));return e.promise=n,n}function h(){c().promise=null}export{l as a,r as b,m as c,u as d,g as e,d as f,h as g};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{d as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VRRFDTZU.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-XMX3E3HG.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2AIQAMK4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";export{a as MaintenanceCompleteDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{A as c,B as d,C as e,D as f,E as g,F as h,G as i,H as j,I as k,y as a,z as b}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-WVQ3GLZ5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-YQMBLRSW.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-KIBYSZCP.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y2KIPCNO.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7G3267J5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-VRRFDTZU.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-XMX3E3HG.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2AIQAMK4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y7HABQI7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O5PSKBSH.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-PRQXFMNO.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";export{b as __resetSettingsCacheForTests,a as getRowActionStyle,h as openCompleteDialog,c as openCreateObjectDialog,e as openCreateTaskDialog,d as openEditObjectDialog,f as openEditTaskDialog,g as openHistoryEditDialog,k as openObjectQuickActions,i as openQrDialog,j as openTaskQuickActions};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-KIBYSZCP.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7G3267J5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2AIQAMK4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";export{a as MaintenanceObjectDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y7HABQI7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-O5PSKBSH.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";export{a as MaintenanceQrDialog};
@@ -0,0 +1,146 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import{a as l,b as m,c as i,f as h,h as v,l as u,m as d,q as p,s as f,u as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";var a=class extends v{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._setups=[];this._selected=new Set;this._baselines=new Map;this._targets=new Map;this._objects=[];this._localeReady=!1;this._toggle=t=>{let e=new Set(this._selected);e.has(t)?e.delete(t):e.add(t),this._selected=e};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/adopt",selections:[...this._selected].map(e=>{let r={device_id:e},c=this._targets.get(e);c&&(r.entry_id=c);let s=this._setups.find(n=>n.device_id===e);for(let n of s?.tasks??[]){let o=this._baselines.get(`${e} ${n.task_name}`),_=o?parseFloat(o):NaN;!isNaN(_)&&_>=0&&((r.baselines??={})[n.task_name]=_)}return r})});this.dispatchEvent(new CustomEvent("integration-setups-adopted",{bubbles:!0,composed:!0,detail:t})),this._open=!1}catch(t){this._error=g(t,this._lang)}finally{this._adopting=!1}}}}get _lang(){return f(this.hass)}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,x(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._setups=[],this._selected=new Set;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/integration_setups/discover"});this._setups=t.setups||[],this._selected=new Set(this._setups.map(e=>e.device_id)),this._baselines=new Map,this._targets=new Map;try{let e=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/objects"});this._objects=(e.objects||[]).map(r=>({entry_id:r.entry_id,name:r.object?.name||r.entry_id})).sort((r,c)=>r.name.localeCompare(c.name))}catch{this._objects=[]}}catch(t){this._error=g(t,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let t=this._lang;return i`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${e=>e.stopPropagation()}>
<div class="title">${p("setups_title",t)}</div>
<div class="hint">${p("setups_hint",t)}</div>
${this._error?i`<div class="error">${this._error}</div>`:h}
${this._loading?i`<div class="loading">…</div>`:this._setups.length===0?i`<div class="empty">${p("setups_none",t)}</div>`:i`
<div class="list">
${this._setups.map(e=>{let r=this._selected.has(e.device_id),c=[e.integration_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
<label class="row">
<input
type="checkbox"
.checked=${r}
@change=${()=>this._toggle(e.device_id)}
/>
<div class="row-main">
<div class="row-top">
<span class="row-name">${e.device_name}</span>
</div>
<div class="row-sub">${c}</div>
<div class="row-target" @click=${s=>s.preventDefault()}>
→
${r&&this._objects.length>0?i`
<select
class="target-select"
@change=${s=>{let n=new Map(this._targets),o=s.target.value;o?n.set(e.device_id,o):n.delete(e.device_id),this._targets=n}}
>
<option value="" ?selected=${!this._targets.get(e.device_id)}>
${e.suggested_entry_id?e.suggested_object_name:p("setups_target_new",t).replace("{name}",e.suggested_object_name)}
</option>
${this._objects.filter(s=>s.entry_id!==e.suggested_entry_id).map(s=>i`<option
value=${s.entry_id}
?selected=${this._targets.get(e.device_id)===s.entry_id}
>
${s.name}
</option>`)}
</select>
`:i`${e.suggested_object_name}${e.suggested_entry_id?h:i` <span class="new-tag">${p("adopt_problem_new_object",t)}</span>`}`}
</div>
<div class="row-tasks">
${e.tasks.map(s=>i`<span class="chip" title=${s.entity_ids.join(", ")}>
<ha-icon icon="mdi:link-variant"></ha-icon>${s.task_name_localized||s.task_name}
</span>`)}
</div>
${r?e.tasks.filter(s=>s.direction==="usage_delta").map(s=>{let n=`${e.device_id} ${s.task_name}`;return i`
<div class="baseline-field" @click=${o=>o.preventDefault()}>
<span class="baseline-label"
>${s.task_name_localized||s.task_name} —
${p("setups_baseline_hint",t)}</span
>
<input
type="number"
step="any"
min="0"
.value=${this._baselines.get(n)??""}
@click=${o=>o.preventDefault()}
@input=${o=>{let _=new Map(this._baselines);_.set(n,o.target.value),this._baselines=_}}
/>
</div>
`}):h}
</div>
</label>
`})}
</div>
`}
<div class="actions">
<ha-button appearance="plain" @click=${this._close}>
${p("cancel",t)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${p("setups_adopt",t)}
</ha-button>
</div>
</div>
</div>
`}};a.styles=m`
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.card {
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
border-radius: 12px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
min-width: min(360px, calc(100vw - 24px));
max-width: 560px;
width: 90vw;
max-height: 80vh;
overflow: hidden;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
}
.title { font-size: 18px; font-weight: 500; }
.hint { color: var(--secondary-text-color); font-size: 13px; }
.error { color: var(--error-color, #f44336); font-size: 13px; }
.loading, .empty { color: var(--secondary-text-color); font-size: 14px; padding: 12px 0; }
.list { display: flex; flex-direction: column; gap: 6px; overflow-y: auto; max-height: 50vh; }
.row {
display: flex; align-items: flex-start; gap: 10px; padding: 8px;
border: 1px solid var(--divider-color); border-radius: 6px; cursor: pointer;
}
.row input { margin-top: 2px; cursor: pointer; }
.row-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.row-name { font-weight: 500; font-size: 13px; }
.row-sub, .row-target { color: var(--secondary-text-color); font-size: 12px; }
.new-tag { font-style: italic; }
.row-tasks { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; }
.chip {
display: inline-flex; align-items: center; gap: 4px;
font-size: 11px; padding: 2px 8px; border-radius: 10px;
background: var(--secondary-background-color, rgba(0, 0, 0, 0.06));
color: var(--primary-text-color); white-space: nowrap;
}
.chip ha-icon { --mdc-icon-size: 12px; color: var(--primary-color); }
.target-select {
font-size: 12px; padding: 2px 4px; max-width: 100%;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.baseline-field {
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
margin-top: 4px; font-size: 12px; color: var(--secondary-text-color);
}
.baseline-field input {
width: 110px; padding: 3px 6px; font-size: 12px;
border: 1px solid var(--divider-color); border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
}
.actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 8px; }
`,l([u({attribute:!1})],a.prototype,"hass",2),l([d()],a.prototype,"_open",2),l([d()],a.prototype,"_loading",2),l([d()],a.prototype,"_adopting",2),l([d()],a.prototype,"_error",2),l([d()],a.prototype,"_setups",2),l([d()],a.prototype,"_selected",2),l([d()],a.prototype,"_baselines",2),l([d()],a.prototype,"_targets",2),l([d()],a.prototype,"_objects",2);customElements.get("maintenance-suggested-setups-dialog")||customElements.define("maintenance-suggested-setups-dialog",a);export{a as MaintenanceSuggestedSetupsDialog};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-Y2KIPCNO.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-7G3267J5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-XMX3E3HG.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-2AIQAMK4.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-PRQXFMNO.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-3IZWSKBJ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NAI3MD3R.js";export{a as MaintenanceTaskDialog};
@@ -0,0 +1,125 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as x,b as $}from"./chunk-WXJEZVBF.js";import{a as h,b as n,d as b,e as y,f as m,g as o,h as _,i as a,j as g,k as v,o as f,p,z as c}from"./chunk-5CEYR4C6.js";import{a as l}from"./chunk-WJSTGOOD.js";var E=80,s=class extends y{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return v(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),g(this,t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load())}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/budget_status"});this._status=t,this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1}catch(t){this._error=c(t,this._lang)}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=this._localMonthly.trim()===""?0:parseFloat(this._localMonthly),r=this._localYearly.trim()===""?0:parseFloat(this._localYearly),i={};!isNaN(t)&&t>=0&&(i.budget_monthly=t),!isNaN(r)&&r>=0&&(i.budget_yearly=r),await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:i}),await this._load()}catch(t){this._error=c(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,r=this._status;if(!r)return n`<ha-card><div class="loading">${a("loading",t)||"Loading\u2026"}</div></ha-card>`;let i=r.currency_symbol||_,k=r.alert_threshold_pct??E,w=[{label:a("budget_monthly",t)||"Monthly",spent:r.monthly_spent||0,budget:r.monthly_budget||0},{label:a("budget_yearly",t)||"Yearly",spent:r.yearly_spent||0,budget:r.yearly_budget||0}];return n`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">💰</span>
<span>${this._config.title||a("settings_budget",t)||"Budget"}</span>
</div>
<span class="currency">${i}</span>
</div>
${this._error?n`<div class="error">${this._error}</div>`:b}
${w.map(e=>{if(!(e.budget>0))return n`
<div class="track spent-only">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ok">${p(e.spent,i,t,0)}</span>
</div>
</div>
`;let d=Math.min(100,Math.max(0,e.spent/e.budget*100)),u=d>=100?"danger":d>=k?"warning":"ok";return n`
<div class="track">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ${u}">
${f(e.spent,t,0)} / ${p(e.budget,i,t,0)}
</span>
</div>
<div class="bar"><div class="bar-fill ${u}" style="width:${d}%"></div></div>
</div>
`})}
${this._isAdmin?n`
<div class="inputs-row">
<div class="input-field">
<label>${a("budget_monthly_set",t)||"Set monthly"}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localMonthly}
?disabled=${this._busy}
@input=${e=>{this._localMonthly=e.target.value,this._dirty=!0}} />
<span class="input-suffix">${i}</span>
</div>
</div>
<div class="input-field">
<label>${a("budget_yearly_set",t)||"Set yearly"}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localYearly}
?disabled=${this._busy}
@input=${e=>{this._localYearly=e.target.value,this._dirty=!0}} />
<span class="input-suffix">${i}</span>
</div>
</div>
</div>
<div class="actions">
<button class="btn ${this._dirty?"primary":"muted"}"
@click=${this._save}
?disabled=${this._busy||!this._dirty}>
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
${this._dirty?a("save",t)||"Save":a("saved",t)||"Saved"}
</button>
<button class="btn link" @click=${this._onDeepLink}>
${a("budget_advanced",t)||"Currency, alerts\u2026"}
</button>
</div>
`:n`
<button class="btn link" @click=${this._onDeepLink}>
${a("budget_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};s.styles=[$,h`
.currency {
font-size: 14px; font-weight: 600;
color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 10px; border-radius: 999px;
}
.track { display: flex; flex-direction: column; gap: 4px; }
.track-label-row {
display: flex; align-items: center; justify-content: space-between;
}
.track-label-row label {
font-size: 12px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.5px;
}
.track-numbers { font-size: 13px; font-weight: 600; }
.track-numbers.ok { color: var(--primary-text-color); }
.track-numbers.warning { color: #ff9800; }
.track-numbers.danger { color: var(--error-color, #f44336); }
.bar {
height: 6px; background: var(--secondary-background-color);
border-radius: 3px; overflow: hidden;
}
.bar-fill { height: 100%; transition: width 0.3s; border-radius: 3px; }
.bar-fill.ok { background: var(--primary-color); }
.bar-fill.warning { background: #ff9800; }
.bar-fill.danger { background: var(--error-color, #f44336); }
.inputs-row {
display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
padding-top: 4px; border-top: 1px solid var(--divider-color);
}
.input-field { display: flex; flex-direction: column; gap: 4px; }
.input-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.input-wrap { position: relative; display: flex; align-items: center; }
.input-wrap input {
flex: 1; padding: 6px 32px 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
.input-suffix {
position: absolute; right: 8px;
color: var(--secondary-text-color); font-size: 13px;
pointer-events: none;
}
.actions { display: flex; gap: 8px; align-items: center; }
`],l([m({attribute:!1})],s.prototype,"hass",2),l([o()],s.prototype,"_config",2),l([o()],s.prototype,"_status",2),l([o()],s.prototype,"_busy",2),l([o()],s.prototype,"_error",2),l([o()],s.prototype,"_localMonthly",2),l([o()],s.prototype,"_localYearly",2),l([o()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);x({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Inline monthly + yearly budget editor",preview:!1});export{s as MaintenanceBudgetSectionCard};
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,64 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as d,b as a,d as l,e as p,f as s,i as c}from"./chunk-5CEYR4C6.js";import{a as r}from"./chunk-WJSTGOOD.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);
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
function l(i){if(!i)return[];let n=[],o=i.photo_doc_id;typeof o=="string"&&n.push(o);let r=i.photo_doc_ids;Array.isArray(r)&&n.push(...r);let e=[];for(let t of n){if(typeof t!="string")continue;let a=t.trim();if(!(!a||e.includes(a))&&(e.push(a),e.length>=10))break}return e}function c(){let i=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:n=>n.map(()=>Math.floor(Math.random()*256))}).getRandomValues(i),Array.from(i,n=>n.toString(16).padStart(2,"0")).join("")}function s(i){let n=i?.reading_values;if(!Array.isArray(n))return[];let o=[],r=new Set;for(let e of n){if(!e||typeof e!="object")continue;let t=e;typeof t.id!="string"||!t.id||r.has(t.id)||typeof t.value!="number"||!Number.isFinite(t.value)||(r.add(t.id),o.push({id:t.id,name:typeof t.name=="string"?t.name:t.id,unit:typeof t.unit=="string"?t.unit:null,value:t.value}))}return o}function u(i){return i.filter(n=>n.type==="completed"&&(n.reading_value!=null||s(n).length>0)).sort((n,o)=>n.timestamp.localeCompare(o.timestamp))}function g(i){return u(i??[]).map(n=>({timestamp:n.timestamp,values:s(n)})).filter(n=>n.values.length>0)}function y(i,n,o){let r;for(let e of i){if(o!==void 0){let a=new Date(e.timestamp).getTime();if(!isNaN(a)&&a>=o)break}let t=e.values.find(a=>a.id===n);t&&(r=t)}return r}function p(i,n,o){let r=s(n).find(t=>t.id===o);if(!r)return null;let e=null;for(let t of u(i)){if(t.timestamp>=n.timestamp)break;let a=s(t).find(d=>d.id===o);a&&(e=a.value)}return e==null?null:r.value-e}function m(i){let n=new Set,o=new Set;for(let r of i){let e=(r.name||"").trim().toLowerCase();e&&(n.has(e)?o.add(r.id):n.add(e))}return o}function R(i){let n=[],o=new Set,r=new Set;for(let e of i){let t=(e.name||"").trim();if(!(!t||o.has(e.id)||r.has(t.toLowerCase()))&&(o.add(e.id),r.add(t.toLowerCase()),n.push({id:e.id,name:t,unit:(e.unit||"").trim()||null}),n.length>=20))break}return n}export{l as a,c as b,s as c,g as d,y as e,p as f,m as g,R as h};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.80.0 */
var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var t=(a,r,c,o)=>{for(var e=o>1?void 0:o?l(r,c):r,i=a.length-1,d;i>=0;i--)(d=a[i])&&(e=(o?d(r,c,e):d(e))||e);return o&&e&&s(r,c,e),e};var m={ok:"var(--success-color, #4caf50)",due_soon:"var(--warning-color, #ff9800)",overdue:"var(--error-color, #f44336)",triggered:"var(--deep-orange-color, #ff5722)",archived:"var(--disabled-color, #9e9e9e)",paused:"var(--info-color, #2196f3)"},v={ok:"mdi:check-circle",due_soon:"mdi:alert-circle",overdue:"mdi:alert-octagon",triggered:"mdi:bell-alert",archived:"mdi:archive-outline",paused:"mdi:pause-circle-outline",completed:"mdi:check-circle",skipped:"mdi:skip-next",missed:"mdi:calendar-remove",reset:"mdi:refresh"};export{t as a,m as b,v as c};
@@ -0,0 +1,60 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as t}from"./chunk-5CEYR4C6.js";function e(o){let r=window;r.customCards=r.customCards||[],r.customCards.some(a=>a.type===o.type)||r.customCards.push(o)}var d=t`
ha-card { overflow: hidden; }
.card-content {
padding: 16px;
display: flex; flex-direction: column;
gap: 12px;
}
.header {
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
}
.title {
display: flex; align-items: center; gap: 8px;
font-size: 16px; font-weight: 500;
}
.emoji { font-size: 20px; }
/* Button family — primary action / muted-saved-state / link / icon-with-text */
.btn {
padding: 6px 12px; font-size: 13px;
border-radius: 6px; cursor: pointer;
border: 1px solid var(--divider-color);
background: var(--secondary-background-color, transparent);
color: var(--primary-text-color);
font-weight: 500;
display: inline-flex; align-items: center; gap: 4px;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn.primary {
background: var(--primary-color);
color: var(--text-primary-color, white);
border-color: var(--primary-color);
}
.btn.primary[disabled] { opacity: 0.6; }
.btn.muted {
background: transparent;
color: var(--secondary-text-color);
border-style: dashed;
}
.btn.muted[disabled] { opacity: 1; cursor: default; }
.btn.muted ha-icon, .btn.primary ha-icon { --mdc-icon-size: 14px; }
.btn.link {
background: transparent; border: none; padding: 6px 4px;
color: var(--primary-color); margin-left: auto;
}
.btn.link:hover { background: transparent; text-decoration: underline; }
/* Error + loading states */
.error {
padding: 8px; border-radius: 6px;
background: rgba(211, 47, 47, 0.1);
color: var(--error-color, #d32f2f); font-size: 13px;
}
.loading {
padding: 24px; text-align: center;
color: var(--secondary-text-color);
}
`;export{e as a,d as b};
@@ -0,0 +1,137 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as v,b}from"./chunk-WXJEZVBF.js";import{a as u,b as s,d as c,e as h,f as g,g as o,i,k as _,m,z as p}from"./chunk-5CEYR4C6.js";import{a}from"./chunk-WJSTGOOD.js";var e=class extends h{constructor(){super(...arguments);this._config={type:""};this._groups={};this._loaded=!1;this._busy=!1;this._error="";this._newName="";this._editingId=null;this._editingName="";this._hasInitiallyLoaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return _(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._hasInitiallyLoaded&&(this._hasInitiallyLoaded=!0,this._load(),m(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/groups"});this._groups=t.groups||{},this._loaded=!0}catch(t){this._error=p(t,this._lang)}}async _addGroup(){if(!this._isAdmin)return;let t=this._newName.trim();if(t){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/create",name:t}),this._newName="",await this._load()}catch(r){this._error=p(r,this._lang)}finally{this._busy=!1}}}_startEdit(t){this._editingId=t,this._editingName=this._groups[t]?.name||""}async _saveEdit(){if(!this._isAdmin||!this._editingId)return;let t=this._editingName.trim();if(t){this._busy=!0,this._error="";try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/update",group_id:this._editingId,name:t}),this._editingId=null,this._editingName="",await this._load()}catch(r){this._error=p(r,this._lang)}finally{this._busy=!1}}}async _deleteGroup(t,r){if(!this._isAdmin)return;let n=(i("group_delete_confirm",this._lang)||'Delete group "{name}"?').replace("{name}",r);if(window.confirm(n)){this._busy=!0;try{await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/group/delete",group_id:t}),await this._load()}catch(d){this._error=p(d,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_groups"),window.dispatchEvent(new CustomEvent("location-changed"))}_onKeyDown(t,r){t.key==="Enter"?(t.preventDefault(),r()):t.key==="Escape"&&(t.preventDefault(),this._editingId=null,this._editingName="")}render(){let t=this._lang;if(!this._loaded)return s`<ha-card><div class="loading">${i("loading",t)||"Loading\u2026"}</div></ha-card>`;let r=Object.keys(this._groups);return s`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏷️</span>
<span>${this._config.title||i("groups",t)||"Groups"}</span>
<span class="count">${r.length}</span>
</div>
</div>
${this._error?s`<div class="error">${this._error}</div>`:c}
${r.length===0?s`<div class="empty">${i("groups_empty",t)||"No groups yet."}</div>`:s`
<div class="group-list">
${r.map(n=>{let d=this._groups[n],y=d.task_refs?.length??0,f=this._editingId===n;return s`
<div class="group-row">
${f?s`
<input class="edit-input" type="text"
.value=${this._editingName}
?disabled=${this._busy}
@input=${l=>{this._editingName=l.target.value}}
@keydown=${l=>this._onKeyDown(l,this._saveEdit.bind(this))} />
<button class="btn small primary"
@click=${this._saveEdit}
?disabled=${this._busy||!this._editingName.trim()}>
${i("save",t)||"Save"}
</button>
<button class="btn small"
@click=${()=>{this._editingId=null}}>
${i("cancel",t)||"Cancel"}
</button>
`:s`
<span class="group-name">${d.name||"Unnamed"}</span>
<span class="task-count">${y}</span>
${this._isAdmin?s`
<button class="icon-btn"
title="${i("edit",t)||"Edit"}"
@click=${()=>this._startEdit(n)}
?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
</button>
<button class="icon-btn danger"
title="${i("delete",t)||"Delete"}"
@click=${()=>this._deleteGroup(n,d.name||"Unnamed")}
?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
</button>
`:c}
`}
</div>
`})}
</div>
`}
${this._isAdmin?s`
<div class="add-row">
<input type="text"
placeholder="${i("group_new_placeholder",t)||"Add group\u2026"}"
.value=${this._newName}
?disabled=${this._busy}
@input=${n=>{this._newName=n.target.value}}
@keydown=${n=>this._onKeyDown(n,this._addGroup.bind(this))} />
<button class="btn primary"
@click=${this._addGroup}
?disabled=${this._busy||!this._newName.trim()}>
<ha-icon icon="mdi:plus"></ha-icon>
${i("add",t)||"Add"}
</button>
</div>
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_manage_tasks",t)||"Manage task assignments\u2026"}
</button>
`:s`
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};e.styles=[b,u`
.count {
font-size: 12px; color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 8px; border-radius: 999px;
}
.empty {
padding: 16px; text-align: center;
color: var(--secondary-text-color); font-style: italic;
}
.group-list { display: flex; flex-direction: column; gap: 4px; }
.group-row {
display: flex; align-items: center; gap: 8px;
padding: 6px 8px; border-radius: 6px;
background: var(--secondary-background-color, rgba(255,255,255,0.03));
}
.group-name { flex: 1; font-size: 14px; }
.task-count {
font-size: 11px; color: var(--secondary-text-color);
background: var(--card-background-color, rgba(0,0,0,0.2));
padding: 1px 8px; border-radius: 999px;
font-weight: 500;
}
.edit-input {
flex: 1; padding: 4px 8px; font-size: 14px;
background: var(--card-background-color, #1c1c1c);
color: var(--primary-text-color);
border: 1px solid var(--primary-color); border-radius: 4px;
font-family: inherit;
}
.icon-btn {
background: transparent; border: none; cursor: pointer;
color: var(--secondary-text-color); padding: 4px;
border-radius: 4px;
}
.icon-btn:hover {
background: var(--state-icon-color, rgba(255,255,255,0.06));
color: var(--primary-text-color);
}
.icon-btn.danger:hover { color: var(--error-color); }
.icon-btn ha-icon { --mdc-icon-size: 18px; }
.add-row {
display: flex; gap: 6px;
padding-top: 8px; border-top: 1px solid var(--divider-color);
}
.add-row input {
flex: 1; padding: 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
/* Card-specific overrides on the shared .btn */
.btn.small { padding: 4px 8px; font-size: 12px; }
.btn ha-icon { --mdc-icon-size: 16px; }
`],a([g({attribute:!1})],e.prototype,"hass",2),a([o()],e.prototype,"_config",2),a([o()],e.prototype,"_groups",2),a([o()],e.prototype,"_loaded",2),a([o()],e.prototype,"_busy",2),a([o()],e.prototype,"_error",2),a([o()],e.prototype,"_newName",2),a([o()],e.prototype,"_editingId",2),a([o()],e.prototype,"_editingName",2);customElements.get("maintenance-groups-section-card")||customElements.define("maintenance-groups-section-card",e);v({type:"maintenance-groups-section-card",name:"Maintenance Supporter \u2014 Groups",description:"Inline group CRUD",preview:!1});export{e as MaintenanceGroupsSectionCard};
@@ -0,0 +1,134 @@
/*! maintenance_supporter frontend 2.80.0 */
import{a as b,b as g}from"./chunk-WXJEZVBF.js";import"./chunk-CWJ56MWO.js";import{a as h,b as n,d as c,e as _,f as v,g as r,i as e,k as f,m,z as o}from"./chunk-5CEYR4C6.js";import{a as i}from"./chunk-WJSTGOOD.js";var a=class extends _{constructor(){super(...arguments);this._config={type:""};this._state=null;this._busy=!1;this._error="";this._localStart="";this._localEnd="";this._localBuffer=7;this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return f(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),m(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/state"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||"",this._localBuffer=t.buffer_days??7,this._dirty=!1}catch(t){this._error=o(t,this._lang)}}async _toggleEnabled(t){this._busy=!0,this._error="";try{let s=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",enabled:t});this._state=s}catch(s){this._error=o(s,this._lang)}finally{this._busy=!1}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",start:this._localStart||null,end:this._localEnd||null,buffer_days:this._localBuffer});this._state=t,this._dirty=!1}catch(t){this._error=o(t,this._lang)}finally{this._busy=!1}}}async _endNow(){if(this._isAdmin&&window.confirm(e("vacation_end_now_confirm",this._lang)||"End vacation immediately?")){this._busy=!0;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/end_now"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||""}catch(t){this._error=o(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_vacation"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,s=this._state;if(!s)return n`<ha-card><div class="loading">${e("loading",t)||"Loading\u2026"}</div></ha-card>`;let p=s.is_active===!0,d=s.enabled===!0,u=s.exempt_task_ids?.length??0,y=p?e("vacation_status_active",t)||"Active now":d?e("vacation_status_scheduled",t)||"Scheduled":e("vacation_status_inactive",t)||"Inactive",$=p?"active":d?"scheduled":"inactive";return n`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏖️</span>
<span>${this._config.title||e("vacation_mode",t)||"Vacation mode"}</span>
</div>
<span class="status-pill ${$}">${y}</span>
</div>
${this._error?n`<div class="error">${this._error}</div>`:c}
${this._isAdmin?n`
<div class="row toggle-row">
<label>${e("enable",t)||"Enable"}</label>
<ha-switch
.checked=${d}
.disabled=${this._busy}
@change=${l=>this._toggleEnabled(l.target.checked)}
></ha-switch>
</div>
<div class="dates-row">
<div class="date-field">
<label>${e("vacation_start",t)||"Start"}</label>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${t}
.value=${this._localStart}
.disabled=${this._busy}
@value-changed=${l=>{this._localStart=l.detail.value,this._dirty=!0}}
></ms-date-field>
</div>
<div class="date-field">
<label>${e("vacation_end",t)||"End"}</label>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${t}
.value=${this._localEnd}
.disabled=${this._busy}
@value-changed=${l=>{this._localEnd=l.detail.value,this._dirty=!0}}
></ms-date-field>
</div>
<div class="date-field buffer">
<label>${e("vacation_buffer",t)||"Buffer days"}</label>
<input type="number" min="0" max="14"
.value=${String(this._localBuffer)}
?disabled=${this._busy}
@input=${l=>{this._localBuffer=parseInt(l.target.value,10)||0,this._dirty=!0}} />
</div>
</div>
<div class="actions">
<button class="btn ${this._dirty?"primary":"muted"}"
@click=${this._save}
?disabled=${this._busy||!this._dirty}>
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
${this._dirty?e("save",t)||"Save":e("saved",t)||"Saved"}
</button>
${p?n`<button class="btn"
@click=${this._endNow}
?disabled=${this._busy}>
${e("vacation_end_now",t)||"End now"}
</button>`:c}
${u>0?n`<button class="btn link"
@click=${this._onDeepLink}>
${u} ${e("vacation_exempt_count",t)||"exempt"}…
</button>`:n`<button class="btn link"
@click=${this._onDeepLink}>
${e("vacation_advanced",t)||"Advanced\u2026"}
</button>`}
</div>
`:n`
<div class="readonly">
${d&&s.start&&s.end?n`<div>${s.start} → ${s.end}</div>`:c}
<button class="btn link" @click=${this._onDeepLink}>
${e("vacation_open_panel",t)||"Open in panel"}
</button>
</div>
`}
</div>
</ha-card>
`}};a.styles=[g,h`
.status-pill {
font-size: 11px; font-weight: 600;
padding: 3px 8px; border-radius: 999px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.status-pill.active {
background: rgba(76, 175, 80, 0.15);
color: #4caf50;
}
.status-pill.scheduled {
background: rgba(255, 152, 0, 0.15);
color: #ff9800;
}
.status-pill.inactive {
background: rgba(158, 158, 158, 0.15);
color: var(--secondary-text-color);
}
.row.toggle-row {
display: flex; align-items: center; justify-content: space-between;
}
.row.toggle-row label {
font-size: 14px; color: var(--primary-text-color);
}
.dates-row {
display: grid; grid-template-columns: 1fr 1fr 100px; gap: 10px;
}
.date-field.buffer label { white-space: nowrap; }
.date-field { display: flex; flex-direction: column; gap: 4px; }
.date-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.date-field input {
padding: 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
.date-field input:disabled { opacity: 0.5; cursor: not-allowed; }
.actions {
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}
.readonly { display: flex; flex-direction: column; gap: 8px; }
`],i([v({attribute:!1})],a.prototype,"hass",2),i([r()],a.prototype,"_config",2),i([r()],a.prototype,"_state",2),i([r()],a.prototype,"_busy",2),i([r()],a.prototype,"_error",2),i([r()],a.prototype,"_localStart",2),i([r()],a.prototype,"_localEnd",2),i([r()],a.prototype,"_localBuffer",2),i([r()],a.prototype,"_dirty",2);customElements.get("maintenance-vacation-section-card")||customElements.define("maintenance-vacation-section-card",a);b({type:"maintenance-vacation-section-card",name:"Maintenance Supporter \u2014 Vacation",description:"Inline vacation mode toggle + dates",preview:!1});export{a as MaintenanceVacationSectionCard};
@@ -1,5 +1,5 @@
/*! maintenance_supporter frontend 2.74.0 */
import{c as y}from"./chunks/chunk-3YPUSKYO.js";var x="maintenance-supporter",G=`ll-strategy-dashboard-${x}`,I="hui-maintenance-supporter-strategy-editor",V="NOT_RUNNING",H=[{status:"overdue",title:"Overdue",icon:y.overdue,path:"overdue"},{status:"triggered",title:"Triggered",icon:y.triggered,path:"triggered"},{status:"due_soon",title:"Due Soon",icon:y.due_soon,path:"due-soon"},{status:"ok",title:"OK",icon:y.ok,path:"ok"}],T=[{title:"Overdue",icon:"mdi:alert-circle",path:"overdue",filter:{filter_due_max_days:-1},matches:n=>n<=-1},{title:"Today",icon:"mdi:calendar-today",path:"today",filter:{filter_due_min_days:0,filter_due_max_days:0},matches:n=>n===0},{title:"This Week",icon:"mdi:calendar-week",path:"this-week",filter:{filter_due_min_days:1,filter_due_max_days:7},matches:n=>n>=1&&n<=7},{title:"This Month",icon:"mdi:calendar-month",path:"this-month",filter:{filter_due_min_days:8,filter_due_max_days:30},matches:n=>n>=8&&n<=30},{title:"Later",icon:"mdi:calendar-clock",path:"later",filter:{filter_due_min_days:31},matches:n=>n>=31}];function l(n){return{type:"grid",cards:[n]}}var N={condition:"view_columns",min:2};function _(n,i,e){let s=i[n];if(s)return`{{ states('${s}') }}`;let t=e[n];return t==null?"\u2014":String(t)}function B(n={},i={}){return{type:"markdown",text_only:!0,content:[`\u{1F534} **${_("overdue",n,i)}** overdue`,`\u26A1 **${_("triggered",n,i)}** triggered`,`\u{1F7E1} **${_("due_soon",n,i)}** due soon`,`\u{1F7E2} **${_("ok",n,i)}** ok`].join(" \xB7 ")}}function $(){return{title:"Maintenance",type:"panel",cards:[{type:"empty-state",icon:"mdi:wrench-clock",icon_color:"primary",content_only:!0,title:"No maintenance objects yet",content:"Open the Maintenance panel to add your first object \u2014 pool pump, HVAC filter, vehicle, anything that needs scheduled care.",buttons:[{icon:"mdi:wrench",text:"Open Maintenance panel",appearance:"filled",variant:"brand",tap_action:{action:"navigate",navigation_path:"/maintenance-supporter"}},{icon:"mdi:plus",text:"Add object",appearance:"outlined",variant:"brand",tap_action:{action:"fire-dom-event",ll_custom:{type:"maintenance-supporter:add-object"}}}]}]}}function U(n={},i={}){let e=B(n,i);return{title:"Overview",icon:"mdi:wrench-clock",path:"overview",type:"sections",header:{layout:"responsive",card:e},sidebar:{sections:[{type:"grid",cards:[{type:"heading",heading:"Status",heading_style:"title"},e]}],visibility:[N],content_label:"Tasks",sidebar_label:"Status"},sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:["overdue","triggered","due_soon"]})]}}function W(n,i){let e=new Map;for(let a of n){let r=a.object.area_id||null;e.has(r)||e.set(r,[]),e.get(r).push(a)}let s=Array.from(e.keys()).filter(a=>a!==null);s.sort((a,r)=>{let d=i[a]?.name||a,c=i[r]?.name||r;return d.localeCompare(c)});let t=[];for(let a of s){let r=e.get(a);if(r.length===0)continue;let d=i[a];t.push({title:d?.name||a,icon:d?.icon||"mdi:floor-plan",path:`area-${a}`,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:r.map(c=>c.object.name)})]})}let o=e.get(null);return o&&o.length>0&&t.push({title:"Unassigned",icon:"mdi:help-circle-outline",path:"unassigned",type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:o.map(a=>a.object.name)})]}),t}function Y(n){let i=new Set;for(let s of n)for(let t of s.tasks||[])t.status&&i.add(t.status);let e=[];for(let s of H)i.has(s.status)&&e.push({title:s.title,icon:s.icon,path:s.path,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:[s.status]})]});return e}function F(n,i,e){let s=new Map;for(let r of n){let d=r.object.area_id,c=d&&i[d]?.floor_id||null;s.has(c)||s.set(c,[]),s.get(c).push(r)}let t=Array.from(s.keys()).filter(r=>r!==null);t.sort((r,d)=>{let c=e[r],g=e[d],u=c?.level??0,p=g?.level??0;return u!==p?u-p:(c?.name||r).localeCompare(g?.name||d)});let o=[];for(let r of t){let d=s.get(r);if(d.length===0)continue;let c=e[r];o.push({title:c?.name||r,icon:c?.icon||"mdi:home-floor-1",path:`floor-${r}`,type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:d.map(g=>g.object.name)})]})}let a=s.get(null);return a&&a.length>0&&o.push({title:"Other",icon:"mdi:help-circle-outline",path:"other",type:"sections",sections:[l({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:a.map(r=>r.object.name)})]}),o}function K(n){return[{title:"Week",icon:"mdi:calendar-week",path:"cal-7",window_days:7},{title:"Fortnight",icon:"mdi:calendar-week-begin",path:"cal-14",window_days:14},{title:"Month",icon:"mdi:calendar-month",path:"cal-30",window_days:30},{title:"Year",icon:"mdi:calendar-clock",path:"cal-365",window_days:365}].map(e=>({title:e.title,icon:e.icon,path:e.path,type:"panel",cards:[{type:"custom:maintenance-supporter-calendar-card",window_days:e.window_days,show_window_chips:!1,show_user_filter:!0}]}))}function q(n){let i=new Set;for(let sLine truncated
/*! maintenance_supporter frontend 2.80.0 */
import{a as T,c as M}from"./chunks/chunk-HYTKA5KV.js";import{c as y}from"./chunks/chunk-WJSTGOOD.js";var D="maintenance-supporter",H=`ll-strategy-dashboard-${D}`,P="hui-maintenance-supporter-strategy-editor",N="NOT_RUNNING",B=[{status:"overdue",title:"Overdue",icon:y.overdue,path:"overdue"},{status:"triggered",title:"Triggered",icon:y.triggered,path:"triggered"},{status:"due_soon",title:"Due Soon",icon:y.due_soon,path:"due-soon"},{status:"ok",title:"OK",icon:y.ok,path:"ok"}],R=[{title:"Overdue",icon:"mdi:alert-circle",path:"overdue",filter:{filter_due_max_days:-1},matches:n=>n<=-1},{title:"Today",icon:"mdi:calendar-today",path:"today",filter:{filter_due_min_days:0,filter_due_max_days:0},matches:n=>n===0},{title:"This Week",icon:"mdi:calendar-week",path:"this-week",filter:{filter_due_min_days:1,filter_due_max_days:7},matches:n=>n>=1&&n<=7},{title:"This Month",icon:"mdi:calendar-month",path:"this-month",filter:{filter_due_min_days:8,filter_due_max_days:30},matches:n=>n>=8&&n<=30},{title:"Later",icon:"mdi:calendar-clock",path:"later",filter:{filter_due_min_days:31},matches:n=>n>=31}];function p(n){return{type:"grid",cards:[n]}}var $={condition:"view_columns",min:2};function _(n,i,e){let s=i[n];if(s)return`{{ states('${s}') }}`;let t=e[n];return t==null?"\u2014":String(t)}function U(n={},i={}){return{type:"markdown",text_only:!0,content:[`\u{1F534} **${_("overdue",n,i)}** overdue`,`\u26A1 **${_("triggered",n,i)}** triggered`,`\u{1F7E1} **${_("due_soon",n,i)}** due soon`,`\u{1F7E2} **${_("ok",n,i)}** ok`].join(" \xB7 ")}}function W(){return{title:"Maintenance",type:"panel",cards:[{type:"empty-state",icon:"mdi:wrench-clock",icon_color:"primary",content_only:!0,title:"No maintenance objects yet",content:"Open the Maintenance panel to add your first object \u2014 pool pump, HVAC filter, vehicle, anything that needs scheduled care.",buttons:[{icon:"mdi:wrench",text:"Open Maintenance panel",appearance:"filled",variant:"brand",tap_action:{action:"navigate",navigation_path:"/maintenance-supporter"}},{icon:"mdi:plus",text:"Add object",appearance:"outlined",variant:"brand",tap_action:{action:"fire-dom-event",ll_custom:{type:"maintenance-supporter:add-object"}}}]}]}}function Y(n={},i={}){let e=U(n,i);return{title:"Overview",icon:"mdi:wrench-clock",path:"overview",type:"sections",header:{layout:"responsive",card:e},sidebar:{sections:[{type:"grid",cards:[{type:"heading",heading:"Status",heading_style:"title"},e]}],visibility:[$],content_label:"Tasks",sidebar_label:"Status"},sections:[p({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:["overdue","triggered","due_soon"]})]}}function F(n,i){let e=new Map;for(let a of n){let r=a.object.area_id||null;e.has(r)||e.set(r,[]),e.get(r).push(a)}let s=Array.from(e.keys()).filter(a=>a!==null);s.sort((a,r)=>{let d=i[a]?.name||a,c=i[r]?.name||r;return d.localeCompare(c)});let t=[];for(let a of s){let r=e.get(a);if(r.length===0)continue;let d=i[a];t.push({title:d?.name||a,icon:d?.icon||"mdi:floor-plan",path:`area-${a}`,type:"sections",sections:[p({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:r.map(c=>c.object.name)})]})}let o=e.get(null);return o&&o.length>0&&t.push({title:"Unassigned",icon:"mdi:help-circle-outline",path:"unassigned",type:"sections",sections:[p({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:o.map(a=>a.object.name)})]}),t}function K(n){let i=new Set;for(let s of n)for(let t of s.tasks||[])t.status&&i.add(t.status);let e=[];for(let s of B)i.has(s.status)&&e.push({title:s.title,icon:s.icon,path:s.path,type:"sections",sections:[p({type:"custom:maintenance-supporter-card",show_header:!1,filter_status:[s.status]})]});return e}function q(n,i,e){let s=new Map;for(let r of n){let d=r.object.area_id,c=d&&i[d]?.floor_id||null;s.has(c)||s.set(c,[]),s.get(c).push(r)}let t=Array.from(s.keys()).filter(r=>r!==null);t.sort((r,d)=>{let c=e[r],l=e[d],u=c?.level??0,m=l?.level??0;return u!==m?u-m:(c?.name||r).localeCompare(l?.name||d)});let o=[];for(let r of t){let d=s.get(r);if(d.length===0)continue;let c=e[r];o.push({title:c?.name||r,icon:c?.icon||"mdi:home-floor-1",path:`floor-${r}`,type:"sections",sections:[p({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:d.map(l=>l.object.name)})]})}let a=s.get(null);return a&&a.length>0&&o.push({title:"Other",icon:"mdi:help-circle-outline",path:"other",type:"sections",sections:[p({type:"custom:maintenance-supporter-card",show_header:!1,filter_objects:a.map(r=>r.object.name)})]}),o}function z(n){return[{title:"Week",icon:"mdi:calendar-week",path:"cal-7",window_days:7},{title:"Fortnight",icon:"mdi:calendar-week-begin",path:"cal-14",window_days:14},{title:"Month",icon:"mdi:calendar-month",path:"cal-30",window_days:30},{title:"Year",icon:"mdi:calendar-clock",path:"cal-365",window_days:365}].map(e=>({title:e.title,icon:e.icon,path:e.path,type:"panel",cards:[{type:"custom:maintenance-supporter-calendar-card",window_days:e.window_days,show_window_chips:!1,show_useLine truncated
<style>
:host, .editor { display: block; padding: 16px 0; }
label { display: block; font-weight: 500; margin-bottom: 8px; }
@@ -22,4 +22,4 @@ import{c as y}from"./chunks/chunk-3YPUSKYO.js";var x="maintenance-supporter",G=`
The "Overview" view is always first. Empty groups are skipped.
</div>
</div>
`;let t=this.querySelector("#group-by");t?.addEventListener("change",()=>{let o={...this._config,group_by:t.value};this._config=o,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:o},bubbles:!0,composed:!0}))})}},E="maintenance-supporter-section",Q=`ll-strategy-section-${E}`,b=class extends HTMLElement{static async generate(i,e){let s={type:"custom:maintenance-supporter-card",show_header:!1};i?.filter_status?.length&&(s.filter_status=i.filter_status),i?.max_items&&(s.max_items=i.max_items),i?.filter_due_min_days!==void 0&&(s.filter_due_min_days=i.filter_due_min_days),i?.filter_due_max_days!==void 0&&(s.filter_due_max_days=i.filter_due_max_days);let t=i?.filter_objects;if(i?.area_id&&!t)try{t=((await e.connection.sendMessagePromise({type:"maintenance_supporter/objects"})).objects||[]).filter(r=>r.object.area_id===i.area_id).map(r=>r.object.name)}catch{}t&&t.length>0&&(s.filter_objects=t);let o=[];return i?.title&&o.push({type:"heading",heading:i.title,heading_style:"title"}),o.push(s),{type:"grid",cards:o}}},D="2.3.6",X="[maintenance-supporter]";function R(n,i){try{return customElements.get(n)?"skipped":(customElements.define(n,i),"ok")}catch(e){return`ERROR: ${String(e)}`}}var P={editor:R(I,k),section:R(Q,b)};console.log(`${X} strategy bundle v${D} loaded \u2014 registrations:`,P,"\u2014 run maintenanceSupporterDiagnose() in console for full state");window.maintenanceSupporterDiagnose=async()=>{let n=G,i=[];try{let e=document.querySelector("home-assistant");e?.hass&&(i=(await e.hass.connection.sendMessagePromise({type:"lovelace/resources"})).resources||[])}catch(e){i=[{url:`<error: ${String(e)}>`}]}return{version:D,el_registered:!!customElements.get(n),el_class_excerpt:customElements.get(n)?.toString().slice(0,100)||null,customStrategies_dashboard:window.customStrategies?.filter(e=>e.strategyType==="dashboard").map(e=>e.type)||[],scriptTagsInPage:Array.from(document.querySelectorAll("script[type=module]")).map(e=>e.src).filter(e=>/maint|strateg/i.test(e)),lovelaceResources_matching:i.filter(e=>/maint|strateg/i.test(e.url||"")).map(e=>e.url),registrations:P}};var w=class extends HTMLElement{static async generate(i,e){return await import("./chunks/vacation-section-card-EWJTZM6N.js"),{type:"grid",cards:[{type:"custom:maintenance-vacation-section-card",title:i?.title}]}}},v=class extends HTMLElement{static async generate(i,e){return await import("./chunks/budget-section-card-LN7A7YQB.js"),{type:"grid",cards:[{type:"custom:maintenance-budget-section-card",title:i?.title}]}}},C=class extends HTMLElement{static async generate(i,e){return await import("./chunks/groups-section-card-F3GLQTRW.js"),{type:"grid",cards:[{type:"custom:maintenance-groups-section-card",title:i?.title}]}}},j="ll-strategy-section-maintenance-supporter-vacation",A="ll-strategy-section-maintenance-supporter-budget",O="ll-strategy-section-maintenance-supporter-groups";customElements.get(j)||customElements.define(j,w);customElements.get(A)||customElements.define(A,v);customElements.get(O)||customElements.define(O,C);var te={[E]:b,"maintenance-supporter-vacation":w,"maintenance-supporter-budget":v,"maintenance-supporter-groups":C},h=null;function J(){return h||(h=import("./chunks/dialog-mount-BLDNA6JI.js").catch(n=>{throw h=null,n})),h}var L="_msSupporterLlCustomBound";function m(n){history.pushState(null,"",n),window.dispatchEvent(new CustomEvent("location-changed"))}function Z(){let n=window;n[L]||(n[L]=!0,document.addEventListener("ll-custom",i=>{let e=i.detail;if(!e||typeof e!="object")return;let s=e.ll_custom,t=s&&typeof s=="object"?s:e;if(!t||typeof t.type!="string"||!t.type.startsWith("maintenance-supporter:"))return;let o=t.type.slice(22);if(o==="deep-link"&&typeof t.path=="string"){m(t.path);return}(async()=>{let a;try{a=await J()}catch{m("/maintenance-supporter");return}if(o==="add-object"){if(a.openCreateObjectDialog())return;m("/maintenance-supporter?ms_action=add_object");return}if(o==="open-task"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"){if(a.openTaskQuickActions(t.entry_id,t.task_id)||a.openEditTaskDialog(t.entry_id,t.task_id))return;m(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}&task_id=${encodeURIComponent(t.task_id)}`);return}if(o==="open-object"&&typeof t.entry_id=="string"){if(a.openObjectQuickActions(t.entry_id))return;m(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}`);return}if(o==="edit-history"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"&&typeof t.original_timestamp=="string"){let d=document.querySelector("home-assistant")?.hass;if(!d)return;try{let u=(await d.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:t.entry_id})).tasks?.find(p=>p.id===t.task_id)?.history?.find(p=>p.timestamp===t.original_timestamp);if(!u)return;a.openHistoryEditDialog({entry_id:t.entry_id,task_id:t.task_id,original_timestamp:t.original_timestamp,type:u.type||"completed",timestamp:u.timestamp||t.original_timestamp,notes:u.notes??n Line truncated
`;let t=this.querySelector("#group-by");t?.addEventListener("change",()=>{let o={...this._config,group_by:t.value};this._config=o,this.dispatchEvent(new CustomEvent("config-changed",{detail:{config:o},bubbles:!0,composed:!0}))})}},E="maintenance-supporter-section",J=`ll-strategy-section-${E}`,b=class extends HTMLElement{static async generate(i,e){let s={type:"custom:maintenance-supporter-card",show_header:!1};i?.filter_status?.length&&(s.filter_status=i.filter_status),i?.max_items&&(s.max_items=i.max_items),i?.filter_due_min_days!==void 0&&(s.filter_due_min_days=i.filter_due_min_days),i?.filter_due_max_days!==void 0&&(s.filter_due_max_days=i.filter_due_max_days);let t=i?.filter_objects;if(i?.area_id&&!t)try{t=((await e.connection.sendMessagePromise({type:"maintenance_supporter/objects"})).objects||[]).filter(r=>r.object.area_id===i.area_id).map(r=>r.object.name)}catch{}t&&t.length>0&&(s.filter_objects=t);let o=[];return i?.title&&o.push({type:"heading",heading:i.title,heading_style:"title"}),o.push(s),{type:"grid",cards:o}}},G="2.3.6",Z="[maintenance-supporter]";function A(n,i){try{return customElements.get(n)?"skipped":(customElements.define(n,i),"ok")}catch(e){return`ERROR: ${String(e)}`}}var V={editor:A(P,k),section:A(J,b)};console.log(`${Z} strategy bundle v${G} loaded \u2014 registrations:`,V,"\u2014 run maintenanceSupporterDiagnose() in console for full state");window.maintenanceSupporterDiagnose=async()=>{let n=H,i=[];try{let e=document.querySelector("home-assistant");e?.hass&&(i=(await e.hass.connection.sendMessagePromise({type:"lovelace/resources"})).resources||[])}catch(e){i=[{url:`<error: ${String(e)}>`}]}return{version:G,el_registered:!!customElements.get(n),el_class_excerpt:customElements.get(n)?.toString().slice(0,100)||null,customStrategies_dashboard:window.customStrategies?.filter(e=>e.strategyType==="dashboard").map(e=>e.type)||[],scriptTagsInPage:Array.from(document.querySelectorAll("script[type=module]")).map(e=>e.src).filter(e=>/maint|strateg/i.test(e)),lovelaceResources_matching:i.filter(e=>/maint|strateg/i.test(e.url||"")).map(e=>e.url),registrations:V}};var w=class extends HTMLElement{static async generate(i,e){return await import("./chunks/vacation-section-card-FJ6A6EZO.js"),{type:"grid",cards:[{type:"custom:maintenance-vacation-section-card",title:i?.title}]}}},v=class extends HTMLElement{static async generate(i,e){return await import("./chunks/budget-section-card-ZS5NH6UV.js"),{type:"grid",cards:[{type:"custom:maintenance-budget-section-card",title:i?.title}]}}},C=class extends HTMLElement{static async generate(i,e){return await import("./chunks/groups-section-card-45HWIJPJ.js"),{type:"grid",cards:[{type:"custom:maintenance-groups-section-card",title:i?.title}]}}},O="ll-strategy-section-maintenance-supporter-vacation",L="ll-strategy-section-maintenance-supporter-budget",x="ll-strategy-section-maintenance-supporter-groups";customElements.get(O)||customElements.define(O,w);customElements.get(L)||customElements.define(L,v);customElements.get(x)||customElements.define(x,C);var re={[E]:b,"maintenance-supporter-vacation":w,"maintenance-supporter-budget":v,"maintenance-supporter-groups":C},h=null;function ee(){return h||(h=import("./chunks/dialog-mount-MDGILK6F.js").catch(n=>{throw h=null,n})),h}var I="_msSupporterLlCustomBound";function g(n){history.pushState(null,"",n),window.dispatchEvent(new CustomEvent("location-changed"))}function te(){let n=window;n[I]||(n[I]=!0,document.addEventListener("ll-custom",i=>{let e=i.detail;if(!e||typeof e!="object")return;let s=e.ll_custom,t=s&&typeof s=="object"?s:e;if(!t||typeof t.type!="string"||!t.type.startsWith("maintenance-supporter:"))return;let o=t.type.slice(22);if(o==="deep-link"&&typeof t.path=="string"){g(t.path);return}(async()=>{let a;try{a=await ee()}catch{g("/maintenance-supporter");return}if(o==="add-object"){if(a.openCreateObjectDialog())return;g("/maintenance-supporter?ms_action=add_object");return}if(o==="open-task"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"){if(a.openTaskQuickActions(t.entry_id,t.task_id)||a.openEditTaskDialog(t.entry_id,t.task_id))return;g(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}&task_id=${encodeURIComponent(t.task_id)}`);return}if(o==="open-object"&&typeof t.entry_id=="string"){if(a.openObjectQuickActions(t.entry_id))return;g(`/maintenance-supporter?entry_id=${encodeURIComponent(t.entry_id)}`);return}if(o==="edit-history"&&typeof t.entry_id=="string"&&typeof t.task_id=="string"&&typeof t.original_timestamp=="string"){let d=document.querySelector("home-assistant")?.hass;if(!d)return;try{let l=(await d.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:t.entry_id})).tasks?.find(m=>m.id===t.task_id),u=l?.history?.find(m=>m.timestamp===t.original_timestamp);if(!u)return;a.openHistoryEditDialog({entry_id:t.entry_id,task_id:t.task_id,original_timestamp:t.original_timestamp,type:u.type||"completed",timestamp:u.timestamp||t.original_timestamp,notes:u.n Line truncated