diff --git a/assets/js/components/Energyflow/Energyflow.vue b/assets/js/components/Energyflow/Energyflow.vue index 857044bf9d..c34a56cedb 100644 --- a/assets/js/components/Energyflow/Energyflow.vue +++ b/assets/js/components/Energyflow/Energyflow.vue @@ -18,7 +18,8 @@ :pvProduction="pvProduction" :homePower="homePower" :batterySoc="batterySoc" - :powerInKw="powerInKw" + :powerInKw="visualizationFormat.kw" + :powerDigits="visualizationFormat.digits" :vehicleIcons="vehicleIcons" /> @@ -74,14 +75,16 @@ icon="sun" :power="pvProduction" :powerTooltip="pvTooltip" - :powerInKw="powerInKw" + :powerInKw="entryFormat.kw" + :powerDigits="entryFormat.digits" /> = 1000; + visualizationFormat: function () { + const max = Math.max(this.gridImport, this.selfPv, this.selfBattery, this.pvExport); + const kw = this.inKw(max); + const digits = kw ? this.digitsKw(max) : 0; + return { kw, digits }; + }, + entryFormat: function () { + const max = Math.max( + this.gridImport, + this.pvProduction, + this.batteryDischarge, + this.homePower, + this.loadpointsPower, + this.pvExport, + this.batteryCharge + ); + const kw = this.inKw(max); + const digits = kw ? this.digitsKw(max) : 0; + return { kw, digits }; }, inPower: function () { return this.gridImport + this.pvProduction + this.batteryDischarge; @@ -285,7 +310,9 @@ export default { if (!Array.isArray(this.pv) || this.pv.length <= 1) { return; } - return this.pv.map(({ power }) => this.fmtKw(power, this.powerInKw)); + return this.pv.map(({ power }) => + this.fmtKw(power, this.entryFormat.kw, true, this.entryFormat.digits) + ); }, batteryFmt() { return (soc) => `${Math.round(soc)}%`; @@ -328,7 +355,7 @@ export default { return this.fmtPricePerKWh(value, this.currency, true); }, kw: function (watt) { - return this.fmtKw(watt, this.powerInKw); + return this.fmtKw(watt, this.entryFormat.kw, true, this.entryFormat.digits); }, toggleDetails: function () { this.updateHeight(); diff --git a/assets/js/components/Energyflow/EnergyflowEntry.vue b/assets/js/components/Energyflow/EnergyflowEntry.vue index 0a055aef4a..4dcf96ad2e 100644 --- a/assets/js/components/Energyflow/EnergyflowEntry.vue +++ b/assets/js/components/Energyflow/EnergyflowEntry.vue @@ -48,6 +48,7 @@ export default { power: { type: Number }, powerTooltip: { type: Array }, powerInKw: { type: Boolean }, + powerDigits: { type: Number }, soc: { type: Number }, details: { type: Number }, detailsFmt: { type: Function }, @@ -86,6 +87,12 @@ export default { this.$refs.powerNumber.forceUpdate(); } }, + powerDigits(newVal, oldVal) { + // force update if digits change but not the value + if (newVal !== oldVal) { + this.$refs.powerNumber.forceUpdate(); + } + }, }, mounted: function () { this.updatePowerTooltip(); @@ -93,7 +100,7 @@ export default { }, methods: { kw: function (watt) { - return this.fmtKw(watt, this.powerInKw); + return this.fmtKw(watt, this.powerInKw, true, this.powerDigits); }, updatePowerTooltip() { this.powerTooltipInstance = this.updateTooltip( diff --git a/assets/js/components/Energyflow/Visualization.vue b/assets/js/components/Energyflow/Visualization.vue index a5f90fab64..aa8ffbe978 100644 --- a/assets/js/components/Energyflow/Visualization.vue +++ b/assets/js/components/Energyflow/Visualization.vue @@ -116,6 +116,7 @@ export default { homePower: { type: Number, default: 0 }, batterySoc: { type: Number, default: 0 }, powerInKw: { type: Boolean, default: false }, + powerDigits: { type: Number, default: 0 }, }, data: function () { return { width: 0 }; @@ -171,7 +172,7 @@ export default { return ""; } const withUnit = this.enoughSpaceForUnit(watt); - return this.fmtKw(watt, this.powerInKw, withUnit); + return this.fmtKw(watt, this.powerInKw, withUnit, this.powerDigits); }, powerLabelAvailableSpace(power) { if (this.totalAdjusted === 0) return 0; @@ -182,7 +183,7 @@ export default { return this.powerLabelAvailableSpace(power) > 40; }, enoughSpaceForUnit(power) { - return this.powerLabelAvailableSpace(power) > 60; + return this.powerLabelAvailableSpace(power) > 80; }, hideLabelIcon(power, minWidth = 32) { if (this.totalAdjusted === 0) return true; diff --git a/assets/js/components/Loadpoint.vue b/assets/js/components/Loadpoint.vue index e8374af872..7bdbb7f477 100644 --- a/assets/js/components/Loadpoint.vue +++ b/assets/js/components/Loadpoint.vue @@ -315,12 +315,10 @@ export default { api.delete(this.apiPath("vehicle")); }, fmtPower(value) { - const inKw = value == 0 || value >= 1000; - return this.fmtKw(value, inKw); + return this.fmtKw(value, this.inKw(value)); }, fmtEnergy(value) { - const inKw = value == 0 || value >= 1000; - return this.fmtKWh(value, inKw); + return this.fmtKWh(value, this.inKw(value)); }, }, }; diff --git a/assets/js/components/LoadpointSettingsModal.vue b/assets/js/components/LoadpointSettingsModal.vue index 2789a6eb15..7f1733659a 100644 --- a/assets/js/components/LoadpointSettingsModal.vue +++ b/assets/js/components/LoadpointSettingsModal.vue @@ -220,7 +220,7 @@ export default { return this.maxPowerPhases(this.phasesConfigured); } } - return this.fmtKw(this.maxCurrent * V * this.phasesActive); + return this.fmtKw(this.maxCurrent * V * this.phasesActive, true, true, 1); }, minPower: function () { if (this.chargerPhases1p3p) { @@ -231,7 +231,7 @@ export default { return this.minPowerPhases(this.phasesConfigured); } } - return this.fmtKw(this.minCurrent * V * this.phasesActive); + return this.fmtKw(this.minCurrent * V * this.phasesActive, true, true, 1); }, minCurrentOptions: function () { const opt1 = [...range(Math.floor(this.maxCurrent), 1), 0.5, 0.25, 0.125]; @@ -279,10 +279,10 @@ export default { }, methods: { maxPowerPhases: function (phases) { - return this.fmtKw(this.maxCurrent * V * phases); + return this.fmtKw(this.maxCurrent * V * phases, true, true, 1); }, minPowerPhases: function (phases) { - return this.fmtKw(this.minCurrent * V * phases); + return this.fmtKw(this.minCurrent * V * phases, true, true, 1); }, formId: function (name) { return `loadpoint_${this.id}_${name}`; diff --git a/assets/js/mixins/formatter.js b/assets/js/mixins/formatter.js index df8c57eeb9..89bc454a88 100644 --- a/assets/js/mixins/formatter.js +++ b/assets/js/mixins/formatter.js @@ -33,9 +33,15 @@ export default { val = Math.abs(val); return val >= this.fmtLimit ? this.round(val / 1e3, this.fmtDigits) : this.round(val, 0); }, + inKw: function (watt) { + return watt === 0 || watt >= 1000; + }, + digitsKw: function (watt) { + return watt < 10000 ? 2 : 1; + }, fmtKw: function (watt = 0, kw = true, withUnit = true, digits) { if (digits === undefined) { - digits = kw ? 1 : 0; + digits = kw ? this.digitsKw(watt) : 0; } const value = kw ? watt / 1000 : watt; let unit = ""; diff --git a/assets/js/mixins/formatter.test.js b/assets/js/mixins/formatter.test.js index 8ebfefe881..bb983152c2 100644 --- a/assets/js/mixins/formatter.test.js +++ b/assets/js/mixins/formatter.test.js @@ -12,14 +12,14 @@ const fmt = mount({ describe("fmtkW", () => { test("should format kW and W", () => { - expect(fmt.fmtKw(0, true)).eq("0,0 kW"); - expect(fmt.fmtKw(1200, true)).eq("1,2 kW"); + expect(fmt.fmtKw(0, true)).eq("0,00 kW"); + expect(fmt.fmtKw(1200, true)).eq("1,20 kW"); expect(fmt.fmtKw(0, false)).eq("0 W"); expect(fmt.fmtKw(1200, false)).eq("1.200 W"); }); test("should format without unit", () => { - expect(fmt.fmtKw(0, true, false)).eq("0,0"); - expect(fmt.fmtKw(1200, true, false)).eq("1,2"); + expect(fmt.fmtKw(0, true, false)).eq("0,00"); + expect(fmt.fmtKw(1200, true, false)).eq("1,20"); expect(fmt.fmtKw(0, false, false)).eq("0"); expect(fmt.fmtKw(1200, false, false)).eq("1.200"); }); @@ -32,8 +32,8 @@ describe("fmtkW", () => { describe("fmtKWh", () => { test("should format with units", () => { - expect(fmt.fmtKWh(1200)).eq("1,2 kWh"); - expect(fmt.fmtKWh(1200, true)).eq("1,2 kWh"); + expect(fmt.fmtKWh(1200)).eq("1,20 kWh"); + expect(fmt.fmtKWh(1200, true)).eq("1,20 kWh"); expect(fmt.fmtKWh(1200, false)).eq("1.200 Wh"); expect(fmt.fmtKWh(1200, false, false)).eq("1.200"); }); diff --git a/assets/js/views/ChargingSessions.vue b/assets/js/views/ChargingSessions.vue index c842193755..be63e3fa01 100644 --- a/assets/js/views/ChargingSessions.vue +++ b/assets/js/views/ChargingSessions.vue @@ -299,7 +299,7 @@ export default { unit: "kWh", total: this.chargedEnergy, value: (session) => session.chargedEnergy, - format: (value) => this.fmtKWh(value * 1e3, true, false), + format: (value) => this.fmtKWh(value * 1e3, true, false, 2), }, { name: "solar", diff --git a/tests/basics.spec.js b/tests/basics.spec.js index 490d7c795e..6511fad449 100644 --- a/tests/basics.spec.js +++ b/tests/basics.spec.js @@ -20,7 +20,7 @@ test.describe("main screen", async () => { test("visualization", async ({ page }) => { const locator = page.getByTestId("visualization"); await expect(locator).toBeVisible(); - await expect(locator).toContainText("1.0 kW"); + await expect(locator).toContainText("1.00 kW"); }); test("one loadpoint", async ({ page }) => { diff --git a/tests/config.spec.js b/tests/config.spec.js index 6edeccb0f6..f563df2999 100644 --- a/tests/config.spec.js +++ b/tests/config.spec.js @@ -177,7 +177,7 @@ test.describe("meters", async () => { await expect(meterModal.getByRole("button", { name: "Validate & save" })).toBeVisible(); await meterModal.getByRole("link", { name: "validate" }).click(); await expect(meterModal.getByText("SoC: 75.0%")).toBeVisible(); - await expect(meterModal.getByText("Power: -2.5 kW")).toBeVisible(); + await expect(meterModal.getByText("Power: -2.50 kW")).toBeVisible(); await meterModal.getByRole("button", { name: "Save" }).click(); await expect(page.getByTestId("battery")).toBeVisible(1); await expect(page.getByTestId("battery")).toContainText("openems"); @@ -190,14 +190,14 @@ test.describe("meters", async () => { await expect(page.getByTestId("battery")).toBeVisible(1); await expect(page.getByTestId("battery")).toContainText("openems"); await expect(page.getByTestId("battery").getByText("SoC: 75.0%")).toBeVisible(); - await expect(page.getByTestId("battery").getByText("Power: -2.5 kW")).toBeVisible(); + await expect(page.getByTestId("battery").getByText("Power: -2.50 kW")).toBeVisible(); await expect(page.getByTestId("battery").getByText("Capacity: 20.0 kWh")).toBeVisible(); // restart and check in main ui await restart(CONFIG_EMPTY); await page.goto("/"); await page.getByTestId("visualization").click(); - await expect(page.getByTestId("energyflow")).toContainText("Battery charging75%2.5 kW"); + await expect(page.getByTestId("energyflow")).toContainText("Battery charging75%2.50 kW"); // delete #1 await page.goto("/#/config");