456 files

This commit is contained in:
Home Assistant Version Control
2026-10-01 05:38:21 +00:00
parent 4192ecbaae
commit 5d6d845ebf
456 changed files with 64951 additions and 3828 deletions
@@ -0,0 +1,127 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as w,b as $}from"./chunk-IWH6LGYJ.js";import{a as h,b as o,d as m,e as y,f as v,g as l,h as g,i as _,j as f,k as x,n as p,z as u}from"./chunk-2ARNLSF6.js";import{a as n,g as i}from"./chunk-3MSNNIFG.js";var E=80,s=class extends y{constructor(){super(...arguments);this._config={type:""};this._status=null;this._busy=!1;this._error="";this._localMonthly="";this._localYearly="";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return f(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),_(this,t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load())}async _load(){let t=await u(this,{type:"maintenance_supporter/budget_status"},{onError:e=>{this._error=e}});t&&(this._status=t,x(t),this._localMonthly=t.monthly_budget?String(t.monthly_budget):"",this._localYearly=t.yearly_budget?String(t.yearly_budget):"",this._dirty=!1)}async _save(){if(!this._isAdmin)return;this._error="";let t=this._localMonthly.trim()===""?0:parseFloat(this._localMonthly),e=this._localYearly.trim()===""?0:parseFloat(this._localYearly),a={};!isNaN(t)&&t>=0&&(a.budget_monthly=t),!isNaN(e)&&e>=0&&(a.budget_yearly=e),await u(this,{type:"maintenance_supporter/global/update",settings:a},{busy:d=>{this._busy=d},reload:()=>this._load(),onError:d=>{this._error=d}})}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_budget"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,e=this._status;if(!e)return o`<ha-card><div class="loading">${i("loading",t)}</div></ha-card>`;let a=g(e),d=e.alert_threshold_pct??E,k=[{label:i("budget_monthly",t),spent:e.monthly_spent||0,budget:e.monthly_budget||0},{label:i("budget_yearly",t),spent:e.yearly_spent||0,budget:e.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||i("settings_budget",t)}</span>
</div>
<span class="currency">${a}</span>
</div>
${this._error?o`<div class="error">${this._error}</div>`:m}
${k.map(r=>{if(!(r.budget>0))return o`
<div class="track spent-only">
<div class="track-label-row">
<label>${r.label}</label>
<span class="track-numbers ok">${p(r.spent,a,t)}</span>
</div>
</div>
`;let c=Math.min(100,Math.max(0,r.spent/r.budget*100)),b=c>=100?"danger":c>=d?"warning":"ok";return o`
<div class="track">
<div class="track-label-row">
<label>${r.label}</label>
<span class="track-numbers ${b}">
${p(r.spent,void 0,t)} / ${p(r.budget,a,t)}
</span>
</div>
<div class="bar"><div class="bar-fill ${b}" style="width:${c}%"></div></div>
</div>
`})}
${this._isAdmin?o`
<div class="inputs-row">
<div class="input-field">
<label>${i("budget_monthly_set",t)}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localMonthly}
?disabled=${this._busy}
@input=${r=>{this._localMonthly=r.target.value,this._dirty=!0}} />
<span class="input-suffix">${a}</span>
</div>
</div>
<div class="input-field">
<label>${i("budget_yearly_set",t)}</label>
<div class="input-wrap">
<input type="number" min="0" step="1"
.value=${this._localYearly}
?disabled=${this._busy}
@input=${r=>{this._localYearly=r.target.value,this._dirty=!0}} />
<span class="input-suffix">${a}</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?i("save",t):i("saved",t)}
</button>
<button class="btn link" @click=${this._onDeepLink}>
${i("budget_advanced",t)}
</button>
</div>
`:o`
<button class="btn link" @click=${this._onDeepLink}>
${i("budget_open_panel",t)}
</button>
`}
</div>
</ha-card>
`}};s.styles=[$,h`
.currency {
font-size: 14px; font-weight: 600;
color: var(--secondary-text-color);
background: var(--secondary-background-color);
padding: 2px 10px; border-radius: 999px;
}
.track { display: flex; flex-direction: column; gap: 4px; }
.track-label-row {
display: flex; align-items: center; justify-content: space-between;
}
.track-label-row label {
font-size: 12px; color: var(--secondary-text-color);
text-transform: uppercase; letter-spacing: 0.5px;
}
.track-numbers { font-size: 13px; font-weight: 600; }
.track-numbers.ok { color: var(--primary-text-color); }
.track-numbers.warning { color: #ff9800; }
.track-numbers.danger { color: var(--error-color, #f44336); }
.bar {
height: 6px; background: var(--secondary-background-color);
border-radius: 3px; overflow: hidden;
}
.bar-fill { height: 100%; transition: width 0.3s; border-radius: 3px; }
.bar-fill.ok { background: var(--primary-color); }
.bar-fill.warning { background: #ff9800; }
.bar-fill.danger { background: var(--error-color, #f44336); }
/* minmax(0, …): a bare 1fr never shrinks below the number inputs'
natural width — the row was 420 px wide on a phone (2026-09-27). */
.inputs-row {
display: grid; grid-template-columns: repeat(2, minmax(0, 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; min-width: 0; width: 100%; box-sizing: border-box; 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([v({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);w({type:"maintenance-budget-section-card",name:"Maintenance Supporter \u2014 Budget",description:"Edit the monthly and yearly budget right on the dashboard.",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.95.0 */
var _=Object.defineProperty;var f=Object.getOwnPropertyDescriptor;var L=(n,e,t,s)=>{for(var r=s>1?void 0:s?f(e,t):e,c=n.length-1,l;c>=0;c--)(l=n[c])&&(r=(s?l(e,t,r):l(r))||r);return s&&r&&_(e,t,r),r};var v={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)"},p=["overdue","triggered","due_soon","ok"],E=["overdue","triggered","due_soon"],T=Object.fromEntries(p.map((n,e)=>[n,e]));var b={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"};var u="2.95.0";var a="en",g=(()=>{let n=window;return n.__msLocales||(n.__msLocales={store:{},inflight:{}}),n.__msLocales})(),o=g.store,i=g.inflight;function N(n){o.en=Object.assign({},n,o.en??{})}var S=new Set(["de","nl","fr","it","es","pt","pt-br","ru","uk","pl","cs","sv","zh","da","fi","nb","ja","hi","hu","ko","tr"]),m="/maintenance_supporter_locales";function d(n){let e=(n||a).toLowerCase();return e.startsWith("pt")&&e.endsWith("br")?"pt-br":e.substring(0,2)}function O(n,e,t){let s=d(e);return o[s]?.[n]??o.en?.[n]??t??n}function U(n){let e=d(n);return e===a||e in o}function k(n){let e=d(n);return e===a||e in o||!S.has(e)?Promise.resolve():(e in i||(i[e]=fetch(`${m}/${e}.json?v=${u}`).then(t=>t.ok?t.json():null).then(t=>{t?o[e]=t:delete i[e]}).catch(()=>{delete i[e]})),i[e])}export{L as a,v as b,E as c,b as d,N as e,d as f,O as g,U as h,k as i};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.95.0 */
function l(r){if(!r)return[];let n=[],i=r.photo_doc_id;typeof i=="string"&&n.push(i);let o=r.photo_doc_ids;Array.isArray(o)&&n.push(...o);let t=[];for(let e of n){if(typeof e!="string")continue;let s=e.trim();if(!(!s||t.includes(s))&&(t.push(s),t.length>=10))break}return t}function f(){let r=new Uint8Array(4);return(globalThis.crypto??{getRandomValues:n=>n.map(()=>Math.floor(Math.random()*256))}).getRandomValues(r),Array.from(r,n=>n.toString(16).padStart(2,"0")).join("")}function d(r){let n=r?.reading_values;if(!Array.isArray(n))return[];let i=[],o=new Set;for(let t of n){if(!t||typeof t!="object")continue;let e=t;typeof e.id!="string"||!e.id||o.has(e.id)||typeof e.value!="number"||!Number.isFinite(e.value)||(o.add(e.id),i.push({id:e.id,name:typeof e.name=="string"?e.name:e.id,unit:typeof e.unit=="string"?e.unit:null,value:e.value}))}return i}function c(r){return r.filter(n=>n.type==="completed"&&(n.reading_value!=null||d(n).length>0)).sort((n,i)=>n.timestamp.localeCompare(i.timestamp))}function p(r){return c(r??[]).map(n=>({timestamp:n.timestamp,values:d(n)})).filter(n=>n.values.length>0)}function m(r,n,i){let o;for(let t of r){if(i!==void 0){let s=new Date(t.timestamp).getTime();if(!isNaN(s)&&s>=i)break}let e=t.values.find(s=>s.id===n);e&&(o=e)}return o}function _(r,n,i){let o=d(n).find(e=>e.id===i);if(!o)return null;let t=null;for(let e of c(r)){if(e.timestamp>=n.timestamp)break;let s=d(e).find(a=>a.id===i);s&&(t=s.value)}return t==null?null:o.value-t}function R(r){let n=new Set,i=new Set;for(let o of r){let t=(o.name||"").trim().toLowerCase();t&&(n.has(t)?i.add(o.id):n.add(t))}return i}function h(r){let n=[],i=new Set,o=new Set;for(let t of r){let e=(t.name||"").trim();if(!(!e||i.has(t.id)||o.has(e.toLowerCase()))&&(i.add(t.id),o.add(e.toLowerCase()),n.push({id:t.id,name:e,unit:(t.unit||"").trim()||null}),n.length>=20))break}return n}function y(r,n,i,o){let t=i,e=u=>typeof u=="string"?u:null,s=u=>typeof u=="number"?u:null,a=e(t.timestamp)??"";return{entry_id:r,task_id:n,original_timestamp:a,type:e(t.type)||"completed",timestamp:a,notes:e(t.notes),cost:s(t.cost),duration:s(t.duration),completed_by:e(t.completed_by),used_parts:Array.isArray(t.used_parts)?t.used_parts:null,photo_doc_ids:l(t),reading_value:s(t.reading_value),reading_values:d(t),readings:o?.readings??[],task_type:o?.type??null,reading_unit:o?.reading_unit??null}}async function E(r,n,i,o){let e=(await r.connection.sendMessagePromise({type:"maintenance_supporter/object",entry_id:n})).tasks?.find(a=>a.id===i);if(!e)return null;let s=e.history?.find(a=>a.timestamp===o);return s||(s=(await r.connection.sendMessagePromise({type:"maintenance_supporter/task/history",entry_id:n,task_id:i})).history?.find(u=>u.timestamp===o)),s?y(n,i,s,e):null}export{l as a,f as b,d as c,p as d,m as e,_ as f,R as g,h,y as i,E as j};
@@ -0,0 +1,66 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as u,b as s,d as r,e as _,f as m,g as o,j as d,x as c}from"./chunk-2ARNLSF6.js";import{a as n,g as p}from"./chunk-3MSNNIFG.js";var g={default_warning_days:[0,365],default_consumable_threshold:[1,90],battery_low_percent:[1,90],battery_recovered_percent:[20,100],archive_oneoff_days:[0,3650],delete_archived_oneoff_days:[0,3650],notify_due_soon_interval_hours:[0,720],notify_overdue_interval_hours:[0,720],notify_triggered_interval_hours:[0,720],max_notifications_per_day:[0,1e3],notification_bundle_threshold:[2,20],snooze_duration_hours:[1,168],warranty_reminder_days:[1,365],budget_alert_threshold:[10,100],currency_decimals:[0,3]};var b=[0,14],x=[0,1440],y=[1,1e4],R=15,A=[.1,.9],E=[0,365],T=[.01,999],$=[.01,9999];var i=class extends _{constructor(){super(...arguments);this._open=!1;this._title="";this._message="";this._confirmText="";this._danger=!1;this._inputLabel="";this._inputType="";this._options=null;this._inputValue="";this._resolve=null;this._promptResolve=null}confirm(e){return this._title=e.title,this._message=e.message,this._confirmText=e.confirmText||"OK",this._danger=e.danger||!1,this._inputLabel="",this._inputType="",this._options=null,this._inputValue="",this._open=!0,new Promise(t=>{this._resolve=t,this._promptResolve=null})}prompt(e){return this._title=e.title,this._message=e.message,this._confirmText=e.confirmText||"OK",this._danger=e.danger||!1,this._inputLabel=e.inputLabel||"",this._inputType=e.inputType||"text",this._options=e.options&&e.options.length?e.options:null,this._inputValue=e.inputValue||"",this._open=!0,new Promise(t=>{this._promptResolve=t,this._resolve=null})}_cancel(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!1,value:""}),this._promptResolve=null),this._resolve?.(!1),this._resolve=null}_confirmAction(){this._open=!1,this._promptResolve&&(this._promptResolve({confirmed:!0,value:this._inputValue}),this._promptResolve=null),this._resolve?.(!0),this._resolve=null}render(){if(!this._open)return r;let e=d(this.hass);return s`
<ha-dialog open @closed=${this._cancel}>
<div class="dialog-title">${this._title}</div>
<div class="content">
${this._message}
${this._inputLabel?s`
<!-- Native <input> rather than <ha-textfield>: HA loads
ha-textfield lazily for its own panels, so inside this custom
panel it can be unregistered and render with zero height —
the prompt then shows no field at all (caught live testing
the pause/replace prompts; same fix as complete-dialog). -->
<label class="field">
<span class="field-label">${this._inputLabel}</span>
${this._options?s`<select class="field-input field-select"
@change=${t=>this._inputValue=t.target.value}>
${this._options.map(t=>s`<option value=${t.value} ?selected=${t.value===this._inputValue}>${t.label}</option>`)}
</select>`:s`<input class="field-input"
type="${this._inputType||"text"}"
.value=${this._inputValue}
@input=${t=>this._inputValue=t.target.value} />`}
</label>
`:r}
</div>
<div class="dialog-actions">
<ha-button appearance="plain" @click=${this._cancel}>
${p("cancel",e)}
</ha-button>
<ha-button
class="${this._danger?"danger":""}"
@click=${this._confirmAction}
>
${this._confirmText}
</ha-button>
</div>
</ha-dialog>
`}};i.styles=[c,u`
.dialog-title {
font-size: 18px;
font-weight: 500;
padding-bottom: 12px;
}
/* shared native-field scaffold from nativeFieldStyles; the prompt input
follows the message text, hence the extra top margin here */
.field { margin-top: 12px; }
.content {
padding: 8px 0;
min-width: 280px;
line-height: 1.5;
display: flex;
flex-direction: column;
gap: 12px;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
padding-top: 16px;
}
ha-textfield {
display: block;
}
ha-button.danger {
--mdc-theme-primary: var(--error-color, #f44336);
}
`],n([m({attribute:!1})],i.prototype,"hass",2),n([o()],i.prototype,"_open",2),n([o()],i.prototype,"_title",2),n([o()],i.prototype,"_message",2),n([o()],i.prototype,"_confirmText",2),n([o()],i.prototype,"_danger",2),n([o()],i.prototype,"_inputLabel",2),n([o()],i.prototype,"_inputType",2),n([o()],i.prototype,"_options",2),n([o()],i.prototype,"_inputValue",2);customElements.get("maintenance-confirm-dialog")||customElements.define("maintenance-confirm-dialog",i);var h="maintenance-confirm-dialog",f="data-ms-lovelace-confirm";function v(){return document.querySelector("home-assistant")?.shadowRoot??document.body}function H(l,a){let e=v(),t=e.querySelector(`${h}[${f}]`);return t||(t=document.createElement(h),t.setAttribute(f,""),e.appendChild(t)),t.hass=l,t.confirm(a)}export{g as a,b,x as c,y as d,R as e,A as f,E as g,T as h,$ as i,H as j};
@@ -0,0 +1,2 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as i}from"./chunk-6BHUADHJ.js";var a={operatorWriteEnabled:!1,operatorIds:[]};function _(e,t=a){return e?e.is_admin?!0:t.operatorWriteEnabled&&t.operatorIds.includes(e.id):!1}var u=["buttons_compact","buttons","icons"];function d(e){let[t,s]=i.default_warning_days,n=e?.general?.default_warning_days,r=e?.general?.row_action_style;return{features:{...o.features,...e?.features??{}},defaultWarningDays:typeof n=="number"&&Number.isFinite(n)&&n>=t&&n<=s?n:o.defaultWarningDays,rowActionStyle:u.includes(r??"")?r:o.rowActionStyle,access:{operatorWriteEnabled:e?.operator_write_enabled===!0,operatorIds:Array.isArray(e?.admin_panel_user_ids)?e.admin_panel_user_ids.filter(c=>typeof c=="string"):[]},budget:e?.budget??null,rowActionNoticePending:e?.general?.row_action_notice_pending===!0,refsInLists:e?.general?.ref_numbers_in_lists===!0,objectsTableColumns:Array.isArray(e?.objects_table_columns)?e.objects_table_columns:void 0}}var o={features:{adaptive:!1,predictions:!1,seasonal:!1,environmental:!1,budget:!1,groups:!1,checklists:!1,schedule_time:!1,completion_actions:!1},defaultWarningDays:7,rowActionStyle:"buttons_compact",access:a,budget:null,rowActionNoticePending:!1,refsInLists:!1,objectsTableColumns:void 0};function l(){let e=window;return e.__msSettingsCache??={promise:null}}function m(e){let t=l();if(t.promise)return t.promise;let s=e.connection.sendMessagePromise({type:"maintenance_supporter/settings"}).then(n=>d(n)).catch(()=>(t.promise===s&&(t.promise=null),o));return t.promise=s,s}function b(){l().promise=null}export{a,_ as b,m as c,b as d};
@@ -0,0 +1,60 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as t}from"./chunk-2ARNLSF6.js";function e(o){let r=window;r.customCards=r.customCards||[],r.customCards.some(a=>a.type===o.type)||r.customCards.push(o)}var d=t`
ha-card { overflow: hidden; }
.card-content {
padding: 16px;
display: flex; flex-direction: column;
gap: 12px;
}
.header {
display: flex; align-items: center; justify-content: space-between;
gap: 12px;
}
.title {
display: flex; align-items: center; gap: 8px;
font-size: 16px; font-weight: 500;
}
.emoji { font-size: 20px; }
/* Button family — primary action / muted-saved-state / link / icon-with-text */
.btn {
padding: 6px 12px; font-size: 13px;
border-radius: 6px; cursor: pointer;
border: 1px solid var(--divider-color);
background: var(--secondary-background-color, transparent);
color: var(--primary-text-color);
font-weight: 500;
display: inline-flex; align-items: center; gap: 4px;
}
.btn:hover { background: var(--state-icon-color, rgba(255,255,255,0.06)); }
.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.btn.primary {
background: var(--primary-color);
color: var(--text-primary-color, white);
border-color: var(--primary-color);
}
.btn.primary[disabled] { opacity: 0.6; }
.btn.muted {
background: transparent;
color: var(--secondary-text-color);
border-style: dashed;
}
.btn.muted[disabled] { opacity: 1; cursor: default; }
.btn.muted ha-icon, .btn.primary ha-icon { --mdc-icon-size: 14px; }
.btn.link {
background: transparent; border: none; padding: 6px 4px;
color: var(--primary-color); margin-left: auto;
}
.btn.link:hover { background: transparent; text-decoration: underline; }
/* Error + loading states */
.error {
padding: 8px; border-radius: 6px;
background: rgba(211, 47, 47, 0.1);
color: var(--error-color, #d32f2f); font-size: 13px;
}
.loading {
padding: 24px; text-align: center;
color: var(--secondary-text-color);
}
`;export{e as a,d as b};
@@ -0,0 +1,88 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as y,b as s,d,e as g,f as o,g as f,p as m}from"./chunk-2ARNLSF6.js";import{a,g as l}from"./chunk-3MSNNIFG.js";function $(n){let t=m("2001-02-03",n).match(/\d+/g)||[],e=v=>t.findIndex(_=>Number(_)===v),h=e(2001),c=e(2),u=e(3);return h===0?"YMD":c>=0&&u>=0&&c<u?"MDY":"DMY"}function p(n,i,t){if(n<1e3||n>9999||i<1||i>12||t<1||t>31)return null;let e=new Date(n,i-1,t);return e.getFullYear()!==n||e.getMonth()!==i-1||e.getDate()!==t?null:`${n}-${String(i).padStart(2,"0")}-${String(t).padStart(2,"0")}`}function b(n,i){let t=n.trim(),e;if(e=t.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/))return p(+e[1],+e[2],+e[3]);if(e=t.match(/^(\d{4})$/))return p(+e[1],1,1);if(e=t.match(/^(\d{1,2})[./](\d{4})$/))return p(+e[2],+e[1],1);if(e=t.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/))return p(+e[3],+e[2],+e[1]);if(e=t.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{4})$/)){let h=+e[1],c=+e[2],u=+e[3];return $(i)==="MDY"?p(u,h,c):p(u,c,h)}return null}function x(n,i){if(i)switch(n){case"date":return i.split("T")[0];case"time":return i.length===5?`${i}:00`:i;case"datetime":{let[t,e="00:00:00"]=i.split("T");return`${t} ${e.length===5?`${e}:00`:e}`}}}function k(n,i){if(typeof i!="string"||!i)return"";switch(n){case"date":return i.slice(0,10);case"time":return i.slice(0,5);case"datetime":{let[t,e="00:00:00"]=i.split(" ");return`${t}T${e.length===5?`${e}:00`:e}`}}}var r=class extends g{constructor(){super(...arguments);this.kind="date";this.label="";this.value="";this.clearable=!1;this.disabled=!1;this.required=!1;this.lang="en";this._typing=!1;this._typed="";this._typedInvalid=!1}_startTyping(){this._typed=this.value,this._typedInvalid=!1,this._typing=!0,this.updateComplete.then(()=>this.shadowRoot?.querySelector("input.typed")?.focus())}_commitTyped(){if(!this._typed.trim()){this._typing=!1;return}let t=b(this._typed,this.lang);if(!t){this._typedInvalid=!0;return}this._typing=!1,this._typedInvalid=!1,this._emit(t)}_onTypedKey(t){t.key==="Enter"?(t.preventDefault(),this._commitTyped()):t.key==="Escape"&&(t.preventDefault(),this._typing=!1,this._typedInvalid=!1)}_selector(){switch(this.kind){case"date":return{date:{}};case"time":return{time:{no_second:!0}};case"datetime":return{datetime:{}}}}_onSelectorChange(t){t.stopPropagation(),this._emit(k(this.kind,t.detail?.value))}_clear(){this._emit("")}_emit(t){t!==this.value&&(this.value=t,this.dispatchEvent(new CustomEvent("value-changed",{bubbles:!0,composed:!0,detail:{value:t}})))}render(){return s`
<div class="field">
${this.label?s`<span class="label">${this.label}${this.required?s`<span class="req">*</span>`:d}</span>`:d}
<div class="row">
${this._typing?s`<input class="typed" type="text" inputmode="numeric" autocomplete="off"
.value=${this._typed}
placeholder=${m("1978-03-15",this.lang)}
aria-invalid=${this._typedInvalid?"true":"false"}
@input=${t=>{this._typed=t.target.value,this._typedInvalid=!1}}
@keydown=${this._onTypedKey}
@blur=${this._commitTyped} />`:s`<ha-selector
.hass=${this.hass}
.selector=${this._selector()}
.value=${x(this.kind,this.value)}
.required=${this.required}
.disabled=${this.disabled}
@value-changed=${this._onSelectorChange}
></ha-selector>`}
${this.kind==="date"&&!this.disabled&&!this._typing?s`<button type="button" class="type-toggle" title=${l("date_type_toggle",this.lang)} aria-label=${l("date_type_toggle",this.lang)} @click=${this._startTyping}>
<ha-icon icon="mdi:keyboard-outline"></ha-icon>
</button>`:d}
${this.clearable&&this.value&&!this.disabled&&!this._typing?s`<button type="button" class="clear" title=${l("clear",this.lang)} aria-label=${l("clear",this.lang)} @click=${this._clear}>
<ha-icon icon="mdi:close"></ha-icon>
</button>`:d}
</div>
${this._typedInvalid?s`<span class="helper invalid">${l("date_type_invalid",this.lang)}</span>`:this.helper?s`<span class="helper">${this.helper}</span>`:d}
</div>
`}};r.styles=y`
:host { display: block; }
.field {
display: flex;
flex-direction: column;
gap: 4px;
}
.label {
font-size: 12px;
color: var(--secondary-text-color, #888);
font-weight: 500;
}
.req { color: var(--error-color, #f44336); margin-left: 2px; }
.row {
display: flex;
align-items: center;
gap: 4px;
}
ha-selector {
flex: 1;
min-width: 0;
}
.clear, .type-toggle {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
border: none;
border-radius: 50%;
background: transparent;
color: var(--secondary-text-color);
cursor: pointer;
--mdc-icon-size: 20px;
}
.clear:hover, .type-toggle:hover { background: rgba(var(--rgb-primary-text-color, 0, 0, 0), 0.08); }
.clear:focus-visible, .type-toggle:focus-visible { outline: 2px solid var(--primary-color); }
.helper {
font-size: 11px;
color: var(--secondary-text-color);
font-style: italic;
}
.helper.invalid { color: var(--error-color, #f44336); font-style: normal; }
input.typed {
flex: 1;
min-width: 0;
height: 40px;
box-sizing: border-box;
padding: 0 12px;
border: 1px solid var(--divider-color, #ccc);
border-radius: 4px;
background: var(--card-background-color, #fff);
color: var(--primary-text-color);
font: inherit;
}
input.typed[aria-invalid="true"] { border-color: var(--error-color, #f44336); }
input.typed:focus { outline: 2px solid var(--primary-color); outline-offset: -1px; }
`,a([o({attribute:!1})],r.prototype,"hass",2),a([o()],r.prototype,"kind",2),a([o()],r.prototype,"label",2),a([o()],r.prototype,"value",2),a([o()],r.prototype,"helper",2),a([o({type:Boolean})],r.prototype,"clearable",2),a([o({type:Boolean})],r.prototype,"disabled",2),a([o({type:Boolean})],r.prototype,"required",2),a([o()],r.prototype,"lang",2),a([f()],r.prototype,"_typing",2),a([f()],r.prototype,"_typed",2),a([f()],r.prototype,"_typedInvalid",2);customElements.get("ms-date-field")||customElements.define("ms-date-field",r);
File diff suppressed because one or more lines are too long
@@ -0,0 +1,137 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as v,b as y}from"./chunk-IWH6LGYJ.js";import{a as f,b as x,c as k}from"./chunk-C275ZO4V.js";import{j as b}from"./chunk-6BHUADHJ.js";import{a as h,b as a,d as c,e as g,f as _,g as o,j as m,z as l}from"./chunk-2ARNLSF6.js";import{a as n,g as i,i as u}from"./chunk-3MSNNIFG.js";var e=class extends g{constructor(){super(...arguments);this._config={type:""};this._groups={};this._loaded=!1;this._busy=!1;this._error="";this._newName="";this._editingId=null;this._editingName="";this._access=f;this._hasInitiallyLoaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return m(this.hass)}get _canWrite(){return x(this.hass?.user,this._access)}updated(t){super.updated(t),t.has("hass")&&this.hass&&!this._hasInitiallyLoaded&&(this._hasInitiallyLoaded=!0,this._load(),k(this.hass).then(r=>{this._access=r.access}),u(this._lang).then(()=>this.requestUpdate()))}async _load(){let t=await l(this,{type:"maintenance_supporter/groups"},{onError:r=>{this._error=r}});t!==void 0&&(this._groups=t?.groups||{},this._loaded=!0)}async _act(t,r){this._error="",await l(this,t,{busy:s=>{this._busy=s},reload:async()=>{r?.(),await this._load()},onError:s=>{this._error=s}})}async _addGroup(){if(!this._canWrite)return;let t=this._newName.trim();t&&await this._act({type:"maintenance_supporter/group/create",name:t},()=>{this._newName=""})}_startEdit(t){this._editingId=t,this._editingName=this._groups[t]?.name||""}async _saveEdit(){if(!this._canWrite||!this._editingId)return;let t=this._editingName.trim();t&&await this._act({type:"maintenance_supporter/group/update",group_id:this._editingId,name:t},()=>{this._editingId=null,this._editingName=""})}async _deleteGroup(t,r){!this._canWrite||!await b(this.hass,{title:i("delete",this._lang),message:i("delete_group_confirm",this._lang).replace("{name}",r),confirmText:i("delete",this._lang),danger:!0})||await this._act({type:"maintenance_supporter/group/delete",group_id:t})}_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 a`<ha-card><div class="loading">${i("loading",t)}</div></ha-card>`;let r=Object.keys(this._groups);return a`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏷️</span>
<span>${this._config.title||i("groups",t)}</span>
<span class="count">${r.length}</span>
</div>
</div>
${this._error?a`<div class="error">${this._error}</div>`:c}
${r.length===0?a`<div class="empty">${i("groups_empty",t)}</div>`:a`
<div class="group-list">
${r.map(s=>{let d=this._groups[s],$=d.task_refs?.length??0,w=this._editingId===s;return a`
<div class="group-row">
${w?a`
<input class="edit-input" type="text"
.value=${this._editingName}
?disabled=${this._busy}
@input=${p=>{this._editingName=p.target.value}}
@keydown=${p=>this._onKeyDown(p,this._saveEdit.bind(this))} />
<button class="btn small primary"
@click=${this._saveEdit}
?disabled=${this._busy||!this._editingName.trim()}>
${i("save",t)}
</button>
<button class="btn small"
@click=${()=>{this._editingId=null}}>
${i("cancel",t)}
</button>
`:a`
<span class="group-name">${d.name||"Unnamed"}</span>
<span class="task-count">${$}</span>
${this._canWrite?a`
<button class="icon-btn"
title="${i("edit",t)}"
@click=${()=>this._startEdit(s)}
?disabled=${this._busy}>
<ha-icon icon="mdi:pencil"></ha-icon>
</button>
<button class="icon-btn danger"
title="${i("delete",t)}"
@click=${()=>this._deleteGroup(s,d.name||"Unnamed")}
?disabled=${this._busy}>
<ha-icon icon="mdi:delete"></ha-icon>
</button>
`:c}
`}
</div>
`})}
</div>
`}
${this._canWrite?a`
<div class="add-row">
<input type="text"
placeholder="${i("group_new_placeholder",t)}"
.value=${this._newName}
?disabled=${this._busy}
@input=${s=>{this._newName=s.target.value}}
@keydown=${s=>this._onKeyDown(s,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)}
</button>
</div>
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_manage_tasks",t)}
</button>
`:a`
<button class="btn link" @click=${this._onDeepLink}>
${i("groups_open_panel",t)}
</button>
`}
</div>
</ha-card>
`}};e.styles=[y,h`
.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; }
`],n([_({attribute:!1})],e.prototype,"hass",2),n([o()],e.prototype,"_config",2),n([o()],e.prototype,"_groups",2),n([o()],e.prototype,"_loaded",2),n([o()],e.prototype,"_busy",2),n([o()],e.prototype,"_error",2),n([o()],e.prototype,"_newName",2),n([o()],e.prototype,"_editingId",2),n([o()],e.prototype,"_editingName",2),n([o()],e.prototype,"_access",2);customElements.get("maintenance-groups-section-card")||customElements.define("maintenance-groups-section-card",e);v({type:"maintenance-groups-section-card",name:"Maintenance Supporter \u2014 Groups",description:"Create, rename and delete task groups right on the dashboard.",preview:!1});export{e as MaintenanceGroupsSectionCard};
@@ -0,0 +1,134 @@
/*! maintenance_supporter frontend 2.95.0 */
import{a as y,b as x}from"./chunk-IWH6LGYJ.js";import"./chunk-N5LSMQW7.js";import{b as p,j as $}from"./chunk-6BHUADHJ.js";import{a as v,b as r,d as c,e as f,f as m,g as n,i as b,j as g,z as u}from"./chunk-2ARNLSF6.js";import{a as s,g as a,i as h}from"./chunk-3MSNNIFG.js";var i=class extends f{constructor(){super(...arguments);this._config={type:""};this._state=null;this._busy=!1;this._error="";this._localStart="";this._localEnd="";this._localBuffer="3";this._dirty=!1;this._loaded=!1}setConfig(t){this._config=t}getCardSize(){return 2}get _lang(){return g(this.hass)}get _isAdmin(){return this.hass?.user?.is_admin??!0}updated(t){super.updated(t),b(this,t),t.has("hass")&&this.hass&&!this._loaded&&(this._loaded=!0,this._load(),h(this._lang).then(()=>this.requestUpdate()))}async _load(){let t=await u(this,{type:"maintenance_supporter/vacation/state"},{onError:e=>{this._error=e}});t&&(this._state=t,this._localStart=t.start||"",this._localEnd=t.end||"",this._localBuffer=String(t.buffer_days??3),this._dirty=!1)}_act(t){return this._error="",u(this,t,{busy:e=>{this._busy=e},onError:e=>{this._error=e}})}async _toggleEnabled(t){let e=await this._act({type:"maintenance_supporter/vacation/update",enabled:t});e&&(this._state=e)}async _save(){if(!this._isAdmin)return;let[t,e]=p,o=Number(this._localBuffer);if(this._localBuffer.trim()===""||!Number.isInteger(o)||o<t||o>e){this._error=a("settings_value_out_of_range",this._lang).replace("{min}",String(t)).replace("{max}",String(e));return}let l=await this._act({type:"maintenance_supporter/vacation/update",start:this._localStart||null,end:this._localEnd||null,buffer_days:o});l&&(this._state=l,this._dirty=!1)}async _endNow(){if(!this._isAdmin||!await $(this.hass,{title:a("vacation_end_now",this._lang),message:a("vacation_end_now_confirm",this._lang),confirmText:a("vacation_end_now",this._lang)}))return;let e=await this._act({type:"maintenance_supporter/vacation/end_now"});e&&(this._state=e,this._localStart=e.start||"",this._localEnd=e.end||"")}_onDeepLink(){history.pushState(null,"","/maintenance-supporter?ms_action=open_vacation"),window.dispatchEvent(new CustomEvent("location-changed"))}render(){let t=this._lang,e=this._state;if(!e)return r`<ha-card><div class="loading">${a("loading",t)}</div></ha-card>`;let o=e.is_active===!0,l=e.enabled===!0,_=e.exempt_task_ids?.length??0,w=o?a("vacation_status_active",t):l?a("vacation_status_scheduled",t):a("vacation_status_inactive",t),k=o?"active":l?"scheduled":"inactive";return r`
<ha-card>
<div class="card-content">
<div class="header">
<div class="title">
<span class="emoji">🏖️</span>
<span>${this._config.title||a("vacation_mode",t)}</span>
</div>
<span class="status-pill ${k}">${w}</span>
</div>
${this._error?r`<div class="error">${this._error}</div>`:c}
${this._isAdmin?r`
<div class="row toggle-row">
<label>${a("enable",t)}</label>
<ha-switch
.checked=${l}
.disabled=${this._busy}
@change=${d=>this._toggleEnabled(d.target.checked)}
></ha-switch>
</div>
<div class="dates-row">
<div class="date-field">
<label>${a("vacation_start",t)}</label>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${t}
.value=${this._localStart}
.disabled=${this._busy}
@value-changed=${d=>{this._localStart=d.detail.value,this._dirty=!0}}
></ms-date-field>
</div>
<div class="date-field">
<label>${a("vacation_end",t)}</label>
<ms-date-field
kind="date"
clearable
.hass=${this.hass}
.lang=${t}
.value=${this._localEnd}
.disabled=${this._busy}
@value-changed=${d=>{this._localEnd=d.detail.value,this._dirty=!0}}
></ms-date-field>
</div>
<div class="date-field buffer">
<label>${a("vacation_buffer",t)}</label>
<input type="number" min=${p[0]} max=${p[1]}
.value=${this._localBuffer}
?disabled=${this._busy}
@input=${d=>{this._localBuffer=d.target.value,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?a("save",t):a("saved",t)}
</button>
${o?r`<button class="btn"
@click=${this._endNow}
?disabled=${this._busy}>
${a("vacation_end_now",t)}
</button>`:c}
${_>0?r`<button class="btn link"
@click=${this._onDeepLink}>
${_} ${a("vacation_exempt_count",t)}…
</button>`:r`<button class="btn link"
@click=${this._onDeepLink}>
${a("vacation_advanced",t)}
</button>`}
</div>
`:r`
<div class="readonly">
${l&&e.start&&e.end?r`<div>${e.start} → ${e.end}</div>`:c}
<button class="btn link" @click=${this._onDeepLink}>
${a("vacation_open_panel",t)}
</button>
</div>
`}
</div>
</ha-card>
`}};i.styles=[x,v`
.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; }
`],s([m({attribute:!1})],i.prototype,"hass",2),s([n()],i.prototype,"_config",2),s([n()],i.prototype,"_state",2),s([n()],i.prototype,"_busy",2),s([n()],i.prototype,"_error",2),s([n()],i.prototype,"_localStart",2),s([n()],i.prototype,"_localEnd",2),s([n()],i.prototype,"_localBuffer",2),s([n()],i.prototype,"_dirty",2);customElements.get("maintenance-vacation-section-card")||customElements.define("maintenance-vacation-section-card",i);y({type:"maintenance-vacation-section-card",name:"Maintenance Supporter \u2014 Vacation",description:"Switch vacation mode on or off and set its dates right on the dashboard.",preview:!1});export{i as MaintenanceVacationSectionCard};