329 files

This commit is contained in:
Home Assistant Version Control
2026-08-06 13:56:25 +00:00
parent 0df89406fa
commit 7afe7add1d
330 changed files with 13098 additions and 5942 deletions
@@ -20,6 +20,7 @@ from ..const import (
STRATEGY_URL,
VENDOR_URL,
)
from ..panel import _async_file_hash
_LOGGER = logging.getLogger(__name__)
@@ -93,10 +94,24 @@ async def async_register_card(hass: HomeAssistant) -> None:
# whenDefined race even under heavy HACS plugin load (the bundle is loaded
# lazily by the shim). The heavy bundle's static path stays mounted above
# so the shim's import() can fetch it on demand.
#
# Every URL carries a content-hash query (issue #124): these module
# scripts are served without Cache-Control, so browsers cache them
# heuristically. After an update a stale cached strategy entry kept
# importing chunk names the update had deleted → 404 → the strategy
# dashboard rendered nothing until a hard refresh (and a rollback
# "fixed" it by putting the old chunks back). A hash-busted URL changes
# with the file, so each release starts from a fresh entry point; the
# chunk layer below is content-hashed already. Same trick the panel has
# used since #112.
extra = hass.data.setdefault(DATA_EXTRA_MODULE_URL, set())
extra.add(CARD_URL)
extra.add(STRATEGY_SHIM_URL)
extra.add(CALENDAR_CARD_URL)
for url, filename in (
(CARD_URL, "maintenance-card.js"),
(STRATEGY_SHIM_URL, "maintenance-strategy-shim.js"),
(CALENDAR_CARD_URL, "maintenance-calendar-card.js"),
):
digest = await _async_file_hash(hass, frontend_dir / filename)
extra.add(f"{url}?v={digest}")
hass.data[_CARD_REGISTERED_KEY] = True
_LOGGER.debug(
@@ -716,6 +716,7 @@
"series_end_count_label": "Počet opakování",
"series_end_until_label": "Datum konce",
"parts_section": "Díly a spotřební materiál",
"parts_inventory_value": "Hodnota zásob",
"part_add": "Přidat díl",
"part_name": "Název",
"part_vendor": "Výrobce",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Brzy",
"battery_fleet_status_ok": "V pořádku",
"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",
"battery_fleet_sort_name": "Řadit podle názvu",
"battery_fleet_sort_urgency": "Řadit podle naléhavosti",
"battery_fleet_mark_recharged": "Označit jako nabitou",
"battery_fleet_sparkline_hint": "Stav baterie za posledních 30 dní — tečkovaně: projekce k prahu vybití",
"battery_fleet_filter_type": "Zobrazit pouze tento typ baterie",
"battery_fleet_record_replacement": "Stav poskočil kolem {date} — zaznamenat tuto výměnu do Battery Notes",
"battery_fleet_total": "Sledováno baterií: {n}",
"battery_fleet_setup_button": "Flotila baterií",
"battery_fleet_setup_done": "Flotila baterií nastavena — jeden úkol sleduje všechny.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Antal gange",
"series_end_until_label": "Slutdato",
"parts_section": "Dele & forbrugsvarer",
"parts_inventory_value": "Lagerværdi",
"part_add": "Tilføj del",
"part_name": "Navn",
"part_vendor": "Producent",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I orden",
"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",
"battery_fleet_sort_name": "Sortér efter navn",
"battery_fleet_sort_urgency": "Sortér efter hastende grad",
"battery_fleet_mark_recharged": "Markér som genopladet",
"battery_fleet_sparkline_hint": "Batteriniveau de seneste 30 dage — stiplet: fremskrivning ned til lav-tærsklen",
"battery_fleet_filter_type": "Vis kun denne batteritype",
"battery_fleet_record_replacement": "Niveauet sprang omkring {date} — registrér denne udskiftning i Battery Notes",
"battery_fleet_total": "{n} batterier overvåges",
"battery_fleet_setup_button": "Batteriflåde",
"battery_fleet_setup_done": "Batteriflåde opsat — én opgave følger alle batterier.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Anzahl",
"series_end_until_label": "Enddatum",
"parts_section": "Teile & Verbrauchsmaterial",
"parts_inventory_value": "Lagerwert",
"part_add": "Teil hinzufügen",
"part_name": "Name",
"part_vendor": "Hersteller",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Bald",
"battery_fleet_status_ok": "In Ordnung",
"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",
"battery_fleet_sort_name": "Nach Name sortieren",
"battery_fleet_sort_urgency": "Nach Dringlichkeit sortieren",
"battery_fleet_mark_recharged": "Als aufgeladen markieren",
"battery_fleet_sparkline_hint": "Batteriestand der letzten 30 Tage — gepunktet: Prognose bis zur Low-Schwelle",
"battery_fleet_filter_type": "Nur diesen Batterietyp anzeigen",
"battery_fleet_record_replacement": "Der Batteriestand ist um den {date} gesprungen — diesen Wechsel in Battery Notes nachtragen",
"battery_fleet_total": "{n} Batterien überwacht",
"battery_fleet_setup_button": "Batterie-Flotte",
"battery_fleet_setup_done": "Batterie-Flotte eingerichtet — ein Task überwacht alle Batterien.",
@@ -722,6 +722,7 @@
"series_end_count_label": "Number of times",
"series_end_until_label": "End date",
"parts_section": "Parts & consumables",
"parts_inventory_value": "Inventory value",
"part_add": "Add part",
"part_name": "Name",
"part_vendor": "Manufacturer",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Soon",
"battery_fleet_status_ok": "Healthy",
"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",
"battery_fleet_sort_name": "Sort by name",
"battery_fleet_sort_urgency": "Sort by urgency",
"battery_fleet_mark_recharged": "Mark as recharged",
"battery_fleet_sparkline_hint": "Battery level over the last 30 days — dotted: projected until the low threshold",
"battery_fleet_filter_type": "Show only this battery type",
"battery_fleet_record_replacement": "The level jumped around {date} — record this replacement in Battery Notes",
"battery_fleet_total": "{n} batteries tracked",
"battery_fleet_setup_button": "Battery fleet",
"battery_fleet_setup_done": "Battery fleet set up — one task tracks all your batteries.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Número de veces",
"series_end_until_label": "Fecha de fin",
"parts_section": "Piezas y consumibles",
"parts_inventory_value": "Valor del inventario",
"part_add": "Añadir pieza",
"part_name": "Nombre",
"part_vendor": "Fabricante",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Pronto",
"battery_fleet_status_ok": "Correcta",
"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",
"battery_fleet_sort_name": "Ordenar por nombre",
"battery_fleet_sort_urgency": "Ordenar por urgencia",
"battery_fleet_mark_recharged": "Marcar como recargada",
"battery_fleet_sparkline_hint": "Nivel de batería de los últimos 30 días — punteado: proyección hasta el umbral bajo",
"battery_fleet_filter_type": "Mostrar solo este tipo de pila",
"battery_fleet_record_replacement": "El nivel dio un salto hacia el {date} — registrar esta sustitución en Battery Notes",
"battery_fleet_total": "{n} baterías monitorizadas",
"battery_fleet_setup_button": "Flota de baterías",
"battery_fleet_setup_done": "Flota de baterías configurada — una tarea controla todas.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Kertojen määrä",
"series_end_until_label": "Päättymispäivä",
"parts_section": "Osat ja tarvikkeet",
"parts_inventory_value": "Varaston arvo",
"part_add": "Lisää osa",
"part_name": "Nimi",
"part_vendor": "Valmistaja",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Pian",
"battery_fleet_status_ok": "Kunnossa",
"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",
"battery_fleet_sort_name": "Lajittele nimen mukaan",
"battery_fleet_sort_urgency": "Lajittele kiireellisyyden mukaan",
"battery_fleet_mark_recharged": "Merkitse ladatuksi",
"battery_fleet_sparkline_hint": "Akun varaustaso viimeisten 30 päivän ajalta — pisteviiva: ennuste alarajaan asti",
"battery_fleet_filter_type": "Näytä vain tämä paristotyyppi",
"battery_fleet_record_replacement": "Varaustaso hyppäsi noin {date} — kirjaa tämä vaihto Battery Notesiin",
"battery_fleet_total": "{n} paristoa seurannassa",
"battery_fleet_setup_button": "Paristokanta",
"battery_fleet_setup_done": "Paristokanta määritetty — yksi tehtävä seuraa kaikkia.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Nombre de fois",
"series_end_until_label": "Date de fin",
"parts_section": "Pièces & consommables",
"parts_inventory_value": "Valeur du stock",
"part_add": "Ajouter une pièce",
"part_name": "Nom",
"part_vendor": "Fabricant",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Bientôt",
"battery_fleet_status_ok": "Bon état",
"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",
"battery_fleet_sort_name": "Trier par nom",
"battery_fleet_sort_urgency": "Trier par urgence",
"battery_fleet_mark_recharged": "Marquer comme rechargée",
"battery_fleet_sparkline_hint": "Niveau de batterie des 30 derniers jours — pointillé : projection jusqu'au seuil bas",
"battery_fleet_filter_type": "Afficher uniquement ce type de pile",
"battery_fleet_record_replacement": "Le niveau a bondi vers le {date} — enregistrer ce remplacement dans Battery Notes",
"battery_fleet_total": "{n} piles suivies",
"battery_fleet_setup_button": "Parc de piles",
"battery_fleet_setup_done": "Parc de piles configuré — une tâche suit toutes vos piles.",
@@ -716,6 +716,7 @@
"series_end_count_label": "कितनी बार",
"series_end_until_label": "समाप्ति तिथि",
"parts_section": "पुर्ज़े और उपभोग्य",
"parts_inventory_value": "इन्वेंट्री मूल्य",
"part_add": "पुर्ज़ा जोड़ें",
"part_name": "नाम",
"part_vendor": "निर्माता",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "जल्द",
"battery_fleet_status_ok": "ठीक",
"battery_fleet_predicted_on": "{date} के आसपास अपेक्षित",
"battery_fleet_predicted_trend": "इस बैटरी के डिस्चार्ज रुझान से अनुमानित: लगभग {date} ({confidence})",
"battery_fleet_rechargeable": "रिचार्जेबल: बदलने के बजाय चार्ज करें — खरीदारी सूची में कभी नहीं",
"battery_fleet_sort_name": "नाम के अनुसार क्रमबद्ध करें",
"battery_fleet_sort_urgency": "तात्कालिकता के अनुसार क्रमबद्ध करें",
"battery_fleet_mark_recharged": "रिचार्ज हो गई के रूप में चिह्नित करें",
"battery_fleet_sparkline_hint": "पिछले 30 दिनों का बैटरी स्तर — बिंदीदार: लो थ्रेशोल्ड तक का अनुमान",
"battery_fleet_filter_type": "केवल यही बैटरी प्रकार दिखाएँ",
"battery_fleet_record_replacement": "स्तर लगभग {date} को उछला — इस बदलाव को Battery Notes में दर्ज करें",
"battery_fleet_total": "{n} बैटरियाँ ट्रैक की गईं",
"battery_fleet_setup_button": "बैटरी फ्लीट",
"battery_fleet_setup_done": "बैटरी फ्लीट सेट — एक कार्य सभी बैटरियों को ट्रैक करता है।",
@@ -722,6 +722,7 @@
"series_end_count_label": "Alkalmak száma",
"series_end_until_label": "Befejezés dátuma",
"parts_section": "Alkatrészek és fogyóeszközök",
"parts_inventory_value": "Készletérték",
"part_add": "Alkatrész hozzáadása",
"part_name": "Név",
"part_vendor": "Gyártó",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Hamarosan",
"battery_fleet_status_ok": "Rendben",
"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",
"battery_fleet_sort_name": "Rendezés név szerint",
"battery_fleet_sort_urgency": "Rendezés sürgősség szerint",
"battery_fleet_mark_recharged": "Megjelölés feltöltöttként",
"battery_fleet_sparkline_hint": "Akkumulátorszint az elmúlt 30 napban — pontozott: előrejelzés az alacsony küszöbig",
"battery_fleet_filter_type": "Csak ez az elemtípus megjelenítése",
"battery_fleet_record_replacement": "A szint {date} körül megugrott — rögzítse ezt a cserét a Battery Notes-ban",
"battery_fleet_total": "{n} elem követve",
"battery_fleet_setup_button": "Elemflotta",
"battery_fleet_setup_done": "Elemflotta beállítva — egyetlen feladat követi az összes elemet.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Numero di volte",
"series_end_until_label": "Data di fine",
"parts_section": "Ricambi e consumabili",
"parts_inventory_value": "Valore delle scorte",
"part_add": "Aggiungi ricambio",
"part_name": "Nome",
"part_vendor": "Produttore",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "A breve",
"battery_fleet_status_ok": "A posto",
"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",
"battery_fleet_sort_name": "Ordina per nome",
"battery_fleet_sort_urgency": "Ordina per urgenza",
"battery_fleet_mark_recharged": "Segna come ricaricata",
"battery_fleet_sparkline_hint": "Livello batteria degli ultimi 30 giorni — tratteggiato: proiezione fino alla soglia di batteria scarica",
"battery_fleet_filter_type": "Mostra solo questo tipo di batteria",
"battery_fleet_record_replacement": "Il livello è balzato intorno al {date} — registra questa sostituzione in Battery Notes",
"battery_fleet_total": "{n} batterie monitorate",
"battery_fleet_setup_button": "Parco batterie",
"battery_fleet_setup_done": "Parco batterie configurato — un'attività monitora tutte.",
@@ -716,6 +716,7 @@
"series_end_count_label": "回数",
"series_end_until_label": "終了日",
"parts_section": "部品・消耗品",
"parts_inventory_value": "在庫金額",
"part_add": "部品を追加",
"part_name": "名前",
"part_vendor": "メーカー",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "まもなく",
"battery_fleet_status_ok": "良好",
"battery_fleet_predicted_on": "{date} 頃の見込み",
"battery_fleet_predicted_trend": "この電池の放電傾向から予測: {date} 頃 ({confidence})",
"battery_fleet_rechargeable": "充電式:交換ではなく充電——買い物リストには載りません",
"battery_fleet_sort_name": "名前順に並べ替え",
"battery_fleet_sort_urgency": "緊急度順に並べ替え",
"battery_fleet_mark_recharged": "充電済みにする",
"battery_fleet_sparkline_hint": "過去30日間のバッテリー残量——点線:低残量しきい値までの予測",
"battery_fleet_filter_type": "この電池タイプのみ表示",
"battery_fleet_record_replacement": "{date}頃に残量が急上昇——この交換をBattery Notesに記録する",
"battery_fleet_total": "{n} 個の電池を監視",
"battery_fleet_setup_button": "電池フリート",
"battery_fleet_setup_done": "電池フリートを設定 — 1つのタスクで全電池を管理。",
@@ -722,6 +722,7 @@
"series_end_count_label": "횟수",
"series_end_until_label": "종료일",
"parts_section": "부품 및 소모품",
"parts_inventory_value": "재고 가치",
"part_add": "부품 추가",
"part_name": "이름",
"part_vendor": "제조사",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "곧",
"battery_fleet_status_ok": "정상",
"battery_fleet_predicted_on": "{date}쯤 예상",
"battery_fleet_predicted_trend": "이 배터리의 방전 추세로 예측: 약 {date} ({confidence})",
"battery_fleet_rechargeable": "충전식: 교체 대신 충전 — 쇼핑 목록에 오르지 않습니다",
"battery_fleet_sort_name": "이름순 정렬",
"battery_fleet_sort_urgency": "긴급도순 정렬",
"battery_fleet_mark_recharged": "충전 완료로 표시",
"battery_fleet_sparkline_hint": "지난 30일간의 배터리 잔량 — 점선: 낮음 임계값까지의 예측",
"battery_fleet_filter_type": "이 배터리 유형만 표시",
"battery_fleet_record_replacement": "{date}쯤 잔량이 급상승했습니다 — 이 교체를 Battery Notes에 기록",
"battery_fleet_total": "배터리 {n}개 추적 중",
"battery_fleet_setup_button": "배터리 플릿",
"battery_fleet_setup_done": "배터리 플릿이 설정되었습니다 — 하나의 작업이 모든 배터리를 추적합니다.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Antall ganger",
"series_end_until_label": "Sluttdato",
"parts_section": "Deler & forbruksvarer",
"parts_inventory_value": "Lagerverdi",
"part_add": "Legg til del",
"part_name": "Navn",
"part_vendor": "Produsent",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I orden",
"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",
"battery_fleet_sort_name": "Sorter etter navn",
"battery_fleet_sort_urgency": "Sorter etter hastegrad",
"battery_fleet_mark_recharged": "Merk som oppladet",
"battery_fleet_sparkline_hint": "Batterinivå de siste 30 dagene — stiplet: fremskrevet ned til lavnivåterskelen",
"battery_fleet_filter_type": "Vis bare denne batteritypen",
"battery_fleet_record_replacement": "Nivået hoppet rundt {date} — registrer dette byttet i Battery Notes",
"battery_fleet_total": "{n} batterier spores",
"battery_fleet_setup_button": "Batteriflåte",
"battery_fleet_setup_done": "Batteriflåte satt opp — én oppgave følger alle batteriene.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Aantal keer",
"series_end_until_label": "Einddatum",
"parts_section": "Onderdelen & verbruiksartikelen",
"parts_inventory_value": "Voorraadwaarde",
"part_add": "Onderdeel toevoegen",
"part_name": "Naam",
"part_vendor": "Fabrikant",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Binnenkort",
"battery_fleet_status_ok": "In orde",
"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",
"battery_fleet_sort_name": "Sorteren op naam",
"battery_fleet_sort_urgency": "Sorteren op urgentie",
"battery_fleet_mark_recharged": "Markeren als opgeladen",
"battery_fleet_sparkline_hint": "Batterijniveau van de afgelopen 30 dagen — gestippeld: prognose tot de lage drempel",
"battery_fleet_filter_type": "Alleen dit batterijtype tonen",
"battery_fleet_record_replacement": "Het niveau maakte rond {date} een sprong — deze vervanging vastleggen in Battery Notes",
"battery_fleet_total": "{n} batterijen gevolgd",
"battery_fleet_setup_button": "Batterijvloot",
"battery_fleet_setup_done": "Batterijvloot ingesteld — één taak volgt al je batterijen.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Liczba razy",
"series_end_until_label": "Data końcowa",
"parts_section": "Części i materiały",
"parts_inventory_value": "Wartość zapasów",
"part_add": "Dodaj część",
"part_name": "Nazwa",
"part_vendor": "Producent",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Wkrótce",
"battery_fleet_status_ok": "W porządku",
"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",
"battery_fleet_sort_name": "Sortuj według nazwy",
"battery_fleet_sort_urgency": "Sortuj według pilności",
"battery_fleet_mark_recharged": "Oznacz jako naładowaną",
"battery_fleet_sparkline_hint": "Poziom baterii z ostatnich 30 dni — kropkowana linia: prognoza do progu niskiego poziomu",
"battery_fleet_filter_type": "Pokaż tylko ten typ baterii",
"battery_fleet_record_replacement": "Poziom skoczył około {date} — zapisz tę wymianę w Battery Notes",
"battery_fleet_total": "Śledzone baterie: {n}",
"battery_fleet_setup_button": "Flota baterii",
"battery_fleet_setup_done": "Flota baterii skonfigurowana — jedno zadanie śledzi wszystkie.",
@@ -722,6 +722,7 @@
"series_end_count_label": "Número de vezes",
"series_end_until_label": "Data final",
"parts_section": "Peças e consumíveis",
"parts_inventory_value": "Valor do estoque",
"part_add": "Adicionar peça",
"part_name": "Nome",
"part_vendor": "Fabricante",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Em breve",
"battery_fleet_status_ok": "Em bom estado",
"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",
"battery_fleet_sort_name": "Ordenar por nome",
"battery_fleet_sort_urgency": "Ordenar por urgência",
"battery_fleet_mark_recharged": "Marcar como recarregada",
"battery_fleet_sparkline_hint": "Nível da bateria dos últimos 30 dias — pontilhado: projeção até o limite baixo",
"battery_fleet_filter_type": "Mostrar apenas este tipo de pilha",
"battery_fleet_record_replacement": "O nível deu um salto por volta de {date} — registrar esta substituição no Battery Notes",
"battery_fleet_total": "{n} baterias acompanhadas",
"battery_fleet_setup_button": "Frota de baterias",
"battery_fleet_setup_done": "Frota de baterias configurada — uma única tarefa acompanha todas as suas baterias.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Número de vezes",
"series_end_until_label": "Data final",
"parts_section": "Peças e consumíveis",
"parts_inventory_value": "Valor do stock",
"part_add": "Adicionar peça",
"part_name": "Nome",
"part_vendor": "Fabricante",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Em breve",
"battery_fleet_status_ok": "Em bom estado",
"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",
"battery_fleet_sort_name": "Ordenar por nome",
"battery_fleet_sort_urgency": "Ordenar por urgência",
"battery_fleet_mark_recharged": "Marcar como recarregada",
"battery_fleet_sparkline_hint": "Nível da bateria dos últimos 30 dias — pontilhado: projeção até ao limiar baixo",
"battery_fleet_filter_type": "Mostrar apenas este tipo de pilha",
"battery_fleet_record_replacement": "O nível deu um salto por volta de {date} — registar esta substituição no Battery Notes",
"battery_fleet_total": "{n} baterias monitorizadas",
"battery_fleet_setup_button": "Frota de baterias",
"battery_fleet_setup_done": "Frota de baterias configurada — uma tarefa acompanha todas.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Число раз",
"series_end_until_label": "Дата окончания",
"parts_section": "Детали и расходники",
"parts_inventory_value": "Стоимость запасов",
"part_add": "Добавить деталь",
"part_name": "Название",
"part_vendor": "Производитель",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Скоро",
"battery_fleet_status_ok": "В норме",
"battery_fleet_predicted_on": "Ожидается примерно {date}",
"battery_fleet_predicted_trend": "Прогноз по тренду разряда этой батареи: примерно {date} ({confidence})",
"battery_fleet_rechargeable": "Аккумулятор: заряжается, а не заменяется — никогда не попадает в список покупок",
"battery_fleet_sort_name": "Сортировать по имени",
"battery_fleet_sort_urgency": "Сортировать по срочности",
"battery_fleet_mark_recharged": "Отметить как заряженную",
"battery_fleet_sparkline_hint": "Уровень заряда за последние 30 дней — пунктир: прогноз до порога разряда",
"battery_fleet_filter_type": "Показать только этот тип батарей",
"battery_fleet_record_replacement": "Уровень резко вырос примерно {date} — записать эту замену в Battery Notes",
"battery_fleet_total": "Отслеживается батарей: {n}",
"battery_fleet_setup_button": "Парк батарей",
"battery_fleet_setup_done": "Парк батарей настроен — одна задача следит за всеми.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Antal gånger",
"series_end_until_label": "Slutdatum",
"parts_section": "Delar & förbrukning",
"parts_inventory_value": "Lagervärde",
"part_add": "Lägg till del",
"part_name": "Namn",
"part_vendor": "Tillverkare",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Snart",
"battery_fleet_status_ok": "I ordning",
"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",
"battery_fleet_sort_name": "Sortera efter namn",
"battery_fleet_sort_urgency": "Sortera efter angelägenhet",
"battery_fleet_mark_recharged": "Markera som uppladdad",
"battery_fleet_sparkline_hint": "Batterinivå de senaste 30 dagarna — prickad: prognos ner till lågnivåtröskeln",
"battery_fleet_filter_type": "Visa endast denna batterityp",
"battery_fleet_record_replacement": "Nivån hoppade omkring {date} — registrera detta byte i Battery Notes",
"battery_fleet_total": "{n} batterier spåras",
"battery_fleet_setup_button": "Batteriflotta",
"battery_fleet_setup_done": "Batteriflotta konfigurerad — en uppgift följer alla batterier.",
@@ -722,6 +722,7 @@
"series_end_count_label": "Tekrar sayısı",
"series_end_until_label": "Bitiş tarihi",
"parts_section": "Parçalar ve sarf malzemeleri",
"parts_inventory_value": "Stok değeri",
"part_add": "Parça ekle",
"part_name": "Ad",
"part_vendor": "Üretici",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Yakında",
"battery_fleet_status_ok": "İyi durumda",
"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",
"battery_fleet_sort_name": "Ada göre sırala",
"battery_fleet_sort_urgency": "Aciliyete göre sırala",
"battery_fleet_mark_recharged": "Şarj edildi olarak işaretle",
"battery_fleet_sparkline_hint": "Son 30 günün pil seviyesi — noktalı: düşük eşiğe kadar projeksiyon",
"battery_fleet_filter_type": "Yalnızca bu pil türünü göster",
"battery_fleet_record_replacement": "Seviye {date} civarında sıçradı — bu değişimi Battery Notes'a kaydet",
"battery_fleet_total": "{n} pil takip ediliyor",
"battery_fleet_setup_button": "Pil filosu",
"battery_fleet_setup_done": "Pil filosu kuruldu — tek bir görev tüm pillerinizi takip ediyor.",
@@ -716,6 +716,7 @@
"series_end_count_label": "Кількість разів",
"series_end_until_label": "Дата завершення",
"parts_section": "Деталі та витратні матеріали",
"parts_inventory_value": "Вартість запасів",
"part_add": "Додати деталь",
"part_name": "Назва",
"part_vendor": "Виробник",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "Незабаром",
"battery_fleet_status_ok": "У нормі",
"battery_fleet_predicted_on": "Очікується приблизно {date}",
"battery_fleet_predicted_trend": "Прогноз за трендом розряду цієї батареї: приблизно {date} ({confidence})",
"battery_fleet_rechargeable": "Акумулятор: заряджається, а не замінюється — ніколи не потрапляє до списку покупок",
"battery_fleet_sort_name": "Сортувати за назвою",
"battery_fleet_sort_urgency": "Сортувати за терміновістю",
"battery_fleet_mark_recharged": "Позначити як заряджену",
"battery_fleet_sparkline_hint": "Рівень заряду за останні 30 днів — пунктир: прогноз до порогу розряду",
"battery_fleet_filter_type": "Показати лише цей тип батарей",
"battery_fleet_record_replacement": "Рівень різко зріс приблизно {date} — записати цю заміну в Battery Notes",
"battery_fleet_total": "Відстежується батарей: {n}",
"battery_fleet_setup_button": "Парк батарей",
"battery_fleet_setup_done": "Парк батарей налаштовано — одне завдання стежить за всіма.",
@@ -716,6 +716,7 @@
"series_end_count_label": "次数",
"series_end_until_label": "结束日期",
"parts_section": "配件与耗材",
"parts_inventory_value": "库存价值",
"part_add": "添加配件",
"part_name": "名称",
"part_vendor": "制造商",
@@ -820,6 +821,14 @@
"battery_fleet_status_soon": "即将",
"battery_fleet_status_ok": "正常",
"battery_fleet_predicted_on": "预计在 {date} 前后",
"battery_fleet_predicted_trend": "根据此电池的放电趋势预测:约 {date}({confidence})",
"battery_fleet_rechargeable": "可充电电池:充电即可,无需更换——不会出现在购物清单中",
"battery_fleet_sort_name": "按名称排序",
"battery_fleet_sort_urgency": "按紧急程度排序",
"battery_fleet_mark_recharged": "标记为已充电",
"battery_fleet_sparkline_hint": "过去 30 天的电池电量——虚线:外推至低电量阈值",
"battery_fleet_filter_type": "仅显示此电池类型",
"battery_fleet_record_replacement": "电量在 {date} 前后跳升——将此次更换记录到 Battery Notes",
"battery_fleet_total": "已跟踪 {n} 个电池",
"battery_fleet_setup_button": "电池群",
"battery_fleet_setup_done": "电池群已设置 — 一个任务跟踪所有电池。",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.46.0 */
var d="maintenance-supporter",C=`ll-strategy-dashboard-${d}`,L="hui-maintenance-supporter-strategy-editor",D="/maintenance_supporter_strategy/maintenance-dashboard-strategy.js",m=null;function k(){return m||(m=import(D)),m}async function N(){let r=await k();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await k(),document.createElement(L)}static async generate(c,h){return(await N()).generate(c,h)}};function M(){try{customElements.define(C,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===d&&r.strategyType==="dashboard")||w.customStrategies.push({type:d,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,h=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${d}`;function E(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let l of Array.from(i))t.push(l)}return null}function S(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function R(){try{let t=E("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=S(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function _(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let l=e.pop();if(i++,!l)continue;let u=l;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&h.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let b=l.children;if(b)for(let s of Array.from(b))e.push(s)}return n?!0:o?!1:a&&t<3&&R()}let A="/maintenance_supporter_strategy_shim.js",y=0,v=0;function T(){let a=Date.now();a-v<5e3||y>=3||(v=a,y+=1,import(`${A}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function f(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}_()?T():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",f):f(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||f()})}catch{}})();
/*! maintenance_supporter frontend 2.52.0 */
var S="2.52.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
@@ -0,0 +1,125 @@
/*! maintenance_supporter frontend 2.52.0 */
import{a as v}from"./chunk-7IBGRLM5.js";import{a as u,b as o,d as h,e as b,f as y,g as l,h as g,i as r,k as m,t as p}from"./chunk-C6VY6OOC.js";import{a as n}from"./chunk-D4IFN5R3.js";var x=80,s=class extends b{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return this.hass?.language||"en"}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/budget_status"});this._status=t,this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1}catch(t){this._error=p(t,this._lang)}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=parseFloat(this._localMonthly),a=parseFloat(this._localYearly),i={};!isNaN(t)&&t>=0&&(i.budget_monthly=t),!isNaN(a)&&a>=0&&(i.budget_yearly=a),await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/global/update",settings:i}),await this._load()}catch(t){this._error=p(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,a=this._status;if(!a)return o`<ha-card><div class="loading">${r("loading",t)||"Loading\u2026"}</div></ha-card>`;let i=a.currency_symbol||g,_=a.alert_threshold_pct??x,f=[{label:r("budget_monthly",t)||"Monthly",spent:a.monthly_spent||0,budget:a.monthly_budget||0},{label:r("budget_yearly",t)||"Yearly",spent:a.yearly_spent||0,budget:a.yearly_budget||0}];return o`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">💰</span>
<span>${this._config.title||r("settings_budget",t)||"Budget"}</span>
</div>
<span class="currency">${i}</span>
</div>
${this._error?o`<div class="error">${this._error}</div>`:h}
${f.map(e=>{if(!(e.budget>0))return o`
<div class="track spent-only">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ok">${e.spent.toFixed(0)} ${i}</span>
</div>
</div>
`;let d=Math.min(100,Math.max(0,e.spent/e.budget*100)),c=d>=100?"danger":d>=_?"warning":"ok";return o`
<div class="track">
<div class="track-label-row">
<label>${e.label}</label>
<span class="track-numbers ${c}">
${e.spent.toFixed(0)} / ${e.budget.toFixed(0)} ${i}
</span>
</div>
<div class="bar"><div class="bar-fill ${c}" style="width:${d}%"></div></div>
</div>
`})}
${this._isAdmin?o`
<div class="inputs-row">
<div class="input-field">
<label>${r("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>${r("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?r("save",t)||"Save":r("saved",t)||"Saved"}
</button>
<button class="btn link" @click=${this._onDeepLink}>
${r("budget_advanced",t)||"Currency, alerts\u2026"}
</button>
</div>
`:o`
<button class="btn link" @click=${this._onDeepLink}>
${r("budget_open_panel",t)||"Open in panel"}
</button>
`}
</div>
</ha-card>
`}};s.styles=[v,u`
.currency {
font-size: 14px; font-weight: 600;
color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 10px; border-radius: 999px;
}
.track { display: flex; flex-direction: column; gap: 4px; }
.track-label-row {
display: flex; align-items: center; justify-content: space-between;
}
.track-label-row label {
font-size: 12px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.5px;
}
.track-numbers { font-size: 13px; font-weight: 600; }
.track-numbers.ok { color: var(--primary-text-color); }
.track-numbers.warning { color: #ff9800; }
.track-numbers.danger { color: var(--error-color, #f44336); }
.bar {
height: 6px; background: var(--secondary-background-color);
border-radius: 3px; overflow: hidden;
}
.bar-fill { height: 100%; transition: width 0.3s; border-radius: 3px; }
.bar-fill.ok { background: var(--primary-color); }
.bar-fill.warning { background: #ff9800; }
.bar-fill.danger { background: var(--error-color, #f44336); }
.inputs-row {
display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
padding-top: 4px; border-top: 1px solid var(--divider-color);
}
.input-field { display: flex; flex-direction: column; gap: 4px; }
.input-field label {
font-size: 11px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.3px;
}
.input-wrap { position: relative; display: flex; align-items: center; }
.input-wrap input {
flex: 1; padding: 6px 32px 6px 8px; font-size: 13px;
background: var(--secondary-background-color, #2c2c2c);
color: var(--primary-text-color);
border: 1px solid var(--divider-color); border-radius: 6px;
font-family: inherit;
}
.input-suffix {
position: absolute; right: 8px;
color: var(--secondary-text-color); font-size: 13px;
pointer-events: none;
}
.actions { display: flex; gap: 8px; align-items: center; }
`],n([y({attribute:!1})],s.prototype,"hass",2),n([l()],s.prototype,"_config",2),n([l()],s.prototype,"_status",2),n([l()],s.prototype,"_busy",2),n([l()],s.prototype,"_error",2),n([l()],s.prototype,"_localMonthly",2),n([l()],s.prototype,"_localYearly",2),n([l()],s.prototype,"_dirty",2);customElements.get("maintenance-budget-section-card")||customElements.define("maintenance-budget-section-card",s);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Inline monthly + yearly budget editor",preview:!1});export{s as MaintenanceBudgetSectionCard};
@@ -0,0 +1,60 @@
/*! maintenance_supporter frontend 2.52.0 */
import{a as r}from"./chunk-C6VY6OOC.js";var a=r`
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{a};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.52.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};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,137 @@
/*! maintenance_supporter frontend 2.52.0 */
import{a as m}from"./chunk-7IBGRLM5.js";import{a as u,b as s,d as l,e as h,f as g,g as o,i,k as _,t as p}from"./chunk-C6VY6OOC.js";import{a}from"./chunk-D4IFN5R3.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?.language||"en"}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(),_(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>`:l}
${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],v=d.task_refs?.length??0,b=this._editingId===n;return s`
<div class="group-row">
${b?s`
<input class="edit-input" type="text"
.value=${this._editingName}
?disabled=${this._busy}
@input=${c=>{this._editingName=c.target.value}}
@keydown=${c=>this._onKeyDown(c,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">${v}</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>
`:l}
`}
</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=[m,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);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-groups-section-card",name:"Maintenance Supporter \u2014 Groups",description:"Inline group CRUD",preview:!1});export{e as MaintenanceGroupsSectionCard};
@@ -0,0 +1,122 @@
/*! maintenance_supporter frontend 2.52.0 */
import{a as m}from"./chunk-7IBGRLM5.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,t as l}from"./chunk-C6VY6OOC.js";import{a as i}from"./chunk-D4IFN5R3.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 this.hass?.language||"en"}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(),f(this._lang).then(()=>this.requestUpdate()))}async _load(){try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/state"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||"",this._localBuffer=t.buffer_days??7,this._dirty=!1}catch(t){this._error=l(t,this._lang)}}async _toggleEnabled(t){this._busy=!0,this._error="";try{let s=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",enabled:t});this._state=s}catch(s){this._error=l(s,this._lang)}finally{this._busy=!1}}async _save(){if(this._isAdmin){this._busy=!0,this._error="";try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/update",start:this._localStart||null,end:this._localEnd||null,buffer_days:this._localBuffer});this._state=t,this._dirty=!1}catch(t){this._error=l(t,this._lang)}finally{this._busy=!1}}}async _endNow(){if(this._isAdmin&&window.confirm(e("vacation_end_now_confirm",this._lang)||"End vacation immediately?")){this._busy=!0;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/vacation/end_now"});this._state=t,this._localStart=t.start||"",this._localEnd=t.end||""}catch(t){this._error=l(t,this._lang)}finally{this._busy=!1}}}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_vacation"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,s=this._state;if(!s)return n`<ha-card><div class="loading">${e("loading",t)||"Loading\u2026"}</div></ha-card>`;let p=s.is_active===!0,d=s.enabled===!0,u=s.exempt_task_ids?.length??0,b=p?e("vacation_status_active",t)||"Active now":d?e("vacation_status_scheduled",t)||"Scheduled":e("vacation_status_inactive",t)||"Inactive",g=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 ${g}">${b}</span>
</div>
${this._error?n`<div class="error">${this._error}</div>`:c}
${this._isAdmin?n`
<div class="row toggle-row">
<label>${e("enable",t)||"Enable"}</label>
<ha-switch
.checked=${d}
.disabled=${this._busy}
@change=${o=>this._toggleEnabled(o.target.checked)}
></ha-switch>
</div>
<div class="dates-row">
<div class="date-field">
<label>${e("vacation_start",t)||"Start"}</label>
<input type="date" .value=${this._localStart}
?disabled=${this._busy}
@input=${o=>{this._localStart=o.target.value,this._dirty=!0}} />
</div>
<div class="date-field">
<label>${e("vacation_end",t)||"End"}</label>
<input type="date" .value=${this._localEnd}
?disabled=${this._busy}
@input=${o=>{this._localEnd=o.target.value,this._dirty=!0}} />
</div>
<div class="date-field buffer">
<label>${e("vacation_buffer",t)||"Buffer days"}</label>
<input type="number" min="0" max="14"
.value=${String(this._localBuffer)}
?disabled=${this._busy}
@input=${o=>{this._localBuffer=parseInt(o.target.value,10)||0,this._dirty=!0}} />
</div>
</div>
<div class="actions">
<button class="btn ${this._dirty?"primary":"muted"}"
@click=${this._save}
?disabled=${this._busy||!this._dirty}>
<ha-icon icon="${this._dirty?"mdi:content-save":"mdi:check"}"></ha-icon>
${this._dirty?e("save",t)||"Save":e("saved",t)||"Saved"}
</button>
${p?n`<button class="btn"
@click=${this._endNow}
?disabled=${this._busy}>
${e("vacation_end_now",t)||"End now"}
</button>`:c}
${u>0?n`<button class="btn link"
@click=${this._onDeepLink}>
${u} ${e("vacation_exempt_count",t)||"exempt"}
</button>`:n`<button class="btn link"
@click=${this._onDeepLink}>
${e("vacation_advanced",t)||"Advanced\u2026"}
</button>`}
</div>
`:n`
<div class="readonly">
${d&&s.start&&s.end?n`<div>${s.start}${s.end}</div>`:c}
<button class="btn link" @click=${this._onDeepLink}>
${e("vacation_open_panel",t)||"Open in panel"}
</button>
</div>
`}
</div>
</ha-card>
`}};a.styles=[m,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);window.customCards=window.customCards||[];window.customCards.push({type:"maintenance-vacation-section-card",name:"Maintenance Supporter \u2014 Vacation",description:"Inline vacation mode toggle + dates",preview:!1});export{a as MaintenanceVacationSectionCard};
File diff suppressed because one or more lines are too long