158 files

This commit is contained in:
Home Assistant Version Control
2026-08-11 11:40:58 +00:00
parent c6db6ca558
commit 5388267a3e
158 changed files with 11840 additions and 698 deletions
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Obnovit",
"environmental_entity_optional": "Senzor prostředí (volitelný)",
"environmental_entity_helper": "např. sensor.outdoor_temperature — upravuje interval podle podmínek prostředí",
"adaptive_prediction_enabled": "Povolit predikce řízené senzory",
"adaptive_seasonal_enabled": "Povolit sezónní povědomí",
"adaptive_max_interval": "Maximální interval (dny)",
"adaptive_min_interval": "Minimální interval (dny)",
"adaptive_ewa_alpha": "Rychlost učení (alpha)",
"adaptive_enabled": "Povolit adaptivní plánování",
"adaptive_section_title": "Adaptivní plánování",
"environmental_attribute_optional": "Atribut prostředí (volitelný)",
"nfc_tag_id": "ID NFC tagu",
"nfc_linked": "NFC tag propojen",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Opdater",
"environmental_entity_optional": "Miljøsensor (valgfrit)",
"environmental_entity_helper": "f.eks. sensor.outdoor_temperature — justerer intervallet baseret på miljøforhold",
"adaptive_prediction_enabled": "Aktivér sensordrevne forudsigelser",
"adaptive_seasonal_enabled": "Aktivér sæsonbevidsthed",
"adaptive_max_interval": "Maksimumsinterval (dage)",
"adaptive_min_interval": "Minimumsinterval (dage)",
"adaptive_ewa_alpha": "Læringsrate (alfa)",
"adaptive_enabled": "Aktivér adaptiv planlægning",
"adaptive_section_title": "Adaptiv planlægning",
"environmental_attribute_optional": "Miljøattribut (valgfrit)",
"nfc_tag_id": "NFC-tag-ID",
"nfc_linked": "NFC-tag tilknyttet",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Aktualisieren",
"environmental_entity_optional": "Umgebungs-Sensor (optional)",
"environmental_entity_helper": "z.B. sensor.aussentemperatur — passt das Intervall an Umgebungswerte an",
"adaptive_prediction_enabled": "Sensorbasierte Vorhersagen aktivieren",
"adaptive_seasonal_enabled": "Saisonale Anpassung aktivieren",
"adaptive_max_interval": "Maximales Intervall (Tage)",
"adaptive_min_interval": "Minimales Intervall (Tage)",
"adaptive_ewa_alpha": "Lernrate (Alpha)",
"adaptive_enabled": "Adaptive Planung aktivieren",
"adaptive_section_title": "Adaptive Planung",
"environmental_attribute_optional": "Umgebungs-Attribut (optional)",
"nfc_tag_id": "NFC-Tag-ID",
"nfc_linked": "NFC-Tag verknüpft",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Refresh",
"environmental_entity_optional": "Environmental sensor (optional)",
"environmental_entity_helper": "e.g. sensor.outdoor_temperature — adjusts the interval based on environmental conditions",
"adaptive_prediction_enabled": "Enable sensor-driven predictions",
"adaptive_seasonal_enabled": "Enable seasonal awareness",
"adaptive_max_interval": "Maximum interval (days)",
"adaptive_min_interval": "Minimum interval (days)",
"adaptive_ewa_alpha": "Learning rate (alpha)",
"adaptive_enabled": "Enable adaptive scheduling",
"adaptive_section_title": "Adaptive Scheduling",
"environmental_attribute_optional": "Environmental attribute (optional)",
"nfc_tag_id": "NFC Tag ID",
"nfc_linked": "NFC tag linked",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Actualizar",
"environmental_entity_optional": "Sensor ambiental (opcional)",
"environmental_entity_helper": "p.ej. sensor.temperatura_exterior — ajusta el intervalo según las condiciones ambientales",
"adaptive_prediction_enabled": "Activar predicciones de sensor",
"adaptive_seasonal_enabled": "Activar conciencia estacional",
"adaptive_max_interval": "Intervalo máximo (días)",
"adaptive_min_interval": "Intervalo mínimo (días)",
"adaptive_ewa_alpha": "Tasa de aprendizaje (alpha)",
"adaptive_enabled": "Activar programación adaptativa",
"adaptive_section_title": "Programación adaptativa",
"environmental_attribute_optional": "Atributo ambiental (opcional)",
"nfc_tag_id": "ID de etiqueta NFC",
"nfc_linked": "Etiqueta NFC vinculada",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Päivitä",
"environmental_entity_optional": "Ympäristöanturi (valinnainen)",
"environmental_entity_helper": "esim. sensor.outdoor_temperature — säätää väliä ympäristöolosuhteiden mukaan",
"adaptive_prediction_enabled": "Ota anturiperusteiset ennusteet käyttöön",
"adaptive_seasonal_enabled": "Ota kausitietoisuus käyttöön",
"adaptive_max_interval": "Enimmäisaikaväli (päivää)",
"adaptive_min_interval": "Vähimmäisaikaväli (päivää)",
"adaptive_ewa_alpha": "Oppimisnopeus (alfa)",
"adaptive_enabled": "Ota mukautuva aikataulutus käyttöön",
"adaptive_section_title": "Mukautuva aikataulutus",
"environmental_attribute_optional": "Ympäristöattribuutti (valinnainen)",
"nfc_tag_id": "NFC-tunnisteen tunnus",
"nfc_linked": "NFC-tunniste linkitetty",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Actualiser",
"environmental_entity_optional": "Capteur d'environnement (optionnel)",
"environmental_entity_helper": "ex. sensor.temperature_exterieure — ajuste l'intervalle selon les conditions environnementales",
"adaptive_prediction_enabled": "Activer les prédictions capteur",
"adaptive_seasonal_enabled": "Activer la sensibilité saisonnière",
"adaptive_max_interval": "Intervalle maximum (jours)",
"adaptive_min_interval": "Intervalle minimum (jours)",
"adaptive_ewa_alpha": "Taux d'apprentissage (alpha)",
"adaptive_enabled": "Activer la planification adaptative",
"adaptive_section_title": "Planification adaptative",
"environmental_attribute_optional": "Attribut d'environnement (optionnel)",
"nfc_tag_id": "ID tag NFC",
"nfc_linked": "Tag NFC lié",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "रिफ़्रेश करें",
"environmental_entity_optional": "पर्यावरण सेंसर (वैकल्पिक)",
"environmental_entity_helper": "उदा. sensor.outdoor_temperature — पर्यावरणीय स्थितियों के आधार पर अंतराल समायोजित करता है",
"adaptive_prediction_enabled": "सेंसर-संचालित पूर्वानुमान सक्षम करें",
"adaptive_seasonal_enabled": "मौसमी जागरूकता सक्षम करें",
"adaptive_max_interval": "अधिकतम अंतराल (दिन)",
"adaptive_min_interval": "न्यूनतम अंतराल (दिन)",
"adaptive_ewa_alpha": "अधिगम दर (अल्फा)",
"adaptive_enabled": "अनुकूली अनुसूचन सक्षम करें",
"adaptive_section_title": "अनुकूली अनुसूचन",
"environmental_attribute_optional": "पर्यावरण विशेषता (वैकल्पिक)",
"nfc_tag_id": "NFC टैग ID",
"nfc_linked": "NFC टैग लिंक किया गया",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Frissítés",
"environmental_entity_optional": "Környezeti érzékelő (opcionális)",
"environmental_entity_helper": "pl. sensor.outdoor_temperature — a környezeti feltételek alapján igazítja az intervallumot",
"adaptive_prediction_enabled": "Érzékelővezérelt előrejelzések engedélyezése",
"adaptive_seasonal_enabled": "Szezonális igazodás engedélyezése",
"adaptive_max_interval": "Maximális intervallum (nap)",
"adaptive_min_interval": "Minimális intervallum (nap)",
"adaptive_ewa_alpha": "Tanulási ráta (alfa)",
"adaptive_enabled": "Adaptív ütemezés engedélyezése",
"adaptive_section_title": "Adaptív ütemezés",
"environmental_attribute_optional": "Környezeti attribútum (opcionális)",
"nfc_tag_id": "NFC címke azonosító",
"nfc_linked": "NFC címke hozzárendelve",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Aggiorna",
"environmental_entity_optional": "Sensore ambientale (opzionale)",
"environmental_entity_helper": "es. sensor.temperatura_esterna — regola l'intervallo in base alle condizioni ambientali",
"adaptive_prediction_enabled": "Abilita previsioni sensore",
"adaptive_seasonal_enabled": "Abilita consapevolezza stagionale",
"adaptive_max_interval": "Intervallo massimo (giorni)",
"adaptive_min_interval": "Intervallo minimo (giorni)",
"adaptive_ewa_alpha": "Tasso di apprendimento (alpha)",
"adaptive_enabled": "Abilita pianificazione adattiva",
"adaptive_section_title": "Pianificazione adattiva",
"environmental_attribute_optional": "Attributo ambientale (opzionale)",
"nfc_tag_id": "ID tag NFC",
"nfc_linked": "Tag NFC collegato",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "更新",
"environmental_entity_optional": "環境センサー(任意)",
"environmental_entity_helper": "例: sensor.outdoor_temperature — 環境条件に応じて間隔を調整します",
"adaptive_prediction_enabled": "センサー駆動の予測を有効にする",
"adaptive_seasonal_enabled": "季節認識を有効にする",
"adaptive_max_interval": "最大間隔 (日)",
"adaptive_min_interval": "最小間隔 (日)",
"adaptive_ewa_alpha": "学習率 (アルファ)",
"adaptive_enabled": "適応スケジューリングを有効にする",
"adaptive_section_title": "適応スケジューリング",
"environmental_attribute_optional": "環境属性(任意)",
"nfc_tag_id": "NFCタグID",
"nfc_linked": "NFCタグ連携済み",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "새로 고침",
"environmental_entity_optional": "환경 센서 (선택)",
"environmental_entity_helper": "예: sensor.outdoor_temperature — 환경 조건에 따라 주기를 조정합니다",
"adaptive_prediction_enabled": "센서 기반 예측 사용",
"adaptive_seasonal_enabled": "계절 인식 사용",
"adaptive_max_interval": "최대 주기(일)",
"adaptive_min_interval": "최소 주기(일)",
"adaptive_ewa_alpha": "학습률(alpha)",
"adaptive_enabled": "적응형 일정 사용",
"adaptive_section_title": "적응형 일정",
"environmental_attribute_optional": "환경 속성 (선택)",
"nfc_tag_id": "NFC 태그 ID",
"nfc_linked": "NFC 태그 연결됨",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Oppdater",
"environmental_entity_optional": "Miljøsensor (valgfritt)",
"environmental_entity_helper": "f.eks. sensor.outdoor_temperature — justerer intervallet basert på miljøforhold",
"adaptive_prediction_enabled": "Aktiver sensordrevne prognoser",
"adaptive_seasonal_enabled": "Aktiver sesongbevissthet",
"adaptive_max_interval": "Største intervall (dager)",
"adaptive_min_interval": "Minste intervall (dager)",
"adaptive_ewa_alpha": "Læringsrate (alfa)",
"adaptive_enabled": "Aktiver adaptiv planlegging",
"adaptive_section_title": "Adaptiv planlegging",
"environmental_attribute_optional": "Miljøattributt (valgfritt)",
"nfc_tag_id": "NFC-brikke-ID",
"nfc_linked": "NFC-brikke koblet",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Vernieuwen",
"environmental_entity_optional": "Omgevingssensor (optioneel)",
"environmental_entity_helper": "bv. sensor.buitentemperatuur — past het interval aan op basis van omgevingswaarden",
"adaptive_prediction_enabled": "Sensorgestuurde voorspellingen inschakelen",
"adaptive_seasonal_enabled": "Seizoensbewustzijn inschakelen",
"adaptive_max_interval": "Maximaal interval (dagen)",
"adaptive_min_interval": "Minimaal interval (dagen)",
"adaptive_ewa_alpha": "Leersnelheid (alpha)",
"adaptive_enabled": "Adaptieve planning inschakelen",
"adaptive_section_title": "Adaptieve planning",
"environmental_attribute_optional": "Omgevingsattribuut (optioneel)",
"nfc_tag_id": "NFC-tag-ID",
"nfc_linked": "NFC-tag gekoppeld",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Odśwież",
"environmental_entity_optional": "Czujnik środowiskowy (opcjonalne)",
"environmental_entity_helper": "np. sensor.outdoor_temperature — dostosowuje interwał na podstawie warunków środowiskowych",
"adaptive_prediction_enabled": "Włącz predykcje sterowane czujnikami",
"adaptive_seasonal_enabled": "Włącz świadomość sezonową",
"adaptive_max_interval": "Maksymalny interwał (dni)",
"adaptive_min_interval": "Minimalny interwał (dni)",
"adaptive_ewa_alpha": "Tempo uczenia (alpha)",
"adaptive_enabled": "Włącz adaptacyjne planowanie",
"adaptive_section_title": "Adaptacyjne planowanie",
"environmental_attribute_optional": "Atrybut środowiskowy (opcjonalne)",
"nfc_tag_id": "ID tagu NFC",
"nfc_linked": "Tag NFC powiązany",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Atualizar",
"environmental_entity_optional": "Sensor ambiental (opcional)",
"environmental_entity_helper": "ex.: sensor.outdoor_temperature — ajusta o intervalo com base nas condições ambientais",
"adaptive_prediction_enabled": "Ativar previsões baseadas em sensor",
"adaptive_seasonal_enabled": "Ativar sazonalidade",
"adaptive_max_interval": "Intervalo máximo (dias)",
"adaptive_min_interval": "Intervalo mínimo (dias)",
"adaptive_ewa_alpha": "Taxa de aprendizado (alpha)",
"adaptive_enabled": "Ativar agendamento adaptativo",
"adaptive_section_title": "Agendamento adaptativo",
"environmental_attribute_optional": "Atributo ambiental (opcional)",
"nfc_tag_id": "ID da tag NFC",
"nfc_linked": "Tag NFC vinculada",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Atualizar",
"environmental_entity_optional": "Sensor ambiental (opcional)",
"environmental_entity_helper": "ex. sensor.temperatura_exterior — ajusta o intervalo segundo as condições ambientais",
"adaptive_prediction_enabled": "Ativar previsões baseadas em sensores",
"adaptive_seasonal_enabled": "Ativar consciência sazonal",
"adaptive_max_interval": "Intervalo máximo (dias)",
"adaptive_min_interval": "Intervalo mínimo (dias)",
"adaptive_ewa_alpha": "Taxa de aprendizagem (alfa)",
"adaptive_enabled": "Ativar agendamento adaptativo",
"adaptive_section_title": "Agendamento Adaptativo",
"environmental_attribute_optional": "Atributo ambiental (opcional)",
"nfc_tag_id": "ID da etiqueta NFC",
"nfc_linked": "Etiqueta NFC associada",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Обновить",
"environmental_entity_optional": "Датчик окружающей среды (опционально)",
"environmental_entity_helper": "напр. sensor.outdoor_temperature — корректирует интервал в зависимости от условий",
"adaptive_prediction_enabled": "Включить прогнозы на основе датчиков",
"adaptive_seasonal_enabled": "Учитывать сезонность",
"adaptive_max_interval": "Максимальный интервал (дни)",
"adaptive_min_interval": "Минимальный интервал (дни)",
"adaptive_ewa_alpha": "Скорость обучения (альфа)",
"adaptive_enabled": "Включить адаптивное планирование",
"adaptive_section_title": "Адаптивное планирование",
"environmental_attribute_optional": "Атрибут среды (опционально)",
"nfc_tag_id": "ID NFC-метки",
"nfc_linked": "NFC-метка привязана",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Uppdatera",
"environmental_entity_optional": "Miljösensor (valfritt)",
"environmental_entity_helper": "t.ex. sensor.outdoor_temperature — justerar intervallet baserat på miljöförhållanden",
"adaptive_prediction_enabled": "Aktivera sensorstyrda prediktioner",
"adaptive_seasonal_enabled": "Aktivera säsongsmedvetenhet",
"adaptive_max_interval": "Största intervall (dagar)",
"adaptive_min_interval": "Minsta intervall (dagar)",
"adaptive_ewa_alpha": "Inlärningshastighet (alpha)",
"adaptive_enabled": "Aktivera adaptiv schemaläggning",
"adaptive_section_title": "Adaptiv schemaläggning",
"environmental_attribute_optional": "Miljöattribut (valfritt)",
"nfc_tag_id": "NFC-tagg-ID",
"nfc_linked": "NFC-tagg länkad",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "Yenile",
"environmental_entity_optional": "Çevresel sensör (isteğe bağlı)",
"environmental_entity_helper": "örn. sensor.outdoor_temperature — aralığı çevresel koşullara göre ayarlar",
"adaptive_prediction_enabled": "Sensör tabanlı tahminleri etkinleştir",
"adaptive_seasonal_enabled": "Mevsimsel farkındalığı etkinleştir",
"adaptive_max_interval": "Maksimum aralık (gün)",
"adaptive_min_interval": "Minimum aralık (gün)",
"adaptive_ewa_alpha": "Öğrenme hızı (alfa)",
"adaptive_enabled": "Uyarlanabilir zamanlamayı etkinleştir",
"adaptive_section_title": "Uyarlanabilir Zamanlama",
"environmental_attribute_optional": "Çevresel öznitelik (isteğe bağlı)",
"nfc_tag_id": "NFC Etiket Kimliği",
"nfc_linked": "NFC etiketi bağlandı",
@@ -205,6 +205,13 @@
"nfc_tags_refresh": "Оновити",
"environmental_entity_optional": "Датчик навколишнього середовища (необов'язково)",
"environmental_entity_helper": "напр. sensor.outdoor_temperature — коригує інтервал відповідно до умов навколишнього середовища",
"adaptive_prediction_enabled": "Увімкнути прогнози за сенсорами",
"adaptive_seasonal_enabled": "Увімкнути сезонну корекцію",
"adaptive_max_interval": "Максимальний інтервал (дні)",
"adaptive_min_interval": "Мінімальний інтервал (дні)",
"adaptive_ewa_alpha": "Швидкість навчання (alpha)",
"adaptive_enabled": "Увімкнути адаптивне планування",
"adaptive_section_title": "Адаптивне планування",
"environmental_attribute_optional": "Атрибут середовища (необов'язково)",
"nfc_tag_id": "ID NFC-тега",
"nfc_linked": "NFC-тег прив'язано",
@@ -206,6 +206,13 @@
"nfc_tags_refresh": "刷新",
"environmental_entity_optional": "环境传感器 (可选)",
"environmental_entity_helper": "例如:sensor.outdoor_temperature — 根据环境条件自动调整间隔",
"adaptive_prediction_enabled": "启用基于传感器的预测",
"adaptive_seasonal_enabled": "启用季节性感知",
"adaptive_max_interval": "最大间隔 (天)",
"adaptive_min_interval": "最小间隔 (天)",
"adaptive_ewa_alpha": "学习率 (alpha)",
"adaptive_enabled": "启用自适应计划",
"adaptive_section_title": "自适应计划",
"environmental_attribute_optional": "环境属性 (可选)",
"nfc_tag_id": "NFC 标签 ID",
"nfc_linked": "NFC 标签已链接",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
/*! maintenance_supporter frontend 2.55.0 */
var S="2.55.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
/*! maintenance_supporter frontend 2.56.0 */
var S="2.56.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,222 @@
/*! maintenance_supporter frontend 2.56.0 */
import{a as d}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-I7J3AORE.js";import{a,b as _,c as t,f as l,g as h,i as g,j as n,n as i,p as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.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._localeReady=!1;this._userService=null;this._toggle=s=>{let o=new Set(this._selected);o.has(s)?o.delete(s):o.add(s),this._selected=o};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})),o=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:o})),this._open=!1}catch(s){this._error=d(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return this.hass?.language||"en"}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="";try{this._userService?this._userService.updateHass(this.hass):this._userService=new u(this.hass);let[s,o]=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=o}catch(s){this._error=d(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return t``;let s=this._lang,o=this._sensors.length>0&&this._selected.size===this._sensors.length;return t`
<div class="overlay" @click=${this._close}>
<div class="card" @click=${e=>e.stopPropagation()}>
<div class="title">${i("adopt_problem_title",s)}</div>
<div class="hint">${i("adopt_problem_hint",s)}</div>
${this._error?t`<div class="error">${this._error}</div>`:l}
${this._loading?t`<div class="loading">…</div>`:this._sensors.length===0?t`<div class="empty">${i("adopt_problem_none",s)}</div>`:t`
<label class="select-all">
<input
type="checkbox"
.checked=${o}
@change=${this._toggleAll}
/>
<span>${i("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),p=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return t`
<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 ${p?"chip-active":"chip-ok"}">
${p?i("adopt_problem_active",s):i("adopt_problem_ok",s)}
</span>
</div>
${c?t`<div class="row-sub">${c}</div>`:l}
<div class="row-target">
${e.suggested_object_name}${e.suggested_entry_id?l:t` <span class="new-tag">${i("adopt_problem_new_object",s)}</span>`}
</div>
${e.suggested_part_name?t`<div class="row-part">
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
${i("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
</div>`:l}
</div>
</label>
`})}
</div>
`}
${!this._loading&&this._sensors.length>0&&this._users.length>0?t`
<label class="responsible">
<span>${i("adopt_problem_responsible",s)}</span>
<select
.value=${this._responsible}
@change=${e=>{this._responsible=e.target.value}}
>
<option value="" ?selected=${!this._responsible}>${i("no_user_assigned",s)}</option>
${this._users.map(e=>t`<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}>
${i("cancel",s)}
</ha-button>
<ha-button
@click=${this._adopt}
.disabled=${this._selected.size===0||this._adopting}
>
${i("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;
}
.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;
}
`,a([g({attribute:!1})],r.prototype,"hass",2),a([n()],r.prototype,"_open",2),a([n()],r.prototype,"_loading",2),a([n()],r.prototype,"_adopting",2),a([n()],r.prototype,"_error",2),a([n()],r.prototype,"_sensors",2),a([n()],r.prototype,"_selected",2),a([n()],r.prototype,"_users",2),a([n()],r.prototype,"_responsible",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.56.0 */
var i=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],s=i.map(n=>n.key),l=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function c(n){if(!Array.isArray(n))return[...l];let o=new Set,e=[];for(let a of n)typeof a=="string"&&s.includes(a)&&!o.has(a)&&(o.add(a),e.push(a));return e.length?(e.includes("name")||e.unshift("name"),e):[...l]}function u(n,o,e){let a=new Blob([n],{type:e}),r=URL.createObjectURL(a),t=document.createElement("a");t.href=r,t.download=o,t.target="_blank",t.rel="noopener",t.style.display="none",document.body.appendChild(t),t.dispatchEvent(new MouseEvent("click")),document.body.removeChild(t),setTimeout(()=>URL.revokeObjectURL(r),6e4)}function y(n,o){let e=document.createElement("a");e.href=n,e.download=o,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{i as a,l as b,c,u as d,y as e};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.56.0 */
var r=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestamp<this.CACHE_TTL_MS)return this.usersCache;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/users/list"});return this.usersCache=t.users,this.cacheTimestamp=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,user_id:t})}async getTasksByUser(s){return(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tasks/by_user",user_id:s})).tasks}getUserName(s){return!s||!this.usersCache?null:this.usersCache.find(t=>t.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{r as a};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.56.0 */
import{n as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var s={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_for_minutes:"trigger_for_minutes"};function c(r,o){let e=s[r];if(!e)return r;let t=a(e,o);return t&&t!==e?t:r}function d(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function g(r,o,e){if(e=e??a("action_error",o),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,_=t.message||t.error?.message||"";if(!_)return e;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 _||e}}export{g as a};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.56.0 */
var o=["notes","cost","duration","photo","user"],t={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{o as a,t as b};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.56.0 */
import{n as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";function _(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let t=!!e.entry_id&&e.entry_id!==r,a=t?e.entry_id:r,o=s.find(p=>p.entry_id===a),n=(o?.parts||[]).find(p=>p.id===e.part_id)||null,d=t&&o?.object?.name||"",i=n?.name||u("shared_part_unknown",c);return{part:n,foreign:t,ownerName:d,label:d?`${i} (${d})`:i}}function f(e,r,s,c){let{part:t,label:a}=l(e,r,s,c),o=t&&t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:"",n=t?.storage_location?` \u2014 ${t.storage_location}`:"";return`${e.quantity}\xD7 ${a}${o}${n}`}function g(e,r,s,c){let a=(s.find(n=>n.entry_id===r)?.parts||[]).map(n=>({...n})),o=new Set(a.map(n=>_({part_id:n.id})));for(let n of e?.consumes_parts||[]){if(!n.entry_id||n.entry_id===r)continue;let d=_(n);if(o.has(d))continue;o.add(d);let{part:i,ownerName:p}=l(n,r,s,c);a.push({id:n.part_id,name:i?.name||u("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:n.entry_id,owner_name:p})}return a}export{_ as a,f as b,g as c};
@@ -0,0 +1,54 @@
/*! maintenance_supporter frontend 2.56.0 */
import{a as t,b as a,c as i,f as l,g as p,i as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var e=class extends p{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(n){let o=n.target.value;this.value=o,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:o}}))}render(){return i`
<label class="field">
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:l}</span>`:l}
<input
.value=${this.value??""}
.type=${this.type}
?required=${this.required}
?disabled=${this.disabled}
placeholder=${this.placeholder}
step=${this.step??l}
min=${this.min??l}
max=${this.max??l}
pattern=${this.pattern??l}
@input=${this._onInput}
@change=${this._onInput}
/>
${this.helper?i`<span class="helper">${this.helper}</span>`:l}
</label>
`}};e.styles=a`
: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; }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
`,t([r()],e.prototype,"label",2),t([r()],e.prototype,"value",2),t([r()],e.prototype,"placeholder",2),t([r()],e.prototype,"type",2),t([r({type:Boolean})],e.prototype,"required",2),t([r({type:Boolean})],e.prototype,"disabled",2),t([r()],e.prototype,"step",2),t([r()],e.prototype,"min",2),t([r()],e.prototype,"max",2),t([r()],e.prototype,"pattern",2),t([r()],e.prototype,"helper",2);customElements.get("ms-textfield")||customElements.define("ms-textfield",e);
@@ -0,0 +1,292 @@
/*! maintenance_supporter frontend 2.56.0 */
import{b as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NGMG4DEY.js";import{a as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-SD6IEJBA.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as s,b,c as a,f as d,g as v,i as n,j as l,n as r,x as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._usedParts={};this.checklistPrefill={}}open(){this._open||(this._open=!0,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((e,t)=>[String(t),!!this.checklistPrefill[e]]).filter(([,e])=>e)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._usedParts=Object.fromEntries(this.consumesParts.map(e=>[_(e),{...e}])))}_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,o=t.files?.[0];if(t.value="",!!o){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",o,o.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(o))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=g(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let o=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(o)||o<=0?null:Math.round(this.restockUnitCost*o*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let o of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(o));c?.cost!=null&&(e+=c.cost*(o.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let o=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return a`<button
type="button"
class="cost-suggestion"
@click=${()=>this._cost=t.toFixed(2)}
>${r("cost_from_parts",e).replace("{amount}",o)}</button>`}_close(){this._open=!1}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>
<div class="content">
${this._error?a`<div class="error">${this._error}</div>`:d}
${this.checklist.length>0?a`
<div class="checklist-section">
<label class="checklist-label">${r("checklist",e)}</label>
${this.checklist.map((t,o)=>a`
<label class="checklist-item" @click=${()=>this._toggleCheck(o)}>
<input type="checkbox" .checked=${!!this._checklistState[String(o)]} />
<span>${t}</span>
</label>
`)}
</div>
`:d}
${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>`:d}
${this.parts.length?a`<div class="used-parts">
<span class="field-label">${r("complete_parts_used",e)}</span>
${this.parts.map(t=>{let o=_({part_id:t.id,entry_id:t.entry_id}),c=this._usedParts[o],p=c!==void 0,u=t.entry_id?{part_id:t.id,quantity:1,entry_id:t.entry_id}:{part_id:t.id,quantity:1};return a`<div class="used-part-row">
<label class="used-part-check">
<input type="checkbox" .checked=${p}
@change=${f=>{let h={...this._usedParts};f.target.checked?h[o]=h[o]||u:delete h[o],this._usedParts=h}} />
<span
>${t.name}${t.owner_name?a`<span class="used-part-owner"> (${t.owner_name})</span>`:d}${t.stock!==null&&t.stock!==void 0?` (${t.stock}${t.unit?" "+t.unit:""})`:""}</span
>
</label>
${p?a`<input class="used-part-qty" type="number" min="0.01" max="999" step="0.01"
.value=${String(c.quantity)}
@input=${f=>{let h=parseFloat(f.target.value);this._usedParts={...this._usedParts,[o]:{...u,quantity:Number.isFinite(h)&&h>=.01?h:1}}}} />`:d}
</div>`})}
</div>`:this.consumesInfo.length?a`<div class="consumes-hint">
${this.consumesInfo.map(t=>a`<div>${t}</div>`)}
</div>`:d}
${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>`:d}
<!-- Native <input>s rather than <ha-textfield>: when this dialog
is opened from a Lovelace card via dialog-mount, ha-textfield
isn't yet registered (HA loads it lazily when its own panels
need it) so the elements render with zero height and the user
only sees the title + Cancel/Complete buttons the original
bug report. Native inputs always render. -->
<label class="field">
<span class="field-label">${r("notes_optional",e)}${this._req("notes")}</span>
<input type="text" class="field-input"
.value=${this._notes}
@input=${t=>this._notes=t.target.value} />
</label>
<label class="field">
<span class="field-label">${r("cost_optional",e)}${this._req("cost")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._cost}
@input=${t=>this._cost=t.target.value} />
${this._renderCostSuggestion(e)}
</label>
<label class="field">
<span class="field-label">${r("duration_minutes",e)}${this._req("duration")}</span>
<input type="number" step="0.01" min="0" class="field-input"
.value=${this._duration}
@input=${t=>this._duration=t.target.value} />
</label>
<div class="field">
<span class="field-label">${r("completion_photo_optional",e)}${this._req("photo")}</span>
${this._photoPreview?a`
<div class="photo-preview">
<img src=${this._photoPreview} alt="" />
<button type="button" class="photo-remove" @click=${this._removePhoto}
title="${r("remove",e)}"></button>
</div>`:a`
<label class="photo-pick">
<ha-icon icon="mdi:camera"></ha-icon>
<span>${this._photoUploading?r("uploading",e):r("add_photo",e)}</span>
<input type="file" accept="image/*" capture="environment"
?disabled=${this._photoUploading}
@change=${this._onPhotoInput} />
</label>`}
</div>
${this.adaptiveEnabled?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>
`:d}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._close}>
${r("cancel",e)}
</ha-button>
<ha-button
@click=${this._complete}
.disabled=${this._loading||this._missingRequired.length>0}
title=${this._missingRequired.length?this._missingRequired.map(t=>r("err_required",e).replace("{field}",r(m[t]??t,e))).join(" \xB7 "):""}
>
${this._loading?r("completing",e):r("complete",e)}
</ha-button>
</div>
</ha-dialog>
`}};i.styles=[k,b`
.req-mark {
color: var(--error-color, #f44336);
margin-left: 2px;
font-weight: 600;
}
/* #104: one-click cost suggestion from parts — quiet link-style chip. */
.cost-suggestion {
align-self: flex-start;
margin-top: 4px;
padding: 0;
border: none;
background: none;
color: var(--primary-color);
font-size: 12.5px;
cursor: pointer;
text-decoration: underline dotted;
text-underline-offset: 2px;
}
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
.content {
display: flex;
flex-direction: column;
gap: 16px;
min-width: 300px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
.consumes-hint {
font-size: 13px;
color: var(--secondary-text-color);
border-left: 3px solid var(--primary-color);
padding: 4px 8px;
margin: 4px 0 8px;
}
/* #99: editable per-completion parts selection */
.used-parts { margin: 4px 0 8px; display: flex; flex-direction: column; gap: 4px; }
.used-part-row { display: flex; align-items: center; gap: 8px; }
.used-part-check {
display: flex; align-items: center; gap: 6px; flex: 1;
font-size: 13px; cursor: pointer;
}
.used-part-check input { cursor: pointer; }
/* #111: whose stock this row draws on. Muted but never omitted an
unlabelled foreign pool is indistinguishable from an own part. */
.used-part-owner { color: var(--secondary-text-color); }
.used-part-qty {
width: 76px; padding: 4px 6px; border-radius: 4px; font: inherit; font-size: 13px;
border: 1px solid var(--divider-color);
background: var(--card-background-color);
color: var(--primary-text-color);
}
.error {
color: var(--error-color, #f44336);
font-size: 13px;
}
/* .field/.field-label/.field-input come from nativeFieldStyles */
.photo-pick {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border: 1px dashed var(--divider-color);
border-radius: 8px;
cursor: pointer;
font-size: 13px;
color: var(--secondary-text-color);
width: fit-content;
}
.photo-pick:hover { border-color: var(--primary-color); }
.photo-pick input[type="file"] { display: none; }
.photo-preview {
position: relative;
width: fit-content;
}
.photo-preview img {
max-width: 160px;
max-height: 160px;
border-radius: 8px;
display: block;
}
.photo-remove {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
border-radius: 50%;
border: none;
background: var(--error-color, #db4437);
color: #fff;
cursor: pointer;
font-size: 12px;
line-height: 1;
}
.checklist-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid var(--divider-color);
margin-bottom: 4px;
}
.checklist-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.checklist-item {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 4px 0;
font-size: 14px;
}
.checklist-item input[type="checkbox"] {
width: 18px;
height: 18px;
cursor: pointer;
}
.feedback-section {
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
border-top: 1px solid var(--divider-color);
}
.feedback-label {
font-weight: 500;
font-size: 13px;
color: var(--secondary-text-color);
}
.feedback-buttons {
display: flex;
gap: 8px;
}
.feedback-btn {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--divider-color);
border-radius: 8px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font-size: 13px;
cursor: pointer;
text-align: center;
transition: all 0.2s;
}
.feedback-btn:hover {
background: var(--secondary-background-color, #f5f5f5);
}
.feedback-btn.selected {
background: var(--primary-color);
color: var(--text-primary-color, #fff);
border-color: var(--primary-color);
}
`],s([n({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"entryId",2),s([n()],i.prototype,"taskId",2),s([n()],i.prototype,"taskName",2),s([n()],i.prototype,"lang",2),s([n({type:Array})],i.prototype,"checklist",2),s([n({type:Boolean})],i.prototype,"adaptiveEnabled",2),s([n()],i.prototype,"taskType",2),s([n()],i.prototype,"readingUnit",2),s([n({attribute:!1})],i.prototype,"restockDefault",2),s([n({attribute:!1})],i.prototype,"restockUnitCost",2),s([n()],i.prototype,"currencySymbol",2),s([n({attribute:!1})],i.prototype,"parts",2),s([n({attribute:!1})],i.prototype,"consumesParts",2),s([n({type:Array})],i.prototype,"consumesInfo",2),s([n({type:Array})],i.prototype,"requiredFields",2),s([l()],i.prototype,"_open",2),s([l()],i.prototype,"_notes",2),s([l()],i.prototype,"_cost",2),s([l()],i.prototype,"_duration",2),s([l()],i.prototype,"_loading",2),s([l()],i.prototype,"_error",2),s([l()],i.prototype,"_checklistState",2),s([l()],i.prototype,"_feedback",2),s([l()],i.prototype,"_photoDocId",2),s([l()],i.prototype,"_photoPreview",2),s([l()],i.prototype,"_photoUploading",2),s([l()],i.prototype,"_readingValue",2),s([l()],i.prototype,"_restockQty",2),s([l()],i.prototype,"_usedParts",2),s([n({attribute:!1})],i.prototype,"checklistPrefill",2);customElements.get("maintenance-complete-dialog")||customElements.define("maintenance-complete-dialog",i);export{i as MaintenanceCompleteDialog};
@@ -0,0 +1,143 @@
/*! maintenance_supporter frontend 2.56.0 */
import"/maintenance_supporter_panelfiles/panel-chunks/chunk-ZK3W7TF6.js";import{a as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as r,b as _,c as l,f as o,g as p,i as d,j as s,n as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";var e=class extends p{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return this.hass?.language??navigator.language.split("-")[0]??"en"}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(a,n){this._entryId=a,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(a){this._error=h(a,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(a=>a.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let a=this._lang,n=this._entryId?i("edit_object",a):i("new_object",a);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",a)}"
required
.value=${this._name}
@input=${t=>this._name=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("manufacturer_optional",a)}"
.value=${this._manufacturer}
@input=${t=>this._manufacturer=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("model_optional",a)}"
.value=${this._model}
@input=${t=>this._model=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("serial_number_optional",a)}"
.value=${this._serialNumber}
@input=${t=>this._serialNumber=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("documentation_url_optional",a)}"
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",a)}"
.value=${this._areaId}
@value-changed=${t=>this._areaId=t.detail.value||""}
></ha-area-picker>
<ms-textfield
label="${i("installation_date_optional",a)}"
type="date"
.value=${this._installationDate}
@input=${t=>this._installationDate=t.target.value}
></ms-textfield>
<ms-textfield
label="${i("warranty_expiry_optional",a)}"
type="date"
.value=${this._warrantyExpiry}
@input=${t=>this._warrantyExpiry=t.target.value}
></ms-textfield>
<ha-form
.hass=${this.hass}
.data=${{device:this._haDeviceId||void 0}}
.schema=${[{name:"device",selector:{device:{}}}]}
.computeLabel=${()=>i("link_device_optional",a)}
@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",a)}</span>
<select
class="parent-select"
.value=${this._parentEntryId}
@change=${t=>this._parentEntryId=t.target.value}
>
<option value="" ?selected=${!this._parentEntryId}>
${i("parent_none",a)}
</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",a)}</span>
<textarea
rows="3"
.value=${this._notes}
@input=${t=>this._notes=t.target.value}
></textarea>
</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>
`}};e.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);
}
.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})],e.prototype,"hass",2),r([d({attribute:!1})],e.prototype,"objects",2),r([s()],e.prototype,"_open",2),r([s()],e.prototype,"_loading",2),r([s()],e.prototype,"_error",2),r([s()],e.prototype,"_name",2),r([s()],e.prototype,"_manufacturer",2),r([s()],e.prototype,"_model",2),r([s()],e.prototype,"_serialNumber",2),r([s()],e.prototype,"_areaId",2),r([s()],e.prototype,"_installationDate",2),r([s()],e.prototype,"_warrantyExpiry",2),r([s()],e.prototype,"_documentationUrl",2),r([s()],e.prototype,"_notes",2),r([s()],e.prototype,"_haDeviceId",2),r([s()],e.prototype,"_parentEntryId",2),r([s()],e.prototype,"_entryId",2);customElements.get("maintenance-object-dialog")||customElements.define("maintenance-object-dialog",e);export{e as MaintenanceObjectDialog};
@@ -0,0 +1,213 @@
/*! maintenance_supporter frontend 2.56.0 */
import{a,b as v,c as n,f as g,g as b,i as m,j as c,n as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.js";function p(l){return l.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function x(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":p(l)}function $(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var r=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,o,s){this._entryId=e,this._taskId=i,this._objectName=o,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 o=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&o.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(o);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let o=i?.code,s=i?.message;this._error=o==="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,o=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let h=this.lang||"en",d=p(i),u=p(o),_=!!this._completeResult,f=p(t("qr_action_view",h)),w=p(t("qr_action_complete",h));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
<meta name="color-scheme" content="light">
<title>${d}</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>${d}</h2>
${u?`<div class="sub">${u}</div>`:""}
<div class="qr-row">
<div class="qr-col">
<img src="${x(this._viewResult.svg_data_uri)}" alt="QR Info" />
<div class="qr-label">${f}</div>
</div>
${_?`<div class="qr-col">
<img src="${x(this._completeResult.svg_data_uri)}" alt="QR Complete" />
<div class="qr-label">${w}</div>
</div>`:""}
</div>
<div class="url">${p(this._viewResult.url)}</div>
<script>setTimeout(()=>window.print(),300)<\/script>
</body></html>`),s.document.close()}_downloadSvg(e,i){let o=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),s=new Blob([o],{type:"image/svg+xml"}),h=URL.createObjectURL(s),d=document.createElement("a");d.href=h;let u=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;d.download=`qr-${$(u)}-${i}.svg`,d.click(),URL.revokeObjectURL(h)}_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}`,o=!!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>`:o?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>
`:g}
</div>
<div class="url-display">${this._viewResult.url}</div>
`:g}
<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=${!o}
>
${t("qr_print",e)}
</ha-button>
</div>
</ha-dialog>
`}};r.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([m({attribute:!1})],r.prototype,"hass",2),a([m()],r.prototype,"lang",2),a([c()],r.prototype,"_open",2),a([c()],r.prototype,"_loading",2),a([c()],r.prototype,"_error",2),a([c()],r.prototype,"_viewResult",2),a([c()],r.prototype,"_completeResult",2),a([c()],r.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",r);export{r as MaintenanceQrDialog};
@@ -0,0 +1,146 @@
/*! maintenance_supporter frontend 2.56.0 */
import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LJXSDCLS.js";import{a as l,b as m,c as i,f as h,g as v,i as u,j as d,n as p,p as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-C5W5B43R.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 this.hass?.language||"en"}updated(t){t.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,f(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};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,125 @@
/*! maintenance_supporter frontend 2.56.0 */
import{a as v}from"./chunk-XWMJNIQI.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-TRW3CRLS.js";import{a as n}from"./chunk-UHANDS57.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};
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.56.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.56.0 */
import{a as r}from"./chunk-TRW3CRLS.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,137 @@
/*! maintenance_supporter frontend 2.56.0 */
import{a as m}from"./chunk-XWMJNIQI.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-TRW3CRLS.js";import{a}from"./chunk-UHANDS57.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.56.0 */
import{a as m}from"./chunk-XWMJNIQI.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-TRW3CRLS.js";import{a as i}from"./chunk-UHANDS57.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