diff --git a/.editorconfig b/.editorconfig index 635e420c2..7a754a73d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,6 @@ indent_size = 4 [*.css] indent_size = 2 -[*.{js,html,yml,yaml,json,md}] +[*.{js,html,yml,yaml,json,md,ts}] indent_style = space indent_size = 2 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..7020916a6 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +tests/custom-css.css \ No newline at end of file diff --git a/assets/js/api.ts b/assets/js/api.ts index 275447021..363931179 100644 --- a/assets/js/api.ts +++ b/assets/js/api.ts @@ -7,63 +7,61 @@ const base = protocol + "//" + hostname + (port ? ":" + port : "") + pathname; // override the way axios serializes arrays in query parameters (a=1&a=2&a=3 instead of a[]=1&a[]=2&a[]=3) function customParamsSerializer(params: { [key: string]: any }) { - return Object.keys(params) - .filter((key) => params[key] !== null) - .map((key) => { - const value = params[key]; - if (Array.isArray(value)) { - return value - .map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`) - .join("&"); - } - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - }) - .join("&"); + return Object.keys(params) + .filter((key) => params[key] !== null) + .map((key) => { + const value = params[key]; + if (Array.isArray(value)) { + return value.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join("&"); + } + return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; + }) + .join("&"); } const api = axios.create({ - baseURL: base + "api/", - headers: { - Accept: "application/json", - }, - paramsSerializer: customParamsSerializer, + baseURL: base + "api/", + headers: { + Accept: "application/json", + }, + paramsSerializer: customParamsSerializer, }); // global error handling api.interceptors.response.use( - (response) => response, - (error) => { - // handle unauthorized errors - if (error.response?.status === 401) { - openLoginModal(); - return Promise.reject(error); - } + (response) => response, + (error) => { + // handle unauthorized errors + if (error.response?.status === 401) { + openLoginModal(); + return Promise.reject(error); + } - const message = [`${error.message}.`]; - if (error.response?.data?.error) { - message.push(`${error.response.data.error}.`); - } - if (error.config) { - const method = error.config.method.toUpperCase(); - const url = error.request.responseURL; - message.push(`${method} ${url}`); - } - window.app.raise({ message }); - return Promise.reject(error); - } + const message = [`${error.message}.`]; + if (error.response?.data?.error) { + message.push(`${error.response.data.error}.`); + } + if (error.config) { + const method = error.config.method.toUpperCase(); + const url = error.request.responseURL; + message.push(`${method} ${url}`); + } + window.app.raise({ message }); + return Promise.reject(error); + } ); export default api; export const i18n = axios.create({ - baseURL: base + "i18n/", - headers: { - Accept: "application/json", - }, + baseURL: base + "i18n/", + headers: { + Accept: "application/json", + }, }); export const allowClientError = { - validateStatus(status: number) { - return status >= 200 && status < 500; - }, + validateStatus(status: number) { + return status >= 200 && status < 500; + }, }; diff --git a/assets/js/app.ts b/assets/js/app.ts index 325aa466e..ac3811f68 100644 --- a/assets/js/app.ts +++ b/assets/js/app.ts @@ -12,58 +12,58 @@ import type { Notification } from "./types/evcc"; // lazy load smoothscroll polyfill. mainly for safari < 15.4 if (!window.CSS.supports("scroll-behavior", "smooth")) { - console.log("no native smoothscroll support. polyfilling..."); - import("smoothscroll-polyfill").then((module) => { - module.polyfill(); - }); + console.log("no native smoothscroll support. polyfilling..."); + import("smoothscroll-polyfill").then((module) => { + module.polyfill(); + }); } const app = createApp( - defineComponent({ - data() { - return { notifications: [] as Notification[], offline: false }; - }, - watch: { - offline(value) { - console.log(`we are ${value ? "offline" : "online"}`); - }, - }, - methods: { - raise(msg: Notification) { - if (this.offline) return; - if (!msg.level) msg.level = "error"; - const now = new Date(); - const latestMsg = this.notifications[0]; - if (latestMsg && latestMsg.message === msg.message && latestMsg.lp === msg.lp) { - latestMsg.count++; - latestMsg.time = now; - } else { - this.notifications = [ - { - ...msg, - count: 1, - time: now, - }, - ...this.notifications, - ]; - } - }, - clear() { - this.notifications = []; - }, - setOnline() { - this.offline = false; - sendToApp({ type: "online" }); - }, - setOffline() { - this.offline = true; - sendToApp({ type: "offline" }); - }, - }, - render() { - return h(App, { notifications: this.notifications, offline: this.offline }); - }, - }) + defineComponent({ + data() { + return { notifications: [] as Notification[], offline: false }; + }, + watch: { + offline(value) { + console.log(`we are ${value ? "offline" : "online"}`); + }, + }, + methods: { + raise(msg: Notification) { + if (this.offline) return; + if (!msg.level) msg.level = "error"; + const now = new Date(); + const latestMsg = this.notifications[0]; + if (latestMsg && latestMsg.message === msg.message && latestMsg.lp === msg.lp) { + latestMsg.count++; + latestMsg.time = now; + } else { + this.notifications = [ + { + ...msg, + count: 1, + time: now, + }, + ...this.notifications, + ]; + } + }, + clear() { + this.notifications = []; + }, + setOnline() { + this.offline = false; + sendToApp({ type: "online" }); + }, + setOffline() { + this.offline = true; + sendToApp({ type: "offline" }); + }, + }, + render() { + return h(App, { notifications: this.notifications, offline: this.offline }); + }, + }) ); const i18n = setupI18n(); @@ -80,8 +80,8 @@ watchThemeChanges(); appDetection(); if (window.evcc.customCss === "true") { - const link = document.createElement("link"); - link.href = `./custom.css`; - link.rel = "stylesheet"; - document.head.appendChild(link); + const link = document.createElement("link"); + link.href = `./custom.css`; + link.rel = "stylesheet"; + document.head.appendChild(link); } diff --git a/assets/js/colors.ts b/assets/js/colors.ts index 649598ae9..c684ead4a 100644 --- a/assets/js/colors.ts +++ b/assets/js/colors.ts @@ -8,79 +8,79 @@ import { reactive } from "vue"; // const COLORS = [ "#0077B6FF", "#00B4D8FF", "#90E0EFFF", "#40A578FF", "#9DDE8BFF", "#F8961EFF", "#F9C74FFF", "#E6FF94FF"]; const colors: { - text: string | null; - muted: string | null; - border: string | null; - self: string | null; - grid: string | null; - co2PerKWh: string | null; - pricePerKWh: string | null; - price: string | null; - co2: string | null; - background: string | null; - light: string | null; - selfPalette: string[]; - palette: string[]; + text: string | null; + muted: string | null; + border: string | null; + self: string | null; + grid: string | null; + co2PerKWh: string | null; + pricePerKWh: string | null; + price: string | null; + co2: string | null; + background: string | null; + light: string | null; + selfPalette: string[]; + palette: string[]; } = reactive({ - text: null, - muted: null, - border: null, - self: null, - grid: null, - co2PerKWh: null, - pricePerKWh: null, - price: null, - co2: null, - background: null, - light: null, - selfPalette: ["#0FDE41FF", "#FFBD2FFF", "#FD6158FF", "#03C1EFFF", "#0F662DFF", "#FF922EFF"], - palette: [ - "#03C1EFFF", - "#FD6158FF", - "#31AB4AFF", - "#41517AFF", - "#FF922EFF", - "#0F662DFF", - "#0470D4FF", - "#FFBD2FFF", - "#77C93EFF", - "#4E1D10FF", - "#0AAFBFFF", - "#813504FF", - ], + text: null, + muted: null, + border: null, + self: null, + grid: null, + co2PerKWh: null, + pricePerKWh: null, + price: null, + co2: null, + background: null, + light: null, + selfPalette: ["#0FDE41FF", "#FFBD2FFF", "#FD6158FF", "#03C1EFFF", "#0F662DFF", "#FF922EFF"], + palette: [ + "#03C1EFFF", + "#FD6158FF", + "#31AB4AFF", + "#41517AFF", + "#FF922EFF", + "#0F662DFF", + "#0470D4FF", + "#FFBD2FFF", + "#77C93EFF", + "#4E1D10FF", + "#0AAFBFFF", + "#813504FF", + ], }); export const dimColor = (color: string | null) => { - return color?.toLowerCase().replace(/ff$/, "20"); + return color?.toLowerCase().replace(/ff$/, "20"); }; export const lighterColor = (color: string | null) => { - return color?.toLowerCase().replace(/ff$/, "aa"); + return color?.toLowerCase().replace(/ff$/, "aa"); }; export const fullColor = (color: string | null) => { - return color?.toLowerCase().replace(/20$/, "ff"); + return color?.toLowerCase().replace(/20$/, "ff"); }; function updateCssColors() { - const style = window.getComputedStyle(document.documentElement); - colors.text = style.getPropertyValue("--evcc-default-text"); - colors.muted = style.getPropertyValue("--bs-gray-medium"); - colors.border = style.getPropertyValue("--bs-border-color-translucent"); - colors.self = style.getPropertyValue("--evcc-self"); - colors.grid = style.getPropertyValue("--evcc-grid"); - colors.price = style.getPropertyValue("--evcc-price"); - colors.co2 = style.getPropertyValue("--evcc-co2"); - colors.background = style.getPropertyValue("--evcc-background"); - colors.pricePerKWh = style.getPropertyValue("--bs-gray-medium"); - colors.co2PerKWh = style.getPropertyValue("--bs-gray-medium"); - colors.light = style.getPropertyValue("--bs-gray-light"); + const style = window.getComputedStyle(document.documentElement); + colors.text = style.getPropertyValue("--evcc-default-text"); + colors.muted = style.getPropertyValue("--bs-gray-medium"); + colors.border = style.getPropertyValue("--bs-border-color-translucent"); + colors.self = style.getPropertyValue("--evcc-self"); + colors.grid = style.getPropertyValue("--evcc-grid"); + colors.price = style.getPropertyValue("--evcc-price"); + colors.co2 = style.getPropertyValue("--evcc-co2"); + colors.background = style.getPropertyValue("--evcc-background"); + colors.pricePerKWh = style.getPropertyValue("--bs-gray-medium"); + colors.co2PerKWh = style.getPropertyValue("--bs-gray-medium"); + colors.light = style.getPropertyValue("--bs-gray-light"); } // update colors on theme change const darkModeMatcher = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)"); if (darkModeMatcher && darkModeMatcher.addEventListener) { - darkModeMatcher.addEventListener("change", updateCssColors); + darkModeMatcher.addEventListener("change", updateCssColors); } // initialize colors updateCssColors(); diff --git a/assets/js/components/Auth/auth.ts b/assets/js/components/Auth/auth.ts index d49554a7b..ab13381a8 100644 --- a/assets/js/components/Auth/auth.ts +++ b/assets/js/components/Auth/auth.ts @@ -5,101 +5,101 @@ import Modal from "bootstrap/js/dist/modal"; import { isSystemError } from "@/utils/fatal.js"; const auth = reactive({ - configured: true, - loggedIn: null as boolean | null, // true / false / null (unknown) - nextUrl: null as string | null, // url to navigate to after login - nextModal: null as Modal | null, // modal instance to show after login + configured: true, + loggedIn: null as boolean | null, // true / false / null (unknown) + nextUrl: null as string | null, // url to navigate to after login + nextModal: null as Modal | null, // modal instance to show after login }); export async function updateAuthStatus() { - if (store.state.offline || isSystemError(store.state.fatal)) { - // system not ready, skip auth check - return; - } + if (store.state.offline || isSystemError(store.state.fatal)) { + // system not ready, skip auth check + return; + } - try { - const res = await api.get("/auth/status", { - validateStatus: (code) => [200, 501, 500].includes(code), - }); - if (res.status === 501) { - auth.configured = false; - } - if (res.status === 200) { - auth.configured = true; - auth.loggedIn = res.data === true; - } - if (res.status === 500) { - auth.loggedIn = null; - console.log("unable to fetch auth status", res); - } - } catch (e) { - console.log("unable to fetch auth status", e); - } + try { + const res = await api.get("/auth/status", { + validateStatus: (code) => [200, 501, 500].includes(code), + }); + if (res.status === 501) { + auth.configured = false; + } + if (res.status === 200) { + auth.configured = true; + auth.loggedIn = res.data === true; + } + if (res.status === 500) { + auth.loggedIn = null; + console.log("unable to fetch auth status", res); + } + } catch (e) { + console.log("unable to fetch auth status", e); + } } export async function logout() { - try { - await api.post("/auth/logout"); - await updateAuthStatus(); - auth.nextUrl = null; - } catch (e) { - console.log("unable to logout", e); - } + try { + await api.post("/auth/logout"); + await updateAuthStatus(); + auth.nextUrl = null; + } catch (e) { + console.log("unable to logout", e); + } } export function isLoggedIn() { - return auth.loggedIn === true; + return auth.loggedIn === true; } export function statusUnknown() { - return auth.loggedIn === null; + return auth.loggedIn === null; } export function isConfigured() { - return auth.configured; + return auth.configured; } export function getAndClearNextUrl() { - const nextUrl = auth.nextUrl; - auth.nextUrl = null; - return nextUrl; + const nextUrl = auth.nextUrl; + auth.nextUrl = null; + return nextUrl; } export function getAndClearNextModal() { - const nextModal = auth.nextModal; - auth.nextModal = null; - return nextModal; + const nextModal = auth.nextModal; + auth.nextModal = null; + return nextModal; } export function openLoginModal(nextUrl: string | null = null, nextModal: Modal | null = null) { - auth.nextUrl = nextUrl; - auth.nextModal = nextModal; - const modal = Modal.getOrCreateInstance(document.getElementById("loginModal") as HTMLElement); - modal.show(); + auth.nextUrl = nextUrl; + auth.nextModal = nextModal; + const modal = Modal.getOrCreateInstance(document.getElementById("loginModal") as HTMLElement); + modal.show(); } // show/hide password modal based on auth status watch( - () => auth.configured, - (configured) => { - console.log("configured", configured); - const modal = Modal.getOrCreateInstance( - document.getElementById("passwordModal") as HTMLElement - ); - if (configured) { - modal.hide(); - } else { - modal.show(); - } - } + () => auth.configured, + (configured) => { + console.log("configured", configured); + const modal = Modal.getOrCreateInstance( + document.getElementById("passwordModal") as HTMLElement + ); + if (configured) { + modal.hide(); + } else { + modal.show(); + } + } ); let timeoutId: number | undefined = undefined; function debounedUpdateAuthStatus() { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - updateAuthStatus(); - }, 500) as unknown as number; + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + updateAuthStatus(); + }, 500) as unknown as number; } // update auth status on reconnect or server restart diff --git a/assets/js/components/ChargingPlans/ChargingPlan.stories.ts b/assets/js/components/ChargingPlans/ChargingPlan.stories.ts index 945d57676..dc7b56710 100644 --- a/assets/js/components/ChargingPlans/ChargingPlan.stories.ts +++ b/assets/js/components/ChargingPlans/ChargingPlan.stories.ts @@ -2,55 +2,55 @@ import ChargingPlan from "./ChargingPlan.vue"; import type { Meta, StoryFn } from "@storybook/vue3"; const hoursFromNow = (hours: number) => { - const now = new Date(); - now.setHours(now.getHours() + hours); - return now.toISOString(); + const now = new Date(); + now.setHours(now.getHours() + hours); + return now.toISOString(); }; export default { - title: "ChargingPlans/ChargingPlan", - component: ChargingPlan, - parameters: { - layout: "centered", - }, - argTypes: { - id: { control: "text" }, - effectivePlanTime: { control: "text" }, - effectivePlanSoc: { control: "number" }, - planEnergy: { control: "number" }, - socBasedCharging: { control: "boolean" }, - socBasedPlanning: { control: "boolean" }, - vehicleSoc: { control: "number" }, - }, + title: "ChargingPlans/ChargingPlan", + component: ChargingPlan, + parameters: { + layout: "centered", + }, + argTypes: { + id: { control: "text" }, + effectivePlanTime: { control: "text" }, + effectivePlanSoc: { control: "number" }, + planEnergy: { control: "number" }, + socBasedCharging: { control: "boolean" }, + socBasedPlanning: { control: "boolean" }, + vehicleSoc: { control: "number" }, + }, } as Meta; const Template: StoryFn = (args) => ({ - components: { ChargingPlan }, - setup() { - return { args }; - }, - template: '', + components: { ChargingPlan }, + setup() { + return { args }; + }, + template: '', }); export const None = Template.bind({}); None.args = { - id: "1", + id: "1", }; export const SocBasedCharging = Template.bind({}); SocBasedCharging.args = { - id: "1", - effectivePlanTime: hoursFromNow(4), - effectivePlanSoc: 77, - socBasedCharging: true, - socBasedPlanning: true, + id: "1", + effectivePlanTime: hoursFromNow(4), + effectivePlanSoc: 77, + socBasedCharging: true, + socBasedPlanning: true, }; export const EnergyBasedCharging = Template.bind({}); EnergyBasedCharging.args = { - id: "1", - effectivePlanTime: hoursFromNow(12), - planEnergy: 77, - socBasedCharging: false, - socBasedPlanning: false, + id: "1", + effectivePlanTime: hoursFromNow(12), + planEnergy: 77, + socBasedCharging: false, + socBasedPlanning: false, }; diff --git a/assets/js/components/ChargingPlans/Preview.stories.ts b/assets/js/components/ChargingPlans/Preview.stories.ts index 40ba06e7d..07c8cddab 100644 --- a/assets/js/components/ChargingPlans/Preview.stories.ts +++ b/assets/js/components/ChargingPlans/Preview.stories.ts @@ -5,100 +5,99 @@ import type { StoryFn } from "@storybook/vue3"; const now = new Date(); function createDate(hoursFromNow: number) { - const result = new Date(now.getTime()); - result.setHours(result.getHours() + hoursFromNow); - return result; + const result = new Date(now.getTime()); + result.setHours(result.getHours() + hoursFromNow); + return result; } function createRate(value: number, hoursFromNow: number, durationHours = 1): Rate { - const start = new Date(now.getTime()); - start.setHours(start.getHours() + hoursFromNow); - start.setMinutes(0); - start.setSeconds(0); - start.setMilliseconds(0); - const end = new Date(start.getTime()); - end.setHours(start.getHours() + durationHours); - end.setMinutes(0); - end.setSeconds(0); - end.setMilliseconds(0); - return { start, end, value }; + const start = new Date(now.getTime()); + start.setHours(start.getHours() + hoursFromNow); + start.setMinutes(0); + start.setSeconds(0); + start.setMilliseconds(0); + const end = new Date(start.getTime()); + end.setHours(start.getHours() + durationHours); + end.setMinutes(0); + end.setSeconds(0); + end.setMilliseconds(0); + return { start, end, value }; } // Scenario data const co2Data = { - rates: [ - 545, 518, 545, 518, 0, 545, 527, 527, 536, 518, 400, 336, 336, 339, 344, 336, 336, 336, 372, - 400, 555, 555, 545, 555, 564, 545, 555, 545, 536, 545, 527, 536, 518, 545, 509, 336, 336, - 336, - ].map((value, i) => createRate(value, i)), - duration: 8695, - plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], - smartCostType: "co2", - targetTime: createDate(14), + rates: [ + 545, 518, 545, 518, 0, 545, 527, 527, 536, 518, 400, 336, 336, 339, 344, 336, 336, 336, 372, + 400, 555, 555, 545, 555, 564, 545, 555, 545, 536, 545, 527, 536, 518, 545, 509, 336, 336, 336, + ].map((value, i) => createRate(value, i)), + duration: 8695, + plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], + smartCostType: "co2", + targetTime: createDate(14), }; const fixedData = { - rates: [createRate(0.442, 0, 50)], - duration: 8695, - plan: [createRate(0.442, 12, 3)], - smartCostType: "price", - currency: CURRENCY.EUR, - targetTime: createDate(14), + rates: [createRate(0.442, 0, 50)], + duration: 8695, + plan: [createRate(0.442, 12, 3)], + smartCostType: "price", + currency: CURRENCY.EUR, + targetTime: createDate(14), }; const zonedData = { - rates: [ - createRate(3.72, 0, 4), - createRate(2.39, 4, 12), - createRate(3.72, 16, 12), - createRate(2.39, 28, 12), - createRate(3.72, 40, 12), - ], - duration: 8695, - plan: [createRate(2.39, 13, 3)], - smartCostType: "price", - currency: CURRENCY.DKK, - targetTime: createDate(17), + rates: [ + createRate(3.72, 0, 4), + createRate(2.39, 4, 12), + createRate(3.72, 16, 12), + createRate(2.39, 28, 12), + createRate(3.72, 40, 12), + ], + duration: 8695, + plan: [createRate(2.39, 13, 3)], + smartCostType: "price", + currency: CURRENCY.DKK, + targetTime: createDate(17), }; const unknownData = { - rates: co2Data.rates.slice(0, 16), - duration: 8695, - plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], - smartCostType: "co2", - targetTime: createDate(14), + rates: co2Data.rates.slice(0, 16), + duration: 8695, + plan: [createRate(213, 4), createRate(336, 11), createRate(336, 12)], + smartCostType: "co2", + targetTime: createDate(14), }; const dynamicData = { - rates: [ - 0.12, 0.15, 0, -0.05, -0.11, -0.24, -0.08, 0.12, 0.25, 0.29, 0.22, 0.31, 0.31, 0.33, - ].map((value, i) => createRate(value, i)), - duration: 8695, - plan: [createRate(0.23, 2, 5)], - smartCostType: "price", - currency: CURRENCY.EUR, - targetTime: createDate(13), + rates: [0.12, 0.15, 0, -0.05, -0.11, -0.24, -0.08, 0.12, 0.25, 0.29, 0.22, 0.31, 0.31, 0.33].map( + (value, i) => createRate(value, i) + ), + duration: 8695, + plan: [createRate(0.23, 2, 5)], + smartCostType: "price", + currency: CURRENCY.EUR, + targetTime: createDate(13), }; export default { - title: "ChargingPlans/Preview", - component: Preview, - argTypes: { - rates: { control: "object" }, - duration: { control: "number" }, - plan: { control: "object" }, - smartCostType: { control: "select", options: ["co2", "price"] }, - currency: { control: "text" }, - targetTime: { control: "text" }, - }, + title: "ChargingPlans/Preview", + component: Preview, + argTypes: { + rates: { control: "object" }, + duration: { control: "number" }, + plan: { control: "object" }, + smartCostType: { control: "select", options: ["co2", "price"] }, + currency: { control: "text" }, + targetTime: { control: "text" }, + }, }; const Template: StoryFn = (args) => ({ - components: { Preview }, - setup() { - return { args }; - }, - template: '', + components: { Preview }, + setup() { + return { args }; + }, + template: '', }); export const Co2 = Template.bind({}); diff --git a/assets/js/components/ChargingPlans/Preview.test.ts b/assets/js/components/ChargingPlans/Preview.test.ts index beb681b33..273028001 100644 --- a/assets/js/components/ChargingPlans/Preview.test.ts +++ b/assets/js/components/ChargingPlans/Preview.test.ts @@ -7,134 +7,134 @@ config.global.mocks["$i18n"] = { locale: "de-DE" }; config.global.mocks["$t"] = (a: any) => a; describe("basics", () => { - const DATE_START = new Date("2023-01-11T11:00:00+01:00"); - const DATE_TARGET = new Date("2023-01-11T13:00:00+01:00"); - const TARIFF_FIXED = [ - { - start: new Date("2023-01-11T11:00:00+01:00"), - end: new Date("2023-01-22T00:00:00+01:00"), - value: 0.4, - }, - ]; - const PLAN = [ - { - start: new Date("2023-01-11T12:00:00+01:00"), - end: new Date("2023-01-11T13:00:00+01:00"), - value: 0.2, - }, - ]; + const DATE_START = new Date("2023-01-11T11:00:00+01:00"); + const DATE_TARGET = new Date("2023-01-11T13:00:00+01:00"); + const TARIFF_FIXED = [ + { + start: new Date("2023-01-11T11:00:00+01:00"), + end: new Date("2023-01-22T00:00:00+01:00"), + value: 0.4, + }, + ]; + const PLAN = [ + { + start: new Date("2023-01-11T12:00:00+01:00"), + end: new Date("2023-01-11T13:00:00+01:00"), + value: 0.2, + }, + ]; - const wrapper = mount(Preview, { - props: { - plan: PLAN, - targetTime: DATE_TARGET, - rates: TARIFF_FIXED, - }, - }); - wrapper.setData({ startTime: DATE_START }); + const wrapper = mount(Preview, { + props: { + plan: PLAN, + targetTime: DATE_TARGET, + rates: TARIFF_FIXED, + }, + }); + wrapper.setData({ startTime: DATE_START }); - let result: Slot[]; + let result: Slot[]; - beforeAll(() => { - result = wrapper.vm.slots; - }); + beforeAll(() => { + result = wrapper.vm.slots; + }); - test("should return 39 slots", () => { - expect(result.length).eq(39); - }); + test("should return 39 slots", () => { + expect(result.length).eq(39); + }); - test("slots should be an hour apart", () => { - expect(result[0].startHour).eq(11); - expect(result[0].endHour).eq(12); - expect(result[0].day).eq("Mi"); + test("slots should be an hour apart", () => { + expect(result[0].startHour).eq(11); + expect(result[0].endHour).eq(12); + expect(result[0].day).eq("Mi"); - expect(result[1].startHour).eq(12); - expect(result[1].endHour).eq(13); - expect(result[1].day).eq("Mi"); + expect(result[1].startHour).eq(12); + expect(result[1].endHour).eq(13); + expect(result[1].day).eq("Mi"); - expect(result[11].startHour).eq(22); - expect(result[11].endHour).eq(23); - expect(result[11].day).eq("Mi"); + expect(result[11].startHour).eq(22); + expect(result[11].endHour).eq(23); + expect(result[11].day).eq("Mi"); - expect(result[24].startHour).eq(11); - expect(result[24].endHour).eq(12); - expect(result[24].day).eq("Do"); - }); + expect(result[24].startHour).eq(11); + expect(result[24].endHour).eq(12); + expect(result[24].day).eq("Do"); + }); - test("slots after target should be toLate", () => { - expect(result[0].toLate).eq(false); - expect(result[1].toLate).eq(false); - expect(result[2].toLate).eq(true); - expect(result[3].toLate).eq(true); - }); + test("slots after target should be toLate", () => { + expect(result[0].toLate).eq(false); + expect(result[1].toLate).eq(false); + expect(result[2].toLate).eq(true); + expect(result[3].toLate).eq(true); + }); - test("slots are marked if charging is happening in them", () => { - expect(result[0].charging).eq(false); - expect(result[1].charging).eq(true); - expect(result[2].charging).eq(false); - expect(result[3].charging).eq(false); - }); + test("slots are marked if charging is happening in them", () => { + expect(result[0].charging).eq(false); + expect(result[1].charging).eq(true); + expect(result[2].charging).eq(false); + expect(result[3].charging).eq(false); + }); - test("all slots have the same fixed value", () => { - result.forEach((slot) => expect(slot.value).eq(0.4)); - }); + test("all slots have the same fixed value", () => { + result.forEach((slot) => expect(slot.value).eq(0.4)); + }); }); describe("zoned tariffs", () => { - const DATE_START = new Date("2023-01-11T11:00:00+01:00"); - const DATE_TARGET = new Date("2023-01-11T16:00:00+01:00"); - const TARIFF_ZONED = [ - { - start: new Date("2023-01-11T11:00:00+01:00"), - end: new Date("2023-01-11T12:00:00+01:00"), - value: 0.2, - }, - { - start: new Date("2023-01-11T12:00:00+01:00"), - end: new Date("2023-01-22T00:00:00+01:00"), - value: 0.4, - }, - ]; - const PLAN = [ - { - start: new Date("2023-01-11T11:30:00+01:00"), - end: new Date("2023-01-11T13:00:00+01:00"), - value: 0.3, - }, - { - start: new Date("2023-01-11T14:30:00+01:00"), - end: new Date("2023-01-11T16:00:00+01:00"), - value: 0.2, - }, - ]; + const DATE_START = new Date("2023-01-11T11:00:00+01:00"); + const DATE_TARGET = new Date("2023-01-11T16:00:00+01:00"); + const TARIFF_ZONED = [ + { + start: new Date("2023-01-11T11:00:00+01:00"), + end: new Date("2023-01-11T12:00:00+01:00"), + value: 0.2, + }, + { + start: new Date("2023-01-11T12:00:00+01:00"), + end: new Date("2023-01-22T00:00:00+01:00"), + value: 0.4, + }, + ]; + const PLAN = [ + { + start: new Date("2023-01-11T11:30:00+01:00"), + end: new Date("2023-01-11T13:00:00+01:00"), + value: 0.3, + }, + { + start: new Date("2023-01-11T14:30:00+01:00"), + end: new Date("2023-01-11T16:00:00+01:00"), + value: 0.2, + }, + ]; - let result: Slot[]; + let result: Slot[]; - const wrapper = mount(Preview, { - props: { - plan: PLAN, - targetTime: DATE_TARGET, - rates: TARIFF_ZONED, - }, - }); - wrapper.setData({ startTime: DATE_START }); + const wrapper = mount(Preview, { + props: { + plan: PLAN, + targetTime: DATE_TARGET, + rates: TARIFF_ZONED, + }, + }); + wrapper.setData({ startTime: DATE_START }); - beforeAll(() => { - result = wrapper.vm.slots; - }); + beforeAll(() => { + result = wrapper.vm.slots; + }); - test("handle multiple charging slots", () => { - expect(result[0].charging).eq(true); - expect(result[1].charging).eq(true); - expect(result[2].charging).eq(false); - expect(result[3].charging).eq(true); - expect(result[4].charging).eq(true); - expect(result[5].charging).eq(false); - }); + test("handle multiple charging slots", () => { + expect(result[0].charging).eq(true); + expect(result[1].charging).eq(true); + expect(result[2].charging).eq(false); + expect(result[3].charging).eq(true); + expect(result[4].charging).eq(true); + expect(result[5].charging).eq(false); + }); - test("first slot is cheap, others are expensive", () => { - const [first, ...others] = result; - expect(first.value).eq(0.2); - others.forEach((slot) => expect(slot.value).eq(0.4)); - }); + test("first slot is cheap, others are expensive", () => { + const [first, ...others] = result; + expect(first.value).eq(0.2); + others.forEach((slot) => expect(slot.value).eq(0.4)); + }); }); diff --git a/assets/js/components/ChargingPlans/types.d.ts b/assets/js/components/ChargingPlans/types.d.ts index 6115a395c..b12b806ae 100644 --- a/assets/js/components/ChargingPlans/types.d.ts +++ b/assets/js/components/ChargingPlans/types.d.ts @@ -1,37 +1,37 @@ import type { Rate } from "@/types/evcc"; export interface RepeatingPlan { - weekdays: number[]; - time: string; - tz: string; // timezone like "Europe/Berlin" - soc: number; - active: boolean; - precondition: number; + weekdays: number[]; + time: string; + tz: string; // timezone like "Europe/Berlin" + soc: number; + active: boolean; + precondition: number; } export interface PlanWrapper { - planId: number; - planTime: Date; - duration: number; - plan: Rate[]; - power: number; + planId: number; + planTime: Date; + duration: number; + plan: Rate[]; + power: number; } export interface PlanResponse { - status: number; - data: { result: PlanWrapper }; + status: number; + data: { result: PlanWrapper }; } export type StaticPlan = StaticSocPlan | StaticEnergyPlan; export interface StaticSocPlan { - soc: number; - time: Date; - precondition: number; + soc: number; + time: Date; + precondition: number; } export interface StaticEnergyPlan { - energy: number; - time: Date; - precondition: number; + energy: number; + time: Date; + precondition: number; } diff --git a/assets/js/components/Config/DeviceModal/index.ts b/assets/js/components/Config/DeviceModal/index.ts index 6fa63c350..87b1425c4 100644 --- a/assets/js/components/Config/DeviceModal/index.ts +++ b/assets/js/components/Config/DeviceModal/index.ts @@ -2,128 +2,128 @@ import type { DeviceType } from "@/types/evcc"; import api from "@/api"; export type Product = { - group: string; - name: string; - template: string; + group: string; + name: string; + template: string; }; export type Template = { - Params: TemplateParam[]; - Requirements: { - Description: string; - }; + Params: TemplateParam[]; + Requirements: { + Description: string; + }; }; export type TemplateType = "vehicle" | "battery" | "grid" | "pv" | "charger" | "aux" | "ext"; export type TemplateParam = { - Name: string; - Required: boolean; - Advanced: boolean; - Deprecated: boolean; - Default?: string | number | boolean; - Choice?: string[]; - Usages?: TemplateType[]; + Name: string; + Required: boolean; + Advanced: boolean; + Deprecated: boolean; + Default?: string | number | boolean; + Choice?: string[]; + Usages?: TemplateType[]; }; export type ModbusCapability = "rs485" | "tcpip"; export type ModbusParam = TemplateParam & { - ID?: string; - Comset?: string; - Baudrate?: number; - Port?: number; + ID?: string; + Comset?: string; + Baudrate?: number; + Port?: number; }; export type DeviceValues = { - type: ConfigType; - icon: string | undefined; - deviceProduct: string | undefined; - yaml: string | undefined; - template: string | null; - [key: string]: any; + type: ConfigType; + icon: string | undefined; + deviceProduct: string | undefined; + yaml: string | undefined; + template: string | null; + [key: string]: any; }; export function handleError(e: any, msg: string) { - console.error(e); - let message = msg; - const { error } = e.response.data || {}; - if (error) message += `: ${error}`; - alert(message); + console.error(e); + let message = msg; + const { error } = e.response.data || {}; + if (error) message += `: ${error}`; + alert(message); } export const timeout = 15000; export enum ConfigType { - Template = "template", - Custom = "custom", + Template = "template", + Custom = "custom", } export function applyDefaultsFromTemplate(template: Template | null, values: DeviceValues) { - const params = template?.Params || []; - params - .filter((p) => p.Default && !values[p.Name]) - .forEach((p) => { - values[p.Name] = p.Default; - }); + const params = template?.Params || []; + params + .filter((p) => p.Default && !values[p.Name]) + .forEach((p) => { + values[p.Name] = p.Default; + }); } export function createDeviceUtils(deviceType: DeviceType) { - function test(id: number | undefined, data: any) { - let url = `config/test/${deviceType}`; - if (id !== undefined) { - url += `/merge/${id}`; - } - return api.post(url, data, { timeout }); - } + function test(id: number | undefined, data: any) { + let url = `config/test/${deviceType}`; + if (id !== undefined) { + url += `/merge/${id}`; + } + return api.post(url, data, { timeout }); + } - function update(id: number, data: any) { - return api.put(`config/devices/${deviceType}/${id}`, data); - } + function update(id: number, data: any) { + return api.put(`config/devices/${deviceType}/${id}`, data); + } - function remove(id: number) { - return api.delete(`config/devices/${deviceType}/${id}`); - } + function remove(id: number) { + return api.delete(`config/devices/${deviceType}/${id}`); + } - async function load(id: number) { - const response = await api.get(`config/devices/${deviceType}/${id}`); - return response.data.result; - } + async function load(id: number) { + const response = await api.get(`config/devices/${deviceType}/${id}`); + return response.data.result; + } - async function create(data: any) { - const response = await api.post(`config/devices/${deviceType}`, data); - return response.data.result; - } + async function create(data: any) { + const response = await api.post(`config/devices/${deviceType}`, data); + return response.data.result; + } - async function loadProducts(lang?: string, usage?: string) { - const params: Record = { lang }; - if (usage) { - params["usage"] = usage; - } - const response = await api.get(`config/products/${deviceType}`, { params }); - return response.data.result; - } + async function loadProducts(lang?: string, usage?: string) { + const params: Record = { lang }; + if (usage) { + params["usage"] = usage; + } + const response = await api.get(`config/products/${deviceType}`, { params }); + return response.data.result; + } - async function loadTemplate(templateName: string, lang?: string) { - if (!templateName) return null; + async function loadTemplate(templateName: string, lang?: string) { + if (!templateName) return null; - const opts = { - params: { - lang, - name: templateName, - }, - }; - const response = await api.get(`config/templates/${deviceType}`, opts); - return response.data.result; - } + const opts = { + params: { + lang, + name: templateName, + }, + }; + const response = await api.get(`config/templates/${deviceType}`, opts); + return response.data.result; + } - return { - test, - update, - remove, - load, - create, - loadProducts, - loadTemplate, - }; + return { + test, + update, + remove, + load, + create, + loadProducts, + loadTemplate, + }; } diff --git a/assets/js/components/Config/utils/test.ts b/assets/js/components/Config/utils/test.ts index ac838bb7b..21e850426 100644 --- a/assets/js/components/Config/utils/test.ts +++ b/assets/js/components/Config/utils/test.ts @@ -1,55 +1,55 @@ import type { AxiosResponse } from "axios"; export type TestState = { - isUnknown: boolean; - isSuccess: boolean; - isError: boolean; - isRunning: boolean; - result: Record | null; - error: string | null; - errorLine: number | null; + isUnknown: boolean; + isSuccess: boolean; + isError: boolean; + isRunning: boolean; + result: Record | null; + error: string | null; + errorLine: number | null; }; export const initialTestState = (): TestState => ({ - isUnknown: true, - isSuccess: false, - isError: false, - isRunning: false, - result: null, - error: null, - errorLine: null, + isUnknown: true, + isSuccess: false, + isError: false, + isRunning: false, + result: null, + error: null, + errorLine: null, }); export const performTest = async ( - state: TestState, - api: () => Promise>, - form: HTMLElement | undefined + state: TestState, + api: () => Promise>, + form: HTMLElement | undefined ) => { - if (form && !(form as HTMLFormElement).reportValidity()) return false; - state.isUnknown = false; - state.isSuccess = false; - state.isError = false; - state.isRunning = true; - state.errorLine = null; - try { - const res = await api(); - for (const [key, value] of Object.entries(res.data.result)) { - const { error } = value as { error?: string }; - if (error) { - state.isError = true; - state.error = `${key}: ${error}`; - return false; - } - } - state.isSuccess = true; - state.result = res.data.result; - return true; - } catch (e: any) { - state.isError = true; - state.error = e.response?.data?.error || e.message; - state.errorLine = e.response?.data?.line || null; - } finally { - state.isRunning = false; - } - return false; + if (form && !(form as HTMLFormElement).reportValidity()) return false; + state.isUnknown = false; + state.isSuccess = false; + state.isError = false; + state.isRunning = true; + state.errorLine = null; + try { + const res = await api(); + for (const [key, value] of Object.entries(res.data.result)) { + const { error } = value as { error?: string }; + if (error) { + state.isError = true; + state.error = `${key}: ${error}`; + return false; + } + } + state.isSuccess = true; + state.result = res.data.result; + return true; + } catch (e: any) { + state.isError = true; + state.error = e.response?.data?.error || e.message; + state.errorLine = e.response?.data?.line || null; + } finally { + state.isRunning = false; + } + return false; }; diff --git a/assets/js/components/Energyflow/BatteryIcon.stories.ts b/assets/js/components/Energyflow/BatteryIcon.stories.ts index 9c8d0e67a..4cb3aecd2 100644 --- a/assets/js/components/Energyflow/BatteryIcon.stories.ts +++ b/assets/js/components/Energyflow/BatteryIcon.stories.ts @@ -2,23 +2,23 @@ import BatteryIcon from "./BatteryIcon.vue"; import type { Meta, StoryFn } from "@storybook/vue3"; export default { - title: "Energyflow/BatteryIcon", - component: BatteryIcon, - parameters: { - layout: "centered", - }, + title: "Energyflow/BatteryIcon", + component: BatteryIcon, + parameters: { + layout: "centered", + }, } as Meta; const Template: StoryFn = (args) => { - const story = () => ({ - components: { BatteryIcon }, - setup() { - return { args }; - }, - template: '', - }); - story.args = args; - return story; + const story = () => ({ + components: { BatteryIcon }, + setup() { + return { args }; + }, + template: '', + }); + story.args = args; + return story; }; export const Empty = Template.bind({}); diff --git a/assets/js/components/Energyflow/Energyflow.stories.ts b/assets/js/components/Energyflow/Energyflow.stories.ts index 5613b235d..926327856 100644 --- a/assets/js/components/Energyflow/Energyflow.stories.ts +++ b/assets/js/components/Energyflow/Energyflow.stories.ts @@ -3,412 +3,412 @@ import Energyflow from "./Energyflow.vue"; import { CURRENCY } from "@/types/evcc"; export default { - title: "Energyflow/Energyflow", - component: Energyflow, + title: "Energyflow/Energyflow", + component: Energyflow, } as Meta; const Template: StoryFn = (args) => ({ - components: { Energyflow }, - setup() { - return { args }; - }, - template: '', + components: { Energyflow }, + setup() { + return { args }; + }, + template: '', }); export const GridAndPV = Template.bind({}); GridAndPV.args = { - gridConfigured: true, - pvConfigured: true, - pvPower: 7300, - gridPower: -2300, - homePower: 800, - loadpointsCompact: [ - { - power: 1000, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1000, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 1000, - icon: "bike", - charging: true, - title: "Garage", - chargePower: 1000, - connected: true, - index: 1, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 2200, - icon: "car", - charging: true, - title: "Garage", - chargePower: 2200, - connected: true, - index: 2, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], - tariffGrid: 0.25, - tariffFeedIn: 0.08, - smartCostType: "price", - currency: CURRENCY.EUR, - pv: [{ power: 5000 }, { power: 2300 }], - forecast: { - solar: { - today: { energy: 1000, complete: true }, - tomorrow: { energy: 1000, complete: false }, - dayAfterTomorrow: { energy: 1000, complete: false }, - }, - }, + gridConfigured: true, + pvConfigured: true, + pvPower: 7300, + gridPower: -2300, + homePower: 800, + loadpointsCompact: [ + { + power: 1000, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1000, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 1000, + icon: "bike", + charging: true, + title: "Garage", + chargePower: 1000, + connected: true, + index: 1, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 2200, + icon: "car", + charging: true, + title: "Garage", + chargePower: 2200, + connected: true, + index: 2, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], + tariffGrid: 0.25, + tariffFeedIn: 0.08, + smartCostType: "price", + currency: CURRENCY.EUR, + pv: [{ power: 5000 }, { power: 2300 }], + forecast: { + solar: { + today: { energy: 1000, complete: true }, + tomorrow: { energy: 1000, complete: false }, + dayAfterTomorrow: { energy: 1000, complete: false }, + }, + }, }; export const BatteryAndGrid = Template.bind({}); BatteryAndGrid.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 0, - gridPower: 1200, - homePower: 2000, - batteryPower: 800, - batterySoc: 77, - tariffGrid: 0.25, - tariffFeedIn: 0.08, - currency: CURRENCY.EUR, - battery: [ - { soc: 44.999, capacity: 13.3, power: 350, controllable: true }, - { soc: 82.3331, capacity: 21, power: 450, controllable: false }, - ], + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 0, + gridPower: 1200, + homePower: 2000, + batteryPower: 800, + batterySoc: 77, + tariffGrid: 0.25, + tariffFeedIn: 0.08, + currency: CURRENCY.EUR, + battery: [ + { soc: 44.999, capacity: 13.3, power: 350, controllable: true }, + { soc: 82.3331, capacity: 21, power: 450, controllable: false }, + ], }; export const BatteryCharging = Template.bind({}); BatteryCharging.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 5000, - gridPower: -1300, - homePower: 800, - loadpointsCompact: [ - { - power: 1400, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1400, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], - batteryPower: -1500, - batterySoc: 75, + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 5000, + gridPower: -1300, + homePower: 800, + loadpointsCompact: [ + { + power: 1400, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1400, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], + batteryPower: -1500, + batterySoc: 75, }; export const GridPVAndBattery = Template.bind({}); GridPVAndBattery.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 1000, - gridPower: 700, - homePower: 3300, - batteryPower: 1500, - batterySoc: 30, + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 1000, + gridPower: 700, + homePower: 3300, + batteryPower: 1500, + batterySoc: 30, }; export const BatteryThresholds = Template.bind({}); BatteryThresholds.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 8700, - gridPower: -500, - loadpointsCompact: [ - { - power: 5000, - icon: "car", - charging: true, - title: "Garage", - chargePower: 5000, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 2500, - icon: "bus", - charging: true, - title: "Garage", - chargePower: 2500, - connected: true, - index: 1, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], - batteryPower: -700, - batterySoc: 95, + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 8700, + gridPower: -500, + loadpointsCompact: [ + { + power: 5000, + icon: "car", + charging: true, + title: "Garage", + chargePower: 5000, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 2500, + icon: "bus", + charging: true, + title: "Garage", + chargePower: 2500, + connected: true, + index: 1, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], + batteryPower: -700, + batterySoc: 95, }; export const PVThresholds = Template.bind({}); PVThresholds.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 300, - gridPower: 6500, - homePower: 1000, - loadpointsCompact: [ - { - power: 5000, - icon: "car", - charging: true, - title: "Garage", - chargePower: 5000, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 1600, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1600, - connected: true, - index: 1, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], - batteryPower: 800, - batterySoc: 76, + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 300, + gridPower: 6500, + homePower: 1000, + loadpointsCompact: [ + { + power: 5000, + icon: "car", + charging: true, + title: "Garage", + chargePower: 5000, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 1600, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1600, + connected: true, + index: 1, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], + batteryPower: 800, + batterySoc: 76, }; export const GridOnly = Template.bind({}); GridOnly.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 0, - gridPower: 6500, - homePower: 1000, - loadpointsCompact: [ - { - power: 5500, - icon: "car", - charging: true, - title: "Garage", - chargePower: 5500, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 0, - icon: "car", - charging: false, - title: "Garage", - chargePower: 0, - connected: false, - index: 1, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 0, - icon: "car", - charging: false, - title: "Garage", - chargePower: 0, - connected: false, - index: 2, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 0, - icon: "car", - charging: false, - title: "Garage", - chargePower: 0, - connected: false, - index: 3, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], - batteryPower: 0, - batterySoc: 0, + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 0, + gridPower: 6500, + homePower: 1000, + loadpointsCompact: [ + { + power: 5500, + icon: "car", + charging: true, + title: "Garage", + chargePower: 5500, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 0, + icon: "car", + charging: false, + title: "Garage", + chargePower: 0, + connected: false, + index: 1, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 0, + icon: "car", + charging: false, + title: "Garage", + chargePower: 0, + connected: false, + index: 2, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 0, + icon: "car", + charging: false, + title: "Garage", + chargePower: 0, + connected: false, + index: 3, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], + batteryPower: 0, + batterySoc: 0, }; export const LowPower = Template.bind({}); LowPower.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 700, - gridPower: -300, - homePower: 300, - batteryPower: -100, - batterySoc: 55, - tariffGrid: 0.25, - tariffFeedIn: 0.08, - currency: CURRENCY.EUR, + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 700, + gridPower: -300, + homePower: 300, + batteryPower: -100, + batterySoc: 55, + tariffGrid: 0.25, + tariffFeedIn: 0.08, + currency: CURRENCY.EUR, }; export const CO2 = Template.bind({}); CO2.args = { - gridConfigured: true, - pvConfigured: true, - pvPower: 7300, - gridPower: -2300, - homePower: 800, - loadpointsCompact: [ - { - power: 1000, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1000, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 1000, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1000, - connected: true, - index: 1, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - { - power: 2200, - icon: "car", - charging: true, - title: "Garage", - chargePower: 2200, - connected: true, - index: 2, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], - tariffGrid: 0.25, - tariffFeedIn: 0.08, - tariffCo2: 723, - smartCostType: "co2", - currency: CURRENCY.EUR, - pv: [{ power: 5000 }, { power: 2300 }], + gridConfigured: true, + pvConfigured: true, + pvPower: 7300, + gridPower: -2300, + homePower: 800, + loadpointsCompact: [ + { + power: 1000, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1000, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 1000, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1000, + connected: true, + index: 1, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + { + power: 2200, + icon: "car", + charging: true, + title: "Garage", + chargePower: 2200, + connected: true, + index: 2, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], + tariffGrid: 0.25, + tariffFeedIn: 0.08, + tariffCo2: 723, + smartCostType: "co2", + currency: CURRENCY.EUR, + pv: [{ power: 5000 }, { power: 2300 }], }; export const UnknownInput = Template.bind({}); UnknownInput.args = { - gridConfigured: true, - pvConfigured: true, - pvPower: 2000, - gridPower: -2000, - loadpointsCompact: [ - { - power: 1000, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1000, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], + gridConfigured: true, + pvConfigured: true, + pvPower: 2000, + gridPower: -2000, + loadpointsCompact: [ + { + power: 1000, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1000, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], }; export const UnknownInputFill = Template.bind({}); UnknownInputFill.args = { - gridConfigured: true, - pvConfigured: true, - batteryConfigured: true, - pvPower: 500, - gridPower: 0, - batteryPower: -1000, - loadpointsCompact: [], + gridConfigured: true, + pvConfigured: true, + batteryConfigured: true, + pvPower: 500, + gridPower: 0, + batteryPower: -1000, + loadpointsCompact: [], }; export const UnknownOutput = Template.bind({}); UnknownOutput.args = { - gridConfigured: true, - pvConfigured: true, - pvPower: 3000, - gridPower: -1000, - loadpointsCompact: [ - { - power: 1700, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1700, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], + gridConfigured: true, + pvConfigured: true, + pvPower: 3000, + gridPower: -1000, + loadpointsCompact: [ + { + power: 1700, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1700, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], }; export const UnknownOutputLessThan10Percent = Template.bind({}); UnknownOutputLessThan10Percent.args = { - gridConfigured: true, - pvConfigured: true, - pvPower: 3000, - gridPower: -1000, - loadpointsCompact: [ - { - power: 1800, - icon: "car", - charging: true, - title: "Garage", - chargePower: 1800, - connected: true, - index: 0, - vehicleName: "", - vehicleSoc: 50, - chargerFeatureHeating: false, - }, - ], + gridConfigured: true, + pvConfigured: true, + pvPower: 3000, + gridPower: -1000, + loadpointsCompact: [ + { + power: 1800, + icon: "car", + charging: true, + title: "Garage", + chargePower: 1800, + connected: true, + index: 0, + vehicleName: "", + vehicleSoc: 50, + chargerFeatureHeating: false, + }, + ], }; diff --git a/assets/js/components/Energyflow/types.d.ts b/assets/js/components/Energyflow/types.d.ts index f06db66c1..1c7f7523c 100644 --- a/assets/js/components/Energyflow/types.d.ts +++ b/assets/js/components/Energyflow/types.d.ts @@ -1,4 +1,4 @@ interface Pv { - title?: string; - power: number; + title?: string; + power: number; } diff --git a/assets/js/components/Footer/Footer.stories.ts b/assets/js/components/Footer/Footer.stories.ts index 3a0a56111..0c295ac76 100644 --- a/assets/js/components/Footer/Footer.stories.ts +++ b/assets/js/components/Footer/Footer.stories.ts @@ -2,58 +2,58 @@ import Footer from "./Footer.vue"; import type { Meta, StoryFn } from "@storybook/vue3"; export default { - title: "Footer/Footer", - component: Footer, - argTypes: { - version: { control: "object" }, - savings: { control: "object" }, - }, + title: "Footer/Footer", + component: Footer, + argTypes: { + version: { control: "object" }, + savings: { control: "object" }, + }, } as Meta; const Template: StoryFn = (args) => ({ - components: { Footer }, - setup() { - return { args }; - }, - template: '