diff --git a/assets/css/app.css b/assets/css/app.css index 997593c5a..d450fc91a 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -501,6 +501,10 @@ small { overflow: hidden; text-overflow: ellipsis; } +.form-select.disabled, +.form-select:disabled { + background-color: var(--bs-secondary-bg); +} .dark .dropdown-menu { box-shadow: 0 0 8px var(--evcc-background); @@ -515,6 +519,7 @@ small { color: var(--bs-gray-medium) !important; } +.dark .form-select.disabled, .dark .form-select:disabled { opacity: 0.5; } diff --git a/assets/js/components/Tariff/SmartCostLimit.vue b/assets/js/components/Tariff/SmartCostLimit.vue index e04f9fe3c..f492c5e63 100644 --- a/assets/js/components/Tariff/SmartCostLimit.vue +++ b/assets/js/components/Tariff/SmartCostLimit.vue @@ -73,10 +73,12 @@ export default defineComponent({ // Smart cost: charge when costs are below or equal to limit return value <= this.currentLimit; }, - async saveLimit(limit: number) { + async saveLimit(limit: number, active: boolean) { // save last selected value to be suggest again when reactivating limit this.saveLastLimit(limit); + if (!active) return; + const url = this.isLoadpoint ? `loadpoints/${this.loadpointId}/smartcostlimit` : "batterygridchargelimit"; diff --git a/assets/js/components/Tariff/SmartFeedInPriority.vue b/assets/js/components/Tariff/SmartFeedInPriority.vue index 4a1ffc5af..370d1f7a9 100644 --- a/assets/js/components/Tariff/SmartFeedInPriority.vue +++ b/assets/js/components/Tariff/SmartFeedInPriority.vue @@ -65,10 +65,12 @@ export default defineComponent({ // Smart feed-in priority: pause when rates are above or equal to limit return value >= this.currentLimit; }, - async saveLimit(limit: number) { + async saveLimit(limit: number, active: boolean) { // save last selected value to be suggest again when reactivating limit this.saveLastLimit(limit); + if (!active) return; + const url = `loadpoints/${this.loadpointId}/smartfeedinprioritylimit`; await api.post(`${url}/${encodeURIComponent(limit)}`); }, diff --git a/assets/js/components/Tariff/SmartTariffBase.vue b/assets/js/components/Tariff/SmartTariffBase.vue index de3af6cde..dfe2f2663 100644 --- a/assets/js/components/Tariff/SmartTariffBase.vue +++ b/assets/js/components/Tariff/SmartTariffBase.vue @@ -27,7 +27,7 @@ :id="formId" v-model.number="selectedLimit" class="form-select form-select-sm" - :disabled="!active" + :class="{ disabled: !active }" :aria-label="limitLabel" @change="changeLimit" > @@ -74,7 +74,8 @@ @@ -205,42 +206,10 @@ export default defineComponent({ return { min, max }; }, slots(): Slot[] { - if (!this.rates?.length) { - return []; - } - - const rates = this.rates; - const quarterHour = 15 * 60 * 1000; - - const base = new Date(); - base.setSeconds(0, 0); - base.setMinutes(base.getMinutes() - (base.getMinutes() % 15)); - - return Array.from({ length: 96 * 4 }, (_, i) => { - const start = new Date(base.getTime() + quarterHour * i); - const end = new Date(start.getTime() + quarterHour); - const value = this.findRateInRange(start, end, rates)?.value; - const active = - this.limitDirection === "below" && - this.currentLimit !== null && - value !== undefined && - value <= this.currentLimit; - const warning = - this.limitDirection === "above" && - this.currentLimit !== null && - value !== undefined && - value >= this.currentLimit; - - return { - day: this.weekdayShort(start), - value, - start, - end, - charging: active, - selectable: value !== undefined, - warning, - }; - }); + return this.slotsForLimit(this.currentLimit); + }, + chartSlots(): Slot[] { + return this.slotsForLimit(this.currentLimit ?? this.selectedLimit); }, totalSlots() { return this.slots.filter((s) => s.value !== undefined); @@ -356,16 +325,53 @@ export default defineComponent({ } return this.fmtPricePerKWh(value, this.currency, true); }, + slotsForLimit(limit: number | null): Slot[] { + if (!this.rates?.length) { + return []; + } + + const rates = this.rates; + const quarterHour = 15 * 60 * 1000; + + const base = new Date(); + base.setSeconds(0, 0); + base.setMinutes(base.getMinutes() - (base.getMinutes() % 15)); + + return Array.from({ length: 96 * 4 }, (_, i) => { + const start = new Date(base.getTime() + quarterHour * i); + const end = new Date(start.getTime() + quarterHour); + const value = this.findRateInRange(start, end, rates)?.value; + const active = + this.limitDirection === "below" && + limit !== null && + value !== undefined && + value <= limit; + const warning = + this.limitDirection === "above" && + limit !== null && + value !== undefined && + value >= limit; + + return { + day: this.weekdayShort(start), + value, + start, + end, + charging: active, + selectable: value !== undefined, + warning, + }; + }); + }, slotHovered(index: number) { this.activeIndex = index; }, slotSelected(index: number) { - const value = this.slots[index]?.value; + const value = this.chartSlots[index]?.value; if (value !== undefined) { // 3 decimal precision const valueRounded = Math.ceil(value * 1000) / 1000; this.selectedLimit = valueRounded; - this.active = true; this.saveLimit(valueRounded); } }, @@ -374,20 +380,19 @@ export default defineComponent({ this.saveLimit(value); }, toggleActive($event: Event) { - const active = ($event.target as HTMLInputElement).checked; - if (active) { + this.active = ($event.target as HTMLInputElement).checked; + if (this.active) { this.saveLimit(this.lastLimit); } else { this.resetLimit(); } - this.active = active; if (this.applyAll) { this.applyToAllVisible = true; } }, saveLimit(limit: number) { - this.$emit("save-limit", limit); - if (this.applyAll) { + this.$emit("save-limit", limit, this.active); + if (this.applyAll && this.active) { this.applyToAllVisible = true; } }, diff --git a/assets/js/components/Tariff/TariffChart.vue b/assets/js/components/Tariff/TariffChart.vue index 0e3d3f446..c2cbca1e6 100644 --- a/assets/js/components/Tariff/TariffChart.vue +++ b/assets/js/components/Tariff/TariffChart.vue @@ -1,6 +1,9 @@