Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
56 changes: 36 additions & 20 deletions custom_components/smart_climate/smart-climate-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class SmartClimateCard extends LitElement {
return { entity: "" };
}

// ── Component lifecycle / state ─────────────────────────────────────────

setConfig(config) {
if (!config.entity) {
throw new Error("Entity required");
Expand Down Expand Up @@ -79,43 +81,43 @@ class SmartClimateCard extends LitElement {
<div class="temp-control">
<div class="temp-control-label">Temperatuur instellen</div>
<div class="temp-control-row">
<button class="temp-btn" @click=${() => this.adjustTemp(-0.5)}>−</button>
<button class="temp-btn" @click=${() => this._adjustTemp(-0.5)}>−</button>
<span class="temp-control-value">${this._overrideTemp}°</span>
<button class="temp-btn" @click=${() => this.adjustTemp(0.5)}>+</button>
<button class="temp-btn" @click=${() => this._adjustTemp(0.5)}>+</button>
</div>
</div>

${isLeaving ? html`
<div class="away-delay-row">
<span class="away-delay-icon">🚶</span>
<span class="away-delay-value countdown">${this.formatSeconds(awayDelayRemaining)}</span>
<span class="away-delay-value countdown">${this._formatSeconds(awayDelayRemaining)}</span>
<span class="away-delay-label">tot afwezig</span>
</div>
` : ""}

${isTimer ? html`
<div class="timer-row">
<span class="timer-icon">⏱</span>
<span class="timer-value countdown">${this.formatTime(remaining)}</span>
<span class="timer-value countdown">${this._formatTime(remaining)}</span>
<span class="timer-label">resterend</span>
<button class="restore-btn" @click=${() => this.clearOverride()}>Herstel schema</button>
<button class="restore-btn" @click=${() => this._clearOverride()}>Herstel schema</button>
</div>
` : ""}

${isInfinity ? html`
<div class="timer-row">
<span class="timer-icon">♾</span>
<span class="timer-label">Infinity actief</span>
<button class="restore-btn" @click=${() => this.clearOverride()}>Herstel schema</button>
<button class="restore-btn" @click=${() => this._clearOverride()}>Herstel schema</button>
</div>
` : ""}

${isNextNode ? html`
<div class="timer-row">
<span class="timer-icon">📅</span>
<span class="timer-value countdown">${this.formatTime(remaining)}</span>
<span class="timer-value countdown">${this._formatTime(remaining)}</span>
<span class="timer-label">tot volgend node</span>
<button class="restore-btn" @click=${() => this.clearOverride()}>Herstel schema</button>
<button class="restore-btn" @click=${() => this._clearOverride()}>Herstel schema</button>
</div>
` : ""}

Expand All @@ -136,17 +138,17 @@ class SmartClimateCard extends LitElement {
max="480"
step="15"
.value=${sliderValue}
@input=${this.onSliderInput}
@change=${this.onSliderChange}
@input=${this._onSliderInput}
@change=${this._onSliderChange}
/>
${nextNodeDotPct != null ? html`
<div class="next-node-dot"
style="--dot-pos: ${nextNodeDotPct.toFixed(1)}%"
@click=${() => this.onNextNodeDotClick()}
@click=${() => this._onNextNodeDotClick()}
title="Override till next node (${nextNodeMinutes}m)">◆</div>
` : ""}
<div class="infinity-indicator"
@click=${() => this.onInfinityDotClick()}
@click=${() => this._onInfinityDotClick()}
title="Override infinity">∞</div>
</div>

Expand All @@ -164,7 +166,9 @@ class SmartClimateCard extends LitElement {
`;
}

onSliderChange(e) {
// ── Event handlers / service calls ──────────────────────────────────────

_onSliderChange(e) {
const value = Number(e.target.value);
const entityId = this.config.entity;

Expand All @@ -186,33 +190,33 @@ class SmartClimateCard extends LitElement {
}
}

onSliderInput(e) {
_onSliderInput(e) {
const value = Number(e.target.value);
const slider = e.target;
slider.style.setProperty("--slider-value", `${(value / 480) * 100}%`);
}

onNextNodeDotClick() {
_onNextNodeDotClick() {
this.hass.callService("smart_climate", "set_override_next_node", {
entity_id: this.config.entity,
temperature: this._overrideTemp,
});
}

onInfinityDotClick() {
_onInfinityDotClick() {
this.hass.callService("smart_climate", "set_override_infinity", {
entity_id: this.config.entity,
temperature: this._overrideTemp,
});
}

clearOverride() {
_clearOverride() {
this.hass.callService("smart_climate", "clear_override", {
entity_id: this.config.entity,
});
}

adjustTemp(delta) {
_adjustTemp(delta) {
const newTemp = Math.min(25, Math.max(5, this._overrideTemp + delta));
this._overrideTemp = newTemp;
const entityId = this.config.entity;
Expand Down Expand Up @@ -244,14 +248,26 @@ class SmartClimateCard extends LitElement {
}
}

formatSeconds(seconds) {
// ── Formatters ──────────────────────────────────────────────────────────

/**
* Format a duration in seconds to a human-readable string (e.g. "2m 30s").
* @param {number} seconds
* @returns {string}
*/
_formatSeconds(seconds) {
const total = Math.max(0, seconds);
const m = Math.floor(total / 60);
const rem = Math.ceil(total % 60);
return m ? `${m}m ${rem}s` : `${rem}s`;
}

formatTime(minutes) {
/**
* Format a duration in minutes to a human-readable string (e.g. "1h 30m").
* @param {number} minutes
* @returns {string}
*/
_formatTime(minutes) {
if (minutes < 60) return `${minutes}m`;
const h = Math.floor(minutes / 60);
const m = minutes % 60;
Expand Down
16 changes: 16 additions & 0 deletions custom_components/smart_climate/smart-climate-config-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class SmartClimateConfigCard extends LitElement {
return { entity: "" };
}

// ── Component lifecycle / state ─────────────────────────────────────────

setConfig(config) {
if (!config.entity) {
throw new Error("Entity required");
Expand Down Expand Up @@ -175,12 +177,26 @@ class SmartClimateConfigCard extends LitElement {
`;
}

// ── Config helpers / service calls ──────────────────────────────────────

/**
* Adjust a numeric config property by *delta*, clamped to [min, max].
* @param {string} prop Name of the reactive property to update.
* @param {number} delta Amount to add (may be negative).
* @param {number} min Minimum allowed value.
* @param {number} max Maximum allowed value.
*/
_adjust(prop, delta, min, max) {
const current = this[prop] ?? 0;
this[prop] = Math.min(max, Math.max(min, Math.round((current + delta) * 10) / 10));
this._dirty = true;
}

/**
* Format a duration in minutes to a human-readable string (e.g. "1h 30m").
* @param {number} minutes
* @returns {string}
*/
_formatDuration(minutes) {
if (minutes < 60) return `${minutes}m`;
const h = Math.floor(minutes / 60);
Expand Down