Chore: Typescript: Rewrite Energyflow (#20814)
Co-authored-by: Michael Geers <michael@geers.tv>
This commit is contained in:
parent
3b672a3f17
commit
e7a5860f5e
26 changed files with 430 additions and 393 deletions
|
|
@ -262,7 +262,7 @@ import collector from "../../mixins/collector.js";
|
|||
import api from "../../api.js";
|
||||
import smartCostAvailable from "../../utils/smartCostAvailable.js";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { Battery, SelectOption } from "assets/js/types/evcc.js";
|
||||
import type { Battery, SelectOption } from "../../types/evcc.js";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BatterySettingsModal",
|
||||
|
|
|
|||
|
|
@ -113,9 +113,9 @@ import collector from "../../mixins/collector.js";
|
|||
import api from "../../api.js";
|
||||
import { optionStep, fmtEnergy } from "../../utils/energyOptions.js";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { CURRENCY, Timeout, Vehicle } from "assets/js/types/evcc.js";
|
||||
import type { CURRENCY, Timeout, Vehicle } from "../../types/evcc.js";
|
||||
import type { StaticPlan, StaticSocPlan, StaticEnergyPlan, RepeatingPlan } from "./types.js";
|
||||
import type { Forecast } from "../../utils/forecast.ts";
|
||||
import type { Forecast } from "../../types/evcc.ts";
|
||||
const ONE_MINUTE = 60 * 1000;
|
||||
|
||||
export default defineComponent({
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ import type {
|
|||
StaticEnergyPlan,
|
||||
PlanResponse,
|
||||
} from "./types.js";
|
||||
import type { Forecast } from "../../utils/forecast.ts";
|
||||
import type { Forecast } from "../../types/evcc.ts";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlansSettings",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter from "../../mixins/formatter.js";
|
||||
import type { PlanWrapper } from "./types.js";
|
||||
import type { Tariff } from "assets/js/types/evcc.js";
|
||||
import type { Tariff } from "../../types/evcc.js";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlanWarnings",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { Rate } from "assets/js/types/evcc";
|
||||
import type { Rate } from "../../types/evcc";
|
||||
|
||||
export interface RepeatingPlan {
|
||||
weekdays: number[];
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
import BatteryIcon from "./BatteryIcon.vue";
|
||||
|
||||
export default {
|
||||
title: "Energyflow/BatteryIcon",
|
||||
component: BatteryIcon,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
};
|
||||
|
||||
const createStory = (args) => {
|
||||
const story = () => ({
|
||||
components: { BatteryIcon },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<BatteryIcon v-bind="args" />',
|
||||
});
|
||||
story.args = args;
|
||||
return story;
|
||||
};
|
||||
|
||||
export const Empty = createStory({ soc: 0 });
|
||||
export const Soc10 = createStory({ soc: 10 });
|
||||
export const Soc20 = createStory({ soc: 20 });
|
||||
export const Soc30 = createStory({ soc: 30 });
|
||||
export const Soc40 = createStory({ soc: 40 });
|
||||
export const Soc50 = createStory({ soc: 50 });
|
||||
export const Soc60 = createStory({ soc: 60 });
|
||||
export const Soc70 = createStory({ soc: 70 });
|
||||
export const Soc80 = createStory({ soc: 80 });
|
||||
export const Soc90 = createStory({ soc: 90 });
|
||||
export const Hold = createStory({ hold: true });
|
||||
export const GridCharge = createStory({ gridCharge: true });
|
||||
58
assets/js/components/Energyflow/BatteryIcon.stories.ts
Normal file
58
assets/js/components/Energyflow/BatteryIcon.stories.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import BatteryIcon from "./BatteryIcon.vue";
|
||||
import type { Meta, StoryFn } from "@storybook/vue3";
|
||||
|
||||
export default {
|
||||
title: "Energyflow/BatteryIcon",
|
||||
component: BatteryIcon,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
} as Meta<typeof BatteryIcon>;
|
||||
|
||||
const Template: StoryFn<typeof BatteryIcon> = (args) => {
|
||||
const story = () => ({
|
||||
components: { BatteryIcon },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<BatteryIcon v-bind="args" />',
|
||||
});
|
||||
story.args = args;
|
||||
return story;
|
||||
};
|
||||
|
||||
export const Empty = Template.bind({});
|
||||
Empty.args = { soc: 0 };
|
||||
|
||||
export const Soc10 = Template.bind({});
|
||||
Soc10.args = { soc: 10 };
|
||||
|
||||
export const Soc20 = Template.bind({});
|
||||
Soc20.args = { soc: 20 };
|
||||
|
||||
export const Soc30 = Template.bind({});
|
||||
Soc30.args = { soc: 30 };
|
||||
|
||||
export const Soc40 = Template.bind({});
|
||||
Soc40.args = { soc: 40 };
|
||||
|
||||
export const Soc50 = Template.bind({});
|
||||
Soc50.args = { soc: 50 };
|
||||
|
||||
export const Soc60 = Template.bind({});
|
||||
Soc60.args = { soc: 60 };
|
||||
|
||||
export const Soc70 = Template.bind({});
|
||||
Soc70.args = { soc: 70 };
|
||||
|
||||
export const Soc80 = Template.bind({});
|
||||
Soc80.args = { soc: 80 };
|
||||
|
||||
export const Soc90 = Template.bind({});
|
||||
Soc90.args = { soc: 90 };
|
||||
|
||||
export const Hold = Template.bind({});
|
||||
Hold.args = { hold: true };
|
||||
|
||||
export const GridCharge = Template.bind({});
|
||||
GridCharge.args = { gridCharge: true };
|
||||
|
|
@ -19,10 +19,11 @@
|
|||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import icon from "../../mixins/icon";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "BatteryIcon",
|
||||
mixins: [icon],
|
||||
props: {
|
||||
|
|
@ -36,5 +37,5 @@ export default {
|
|||
return `M30 38H18v-${height}h12v${height}z`;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,215 +0,0 @@
|
|||
import Energyflow from "./Energyflow.vue";
|
||||
|
||||
export default {
|
||||
title: "Energyflow/Energyflow",
|
||||
component: Energyflow,
|
||||
};
|
||||
|
||||
const Template = (args) => ({
|
||||
components: { Energyflow },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<Energyflow v-bind="args" />',
|
||||
});
|
||||
|
||||
export const GridAndPV = Template.bind({});
|
||||
GridAndPV.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
pvPower: 7300,
|
||||
gridPower: -2300,
|
||||
homePower: 800,
|
||||
loadpointsCompact: [
|
||||
{ power: 1000, icon: "car", charging: true },
|
||||
{ power: 1000, icon: "bike", charging: true },
|
||||
{ power: 2200, icon: "car", charging: true },
|
||||
],
|
||||
tariffGrid: 0.25,
|
||||
tariffFeedIn: 0.08,
|
||||
tariffEffectivePrice: 0.08,
|
||||
smartCostType: "price",
|
||||
smartCostAvailable: true,
|
||||
currency: "EUR",
|
||||
siteTitle: "Home",
|
||||
pv: [{ power: 5000 }, { power: 2300 }],
|
||||
};
|
||||
|
||||
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,
|
||||
tariffEffectivePrice: 0.08,
|
||||
currency: "EUR",
|
||||
siteTitle: "Home",
|
||||
battery: [
|
||||
{ soc: 44.999, capacity: 13.3, power: 350 },
|
||||
{ soc: 82.3331, capacity: 21, power: 450 },
|
||||
],
|
||||
};
|
||||
|
||||
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 }],
|
||||
batteryPower: -1500,
|
||||
batterySoc: 75,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
export const GridPVAndBattery = Template.bind({});
|
||||
GridPVAndBattery.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
batteryConfigured: true,
|
||||
pvPower: 1000,
|
||||
gridPower: 700,
|
||||
homePower: 3300,
|
||||
batteryPower: 1500,
|
||||
batterySoc: 30,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
export const BatteryThresholds = Template.bind({});
|
||||
BatteryThresholds.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
batteryConfigured: true,
|
||||
pvPower: 8700,
|
||||
gridPower: -500,
|
||||
loadpointsCompact: [
|
||||
{ power: 5000, icon: "car", charging: true },
|
||||
{ power: 2500, icon: "bus", charging: true },
|
||||
],
|
||||
batteryPower: -700,
|
||||
batterySoc: 95,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
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 },
|
||||
{ power: 1600, icon: "car", charging: true },
|
||||
],
|
||||
batteryPower: 800,
|
||||
batterySoc: 76,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
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 },
|
||||
{ power: 0, icon: "car", charging: false },
|
||||
{ power: 0, icon: "car", charging: false },
|
||||
{ power: 0, icon: "car", charging: false },
|
||||
],
|
||||
batteryPower: 0,
|
||||
batterySoc: 0,
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
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,
|
||||
tariffEffectivePrice: 0.08,
|
||||
currency: "EUR",
|
||||
siteTitle: "Home",
|
||||
};
|
||||
|
||||
export const CO2 = Template.bind({});
|
||||
CO2.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
pvPower: 7300,
|
||||
gridPower: -2300,
|
||||
homePower: 800,
|
||||
loadpointsCompact: [
|
||||
{ power: 1000, icon: "car", charging: true },
|
||||
{ power: 1000, icon: "car", charging: true },
|
||||
{ power: 2200, icon: "car", charging: true },
|
||||
],
|
||||
tariffGrid: 0.25,
|
||||
tariffFeedIn: 0.08,
|
||||
tariffEffectivePrice: 0.08,
|
||||
tariffCo2: 723,
|
||||
tariffEffectiveCo2: 0,
|
||||
smartCostType: "co2",
|
||||
smartCostAvailable: true,
|
||||
currency: "EUR",
|
||||
siteTitle: "Home",
|
||||
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 }],
|
||||
};
|
||||
|
||||
export const UnknownInputFill = Template.bind({});
|
||||
UnknownInputFill.args = {
|
||||
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 }],
|
||||
};
|
||||
|
||||
export const UnknownOutputLessThan10Percent = Template.bind({});
|
||||
UnknownOutputLessThan10Percent.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
pvPower: 3000,
|
||||
gridPower: -1000,
|
||||
loadpointsCompact: [{ power: 1800, icon: "car", charging: true }],
|
||||
};
|
||||
201
assets/js/components/Energyflow/Energyflow.stories.ts
Normal file
201
assets/js/components/Energyflow/Energyflow.stories.ts
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
import type { Meta, StoryFn } from "@storybook/vue3";
|
||||
import Energyflow from "./Energyflow.vue";
|
||||
import { CURRENCY } from "../../types/evcc";
|
||||
|
||||
export default {
|
||||
title: "Energyflow/Energyflow",
|
||||
component: Energyflow,
|
||||
} as Meta<typeof Energyflow>;
|
||||
|
||||
const Template: StoryFn<typeof Energyflow> = (args) => ({
|
||||
components: { Energyflow },
|
||||
setup() {
|
||||
return { args };
|
||||
},
|
||||
template: '<Energyflow v-bind="args" />',
|
||||
});
|
||||
|
||||
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" },
|
||||
{ power: 1000, icon: "bike", charging: true, title: "Garage" },
|
||||
{ power: 2200, icon: "car", charging: true, title: "Garage" },
|
||||
],
|
||||
tariffGrid: 0.25,
|
||||
tariffFeedIn: 0.08,
|
||||
smartCostType: "price",
|
||||
currency: CURRENCY.EUR,
|
||||
pv: [{ power: 5000 }, { power: 2300 }],
|
||||
};
|
||||
|
||||
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 },
|
||||
],
|
||||
};
|
||||
|
||||
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" }],
|
||||
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,
|
||||
};
|
||||
|
||||
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" },
|
||||
{ power: 2500, icon: "bus", charging: true, title: "Garage" },
|
||||
],
|
||||
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" },
|
||||
{ power: 1600, icon: "car", charging: true, title: "Garage" },
|
||||
],
|
||||
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" },
|
||||
{ power: 0, icon: "car", charging: false, title: "Garage" },
|
||||
{ power: 0, icon: "car", charging: false, title: "Garage" },
|
||||
{ power: 0, icon: "car", charging: false, title: "Garage" },
|
||||
],
|
||||
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,
|
||||
};
|
||||
|
||||
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" },
|
||||
{ power: 1000, icon: "car", charging: true, title: "Garage" },
|
||||
{ power: 2200, icon: "car", charging: true, title: "Garage" },
|
||||
],
|
||||
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" }],
|
||||
};
|
||||
|
||||
export const UnknownInputFill = Template.bind({});
|
||||
UnknownInputFill.args = {
|
||||
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" }],
|
||||
};
|
||||
|
||||
export const UnknownOutputLessThan10Percent = Template.bind({});
|
||||
UnknownOutputLessThan10Percent.args = {
|
||||
gridConfigured: true,
|
||||
pvConfigured: true,
|
||||
pvPower: 3000,
|
||||
gridPower: -1000,
|
||||
loadpointsCompact: [{ power: 1800, icon: "car", charging: true, title: "Garage" }],
|
||||
};
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
@details-clicked="openForecastModal"
|
||||
@toggle="togglePv"
|
||||
>
|
||||
<template v-if="pv.length > 1" #expanded>
|
||||
<template v-if="multiplePv" #expanded>
|
||||
<EnergyflowEntry
|
||||
v-for="(p, index) in pv"
|
||||
:key="index"
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
<template v-if="batteryGridChargeLimitSet" #subline>
|
||||
<div class="d-none d-md-block"> </div>
|
||||
</template>
|
||||
<template v-if="battery.length > 1" #expanded>
|
||||
<template v-if="multipleBattery" #expanded>
|
||||
<EnergyflowEntry
|
||||
v-for="(b, index) in battery"
|
||||
:key="index"
|
||||
|
|
@ -174,6 +174,7 @@
|
|||
/>
|
||||
<EnergyflowEntry
|
||||
:name="
|
||||
// @ts-ignore
|
||||
$t('main.energyflow.loadpoints', activeLoadpointsCount, {
|
||||
count: activeLoadpointsCount,
|
||||
})
|
||||
|
|
@ -248,7 +249,7 @@
|
|||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<template v-if="battery.length > 1" #expanded>
|
||||
<template v-if="multipleBattery" #expanded>
|
||||
<EnergyflowEntry
|
||||
v-for="(b, index) in battery"
|
||||
:key="index"
|
||||
|
|
@ -278,7 +279,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import "@h2d2/shopicons/es/filled/square";
|
||||
import Modal from "bootstrap/js/dist/modal";
|
||||
import Visualization from "./Visualization.vue";
|
||||
|
|
@ -288,8 +289,10 @@ import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
|||
import settings from "../../settings";
|
||||
import { CO2_TYPE } from "../../units";
|
||||
import collector from "../../mixins/collector";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { Battery, CURRENCY, Forecast, LoadpointCompact } from "../../types/evcc";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "Energyflow",
|
||||
components: {
|
||||
Visualization,
|
||||
|
|
@ -302,11 +305,11 @@ export default {
|
|||
gridPower: { type: Number, default: 0 },
|
||||
homePower: { type: Number, default: 0 },
|
||||
pvConfigured: Boolean,
|
||||
pv: { type: Array },
|
||||
pv: { type: Array as PropType<Pv[]> },
|
||||
pvPower: { type: Number, default: 0 },
|
||||
loadpointsCompact: { type: Array, default: () => [] },
|
||||
loadpointsCompact: { type: Array as PropType<LoadpointCompact[]>, default: () => [] },
|
||||
batteryConfigured: { type: Boolean },
|
||||
battery: { type: Array },
|
||||
battery: { type: Array as PropType<Battery[]> },
|
||||
batteryPower: { type: Number, default: 0 },
|
||||
batterySoc: { type: Number, default: 0 },
|
||||
batteryDischargeControl: { type: Boolean },
|
||||
|
|
@ -314,21 +317,21 @@ export default {
|
|||
batteryGridChargeActive: { type: Boolean },
|
||||
batteryMode: { type: String },
|
||||
tariffGrid: { type: Number },
|
||||
tariffFeedIn: { type: Number },
|
||||
tariffFeedIn: { type: Number, default: 0 },
|
||||
tariffCo2: { type: Number },
|
||||
tariffPriceHome: { type: Number },
|
||||
tariffCo2Home: { type: Number },
|
||||
tariffPriceLoadpoints: { type: Number },
|
||||
tariffCo2Loadpoints: { type: Number },
|
||||
smartCostType: { type: String },
|
||||
currency: { type: String },
|
||||
currency: { type: String as PropType<CURRENCY> },
|
||||
prioritySoc: { type: Number },
|
||||
bufferSoc: { type: Number },
|
||||
bufferStartSoc: { type: Number },
|
||||
forecast: { type: Object, default: () => ({}) },
|
||||
forecast: { type: Object as PropType<Forecast>, default: () => ({}) },
|
||||
},
|
||||
data: () => {
|
||||
return { detailsOpen: false, detailsCompleteHeight: null, ready: false };
|
||||
return { detailsOpen: false, detailsCompleteHeight: null as number | null, ready: false };
|
||||
},
|
||||
computed: {
|
||||
gridImport() {
|
||||
|
|
@ -401,13 +404,19 @@ export default {
|
|||
return this.detailsOpen ? this.detailsCompleteHeight + "px" : 0;
|
||||
},
|
||||
batteryFmt() {
|
||||
return (soc) => this.fmtPercentage(soc, 0);
|
||||
return (soc: number) => this.fmtPercentage(soc, 0);
|
||||
},
|
||||
multipleBattery() {
|
||||
return (this.battery?.length || 0) > 1;
|
||||
},
|
||||
multiplePv() {
|
||||
return (this.pv?.length || 0) > 1;
|
||||
},
|
||||
fmtLoadpointSoc() {
|
||||
return (soc) => this.fmtPercentage(soc, 0);
|
||||
return (soc: number) => this.fmtPercentage(soc, 0);
|
||||
},
|
||||
fmtLoadpointTemp() {
|
||||
return (temp) => this.fmtTemperature(temp);
|
||||
return (temp: number) => this.fmtTemperature(temp);
|
||||
},
|
||||
co2Available() {
|
||||
return this.smartCostType === CO2_TYPE;
|
||||
|
|
@ -444,7 +453,8 @@ export default {
|
|||
}
|
||||
const { today, scale } = this.forecast.solar || {};
|
||||
const factor = this.$hiddenFeatures() && settings.solarAdjusted && scale ? scale : 1;
|
||||
return today.energy * factor;
|
||||
const energy = today?.energy || 0;
|
||||
return energy * factor;
|
||||
},
|
||||
solarForecastIcon() {
|
||||
return this.solarForecastExists ? "forecast" : undefined;
|
||||
|
|
@ -494,7 +504,7 @@ export default {
|
|||
window.removeEventListener("resize", this.updateHeight);
|
||||
},
|
||||
methods: {
|
||||
detailsTooltip(price, co2) {
|
||||
detailsTooltip(price?: number, co2?: number) {
|
||||
const result = [];
|
||||
if (co2 !== undefined) {
|
||||
result.push(`${this.fmtCo2Long(co2)}`);
|
||||
|
|
@ -504,25 +514,25 @@ export default {
|
|||
}
|
||||
return result;
|
||||
},
|
||||
detailsValue(price, co2) {
|
||||
detailsValue(price?: number, co2?: number) {
|
||||
if (this.co2Available) {
|
||||
return co2;
|
||||
}
|
||||
return price;
|
||||
},
|
||||
detailsFmt(value) {
|
||||
detailsFmt(value: number) {
|
||||
if (this.co2Available) {
|
||||
return this.fmtCo2Short(value);
|
||||
}
|
||||
return this.fmtPricePerKWh(value, this.currency, true);
|
||||
},
|
||||
forecastFmt(value) {
|
||||
forecastFmt(value: number) {
|
||||
if (value === null) {
|
||||
return "";
|
||||
}
|
||||
return `${this.fmtWh(value, POWER_UNIT.KW)}`;
|
||||
},
|
||||
kw(watt) {
|
||||
kw(watt: number) {
|
||||
return this.fmtW(watt, this.powerUnit);
|
||||
},
|
||||
toggleDetails() {
|
||||
|
|
@ -531,22 +541,24 @@ export default {
|
|||
settings.energyflowDetails = this.detailsOpen;
|
||||
},
|
||||
updateHeight() {
|
||||
this.detailsCompleteHeight = this.$refs.detailsInner.offsetHeight;
|
||||
this.detailsCompleteHeight = this.$refs["detailsInner"]?.offsetHeight ?? 0;
|
||||
},
|
||||
openBatterySettingsModal() {
|
||||
const modal = Modal.getOrCreateInstance(
|
||||
document.getElementById("batterySettingsModal")
|
||||
document.getElementById("batterySettingsModal") as HTMLElement
|
||||
);
|
||||
modal.show();
|
||||
},
|
||||
openForecastModal() {
|
||||
const modal = Modal.getOrCreateInstance(document.getElementById("forecastModal"));
|
||||
const modal = Modal.getOrCreateInstance(
|
||||
document.getElementById("forecastModal") as HTMLElement
|
||||
);
|
||||
modal.show();
|
||||
},
|
||||
dischargePower(power) {
|
||||
dischargePower(power: number) {
|
||||
return Math.abs(Math.max(0, power));
|
||||
},
|
||||
chargePower(power) {
|
||||
chargePower(power: number) {
|
||||
return Math.abs(Math.min(0, power) * -1);
|
||||
},
|
||||
toggleBattery() {
|
||||
|
|
@ -561,14 +573,14 @@ export default {
|
|||
settings.energyflowLoadpoints = !settings.energyflowLoadpoints;
|
||||
this.$nextTick(this.updateHeight);
|
||||
},
|
||||
genericBatteryTitle(index) {
|
||||
genericBatteryTitle(index: number) {
|
||||
return `${this.$t("config.devices.batteryStorage")} #${index + 1}`;
|
||||
},
|
||||
genericPvTitle(index) {
|
||||
genericPvTitle(index: number) {
|
||||
return `${this.$t("config.devices.solarSystem")} #${index + 1}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.details {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<component :is="`shopicon-regular-${icon}`" v-else></component>
|
||||
</span>
|
||||
<div class="d-block flex-grow-1 ms-3 text-truncate">
|
||||
<span v-if="!$slots.expanded">{{ name }}</span>
|
||||
<span v-if="!$slots['expanded']">{{ name }}</span>
|
||||
<button
|
||||
v-else
|
||||
class="btn-neutral d-flex align-items-baseline flex-shrink-1 flex-grow-1"
|
||||
|
|
@ -42,7 +42,11 @@
|
|||
v-if="detailsIcon === 'forecast'"
|
||||
class="ms-2 me-1 d-inline-block"
|
||||
/>
|
||||
<AnimatedNumber v-if="!isNaN(details)" :to="details" :format="detailsFmt" />
|
||||
<AnimatedNumber
|
||||
v-if="details !== undefined && !isNaN(details)"
|
||||
:to="details"
|
||||
:format="detailsFmt as Function"
|
||||
/>
|
||||
</div>
|
||||
<div ref="power" class="power" data-bs-toggle="tooltip" @click="powerClicked">
|
||||
<AnimatedNumber ref="powerNumber" :to="power" :format="kw" />
|
||||
|
|
@ -51,31 +55,32 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="$slots.expanded"
|
||||
v-if="$slots['expanded']"
|
||||
class="expandable ms-2"
|
||||
:class="{ 'expandable--open': expanded }"
|
||||
>
|
||||
<slot name="expanded" />
|
||||
</div>
|
||||
<div v-if="$slots.subline" class="ms-4 ps-3 mb-2">
|
||||
<div v-if="$slots['subline']" class="ms-4 ps-3 mb-2">
|
||||
<slot name="subline" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import "@h2d2/shopicons/es/regular/powersupply";
|
||||
import "@h2d2/shopicons/es/regular/sun";
|
||||
import "@h2d2/shopicons/es/regular/home";
|
||||
import "@h2d2/shopicons/es/regular/arrowdropdown";
|
||||
import Tooltip from "bootstrap/js/dist/tooltip";
|
||||
import BatteryIcon from "./BatteryIcon.vue";
|
||||
import formatter from "../../mixins/formatter";
|
||||
import formatter, { POWER_UNIT } from "../../mixins/formatter";
|
||||
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
||||
import VehicleIcon from "../VehicleIcon";
|
||||
import ForecastIcon from "../MaterialIcon/Forecast.vue";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "EnergyflowEntry",
|
||||
components: { BatteryIcon, AnimatedNumber, VehicleIcon, ForecastIcon },
|
||||
mixins: [formatter],
|
||||
|
|
@ -83,20 +88,23 @@ export default {
|
|||
name: { type: String },
|
||||
icon: { type: String },
|
||||
iconProps: { type: Object, default: () => ({}) },
|
||||
power: { type: Number },
|
||||
powerTooltip: { type: Array },
|
||||
powerUnit: { type: String },
|
||||
power: { type: Number, default: 0 },
|
||||
powerTooltip: { type: Array as PropType<string[]> },
|
||||
powerUnit: { type: String as PropType<POWER_UNIT> },
|
||||
details: { type: Number },
|
||||
detailsIcon: { type: String },
|
||||
detailsFmt: { type: Function },
|
||||
detailsTooltip: { type: Array },
|
||||
detailsTooltip: { type: Array as PropType<string[]> },
|
||||
detailsClickable: { type: Boolean },
|
||||
detailsInactive: { type: Boolean },
|
||||
expanded: { type: Boolean, default: false },
|
||||
},
|
||||
emits: ["details-clicked", "toggle"],
|
||||
data() {
|
||||
return { powerTooltipInstance: null, detailsTooltipInstance: null };
|
||||
return {
|
||||
powerTooltipInstance: null as Tooltip | null,
|
||||
detailsTooltipInstance: null as Tooltip | null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
active() {
|
||||
|
|
@ -123,7 +131,7 @@ export default {
|
|||
powerInKw(newVal, oldVal) {
|
||||
// force update if unit changes but not the value
|
||||
if (newVal !== oldVal) {
|
||||
this.$refs.powerNumber.forceUpdate();
|
||||
(this.$refs["powerNumber"] as any).forceUpdate();
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -132,29 +140,29 @@ export default {
|
|||
this.updateDetailsTooltip();
|
||||
},
|
||||
methods: {
|
||||
kw(watt) {
|
||||
kw(watt: number) {
|
||||
return this.fmtW(watt, this.powerUnit);
|
||||
},
|
||||
updatePowerTooltip() {
|
||||
this.powerTooltipInstance = this.updateTooltip(
|
||||
this.powerTooltipInstance,
|
||||
this.powerTooltip,
|
||||
this.$refs.power
|
||||
this.$refs["power"],
|
||||
this.powerTooltip
|
||||
);
|
||||
},
|
||||
updateDetailsTooltip() {
|
||||
this.detailsTooltipInstance = this.updateTooltip(
|
||||
this.detailsTooltipInstance,
|
||||
this.detailsTooltip,
|
||||
this.$refs.details
|
||||
this.$refs["details"],
|
||||
this.detailsTooltip
|
||||
);
|
||||
},
|
||||
updateTooltip(instance, content, ref) {
|
||||
updateTooltip(instance: Tooltip | null, ref: any, content?: string[]) {
|
||||
if (!Array.isArray(content) || !content.length) {
|
||||
if (instance) {
|
||||
instance.dispose();
|
||||
}
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
let newInstance = instance;
|
||||
if (!newInstance) {
|
||||
|
|
@ -164,12 +172,12 @@ export default {
|
|||
newInstance.setContent({ ".tooltip-inner": html });
|
||||
return newInstance;
|
||||
},
|
||||
powerClicked($event) {
|
||||
powerClicked($event: Event) {
|
||||
if (this.powerTooltip) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
},
|
||||
detailsClicked($event) {
|
||||
detailsClicked($event: Event) {
|
||||
if (this.detailsClickable || this.detailsTooltip) {
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
|
@ -179,12 +187,12 @@ export default {
|
|||
// hide tooltip, chrome needs a timeout
|
||||
setTimeout(() => this.detailsTooltipInstance?.hide(), 10);
|
||||
},
|
||||
toggle($event) {
|
||||
toggle($event: Event) {
|
||||
$event.stopPropagation();
|
||||
this.$emit("toggle");
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.entry {
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "LabelBar",
|
||||
props: {
|
||||
value: { type: Number, default: 0 },
|
||||
|
|
@ -29,7 +31,7 @@ export default {
|
|||
first: { type: Boolean },
|
||||
last: { type: Boolean },
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.label-bar {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import formatter, { POWER_UNIT } from "../../mixins/formatter";
|
||||
import BatteryIcon from "./BatteryIcon.vue";
|
||||
import LabelBar from "./LabelBar.vue";
|
||||
|
|
@ -115,8 +115,10 @@ import VehicleIcon from "../VehicleIcon";
|
|||
import QuestionIcon from "../MaterialIcon/Question.vue";
|
||||
import "@h2d2/shopicons/es/regular/sun";
|
||||
import "@h2d2/shopicons/es/regular/home";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { LoadpointCompact } from "../../types/evcc";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "Visualization",
|
||||
components: { BatteryIcon, LabelBar, AnimatedNumber, VehicleIcon, QuestionIcon },
|
||||
mixins: [formatter],
|
||||
|
|
@ -125,7 +127,7 @@ export default {
|
|||
selfPv: { type: Number, default: 0 },
|
||||
selfBattery: { type: Number, default: 0 },
|
||||
pvExport: { type: Number, default: 0 },
|
||||
loadpoints: { type: Array, default: () => [] },
|
||||
loadpoints: { type: Array as PropType<LoadpointCompact[]>, default: () => [] },
|
||||
batteryCharge: { type: Number, default: 0 },
|
||||
batteryDischarge: { type: Number, default: 0 },
|
||||
batteryHold: { type: Boolean, default: false },
|
||||
|
|
@ -133,7 +135,7 @@ export default {
|
|||
pvProduction: { type: Number, default: 0 },
|
||||
homePower: { type: Number, default: 0 },
|
||||
batterySoc: { type: Number, default: 0 },
|
||||
powerUnit: { type: String, default: POWER_UNIT.KW },
|
||||
powerUnit: { type: String as PropType<POWER_UNIT>, default: POWER_UNIT.KW },
|
||||
inPower: { type: Number, default: 0 },
|
||||
outPower: { type: Number, default: 0 },
|
||||
},
|
||||
|
|
@ -197,42 +199,42 @@ export default {
|
|||
window.removeEventListener("resize", this.updateElementWidth);
|
||||
},
|
||||
methods: {
|
||||
widthTotal(power) {
|
||||
widthTotal(power: number) {
|
||||
if (this.totalAdjusted === 0 || power === 0) return "0";
|
||||
return (100 / this.totalAdjusted) * power + "%";
|
||||
},
|
||||
fmtBarValue(watt) {
|
||||
fmtBarValue(watt: number) {
|
||||
if (!this.enoughSpaceForValue(watt)) {
|
||||
return "";
|
||||
}
|
||||
const withUnit = this.enoughSpaceForUnit(watt);
|
||||
return this.fmtW(watt, this.powerUnit, withUnit);
|
||||
},
|
||||
powerLabelAvailableSpace(power) {
|
||||
powerLabelAvailableSpace(power: number) {
|
||||
if (this.totalAdjusted === 0) return 0;
|
||||
const percent = (100 / this.totalAdjusted) * power;
|
||||
return (this.width / 100) * percent;
|
||||
},
|
||||
enoughSpaceForValue(power) {
|
||||
enoughSpaceForValue(power: number) {
|
||||
return this.powerLabelAvailableSpace(power) > 40;
|
||||
},
|
||||
enoughSpaceForUnit(power) {
|
||||
enoughSpaceForUnit(power: number) {
|
||||
return this.powerLabelAvailableSpace(power) > 60;
|
||||
},
|
||||
hideLabelIcon(power, minWidth = 32) {
|
||||
hideLabelIcon(power: number, minWidth = 32) {
|
||||
if (this.totalAdjusted === 0) return true;
|
||||
const percent = (100 / this.totalAdjusted) * power;
|
||||
return (this.width / 100) * percent < minWidth;
|
||||
},
|
||||
applyThreshold(power, threshold = 2) {
|
||||
applyThreshold(power: number, threshold = 2) {
|
||||
const percent = (100 / this.totalRaw) * power;
|
||||
return percent < threshold ? 0 : power;
|
||||
},
|
||||
updateElementWidth() {
|
||||
this.width = this.$refs.site_progress.getBoundingClientRect().width;
|
||||
this.width = this.$refs["site_progress"]?.getBoundingClientRect().width ?? 0;
|
||||
},
|
||||
labelBarProps(position, name, val) {
|
||||
const value = val === undefined ? this[name] : val;
|
||||
labelBarProps(position: string, name: string, val?: number) {
|
||||
const value = val === undefined ? (this as any)[name] : val;
|
||||
const minWidth = 40;
|
||||
return {
|
||||
value,
|
||||
|
|
@ -242,7 +244,7 @@ export default {
|
|||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped>
|
||||
.site-progress {
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
import Energyflow from "./Energyflow.vue";
|
||||
|
||||
export default Energyflow;
|
||||
4
assets/js/components/Energyflow/types.d.ts
vendored
Normal file
4
assets/js/components/Energyflow/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
interface Pv {
|
||||
title?: string;
|
||||
power: number;
|
||||
}
|
||||
|
|
@ -9,10 +9,9 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import type { PropType } from "vue";
|
||||
import { isForecastSlot, type ForecastSlot, type TimeseriesEntry } from "../../utils/forecast";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter from "../../mixins/formatter";
|
||||
import { isForecastSlot, type ForecastSlot, type TimeseriesEntry } from "./types.js";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ForecastActiveSlot",
|
||||
|
|
|
|||
|
|
@ -35,14 +35,9 @@ import "chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm";
|
|||
import { registerChartComponents, commonOptions } from "../Sessions/chartConfig";
|
||||
import formatter, { POWER_UNIT } from "../../mixins/formatter.ts";
|
||||
import colors, { lighterColor } from "../../colors.ts";
|
||||
import {
|
||||
highestSlotIndexByDay,
|
||||
ForecastType,
|
||||
type ForecastSlot,
|
||||
type SolarDetails,
|
||||
type TimeseriesEntry,
|
||||
} from "../../utils/forecast.ts";
|
||||
import type { CURRENCY } from "assets/js/types/evcc.ts";
|
||||
import type { CURRENCY } from "../../types/evcc.ts";
|
||||
import { ForecastType, highestSlotIndexByDay } from "../../utils/forecast.ts";
|
||||
import type { ForecastSlot, SolarDetails, TimeseriesEntry } from "./types.ts";
|
||||
|
||||
registerChartComponents([
|
||||
BarController,
|
||||
|
|
|
|||
|
|
@ -64,10 +64,11 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import { ForecastType, type ForecastSlot, type SolarDetails } from "../../utils/forecast.ts";
|
||||
import formatter, { POWER_UNIT } from "../../mixins/formatter.ts";
|
||||
import AnimatedNumber from "../Helper/AnimatedNumber.vue";
|
||||
import type { CURRENCY, Timeout } from "assets/js/types/evcc.ts";
|
||||
import type { CURRENCY, Timeout } from "../../types/evcc.ts";
|
||||
import { ForecastType } from "../../utils/forecast.ts";
|
||||
import type { ForecastSlot, SolarDetails } from "./types.ts";
|
||||
const LOCALES_WITHOUT_DAY_AFTER_TOMORROW = ["en", "tr"];
|
||||
|
||||
export interface Energy {
|
||||
|
|
|
|||
|
|
@ -58,16 +58,12 @@ import Chart from "./Chart.vue";
|
|||
import TypeSelect from "./TypeSelect.vue";
|
||||
import Details from "./Details.vue";
|
||||
import ActiveSlot from "./ActiveSlot.vue";
|
||||
import {
|
||||
type ForecastSlot,
|
||||
type TimeseriesEntry,
|
||||
type Forecast,
|
||||
ForecastType,
|
||||
adjustedSolar,
|
||||
} from "../../utils/forecast.ts";
|
||||
|
||||
import formatter from "../../mixins/formatter.ts";
|
||||
import settings from "../../settings";
|
||||
import type { CURRENCY } from "assets/js/types/evcc.ts";
|
||||
import type { CURRENCY, Forecast } from "../../types/evcc.ts";
|
||||
import { ForecastType, adjustedSolar } from "../../utils/forecast.ts";
|
||||
import type { ForecastSlot, TimeseriesEntry } from "./types.ts";
|
||||
export default defineComponent({
|
||||
name: "ForecastModal",
|
||||
components: {
|
||||
|
|
|
|||
27
assets/js/components/Forecast/types.ts
Normal file
27
assets/js/components/Forecast/types.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export function isForecastSlot(obj?: TimeseriesEntry | ForecastSlot): obj is ForecastSlot {
|
||||
return (obj as ForecastSlot).start !== undefined;
|
||||
}
|
||||
|
||||
export interface TimeseriesEntry {
|
||||
val: number;
|
||||
ts: string;
|
||||
}
|
||||
|
||||
export interface ForecastSlot {
|
||||
start: string;
|
||||
end: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface EnergyByDay {
|
||||
energy: number;
|
||||
complete: boolean;
|
||||
}
|
||||
|
||||
export interface SolarDetails {
|
||||
scale?: number;
|
||||
today?: EnergyByDay;
|
||||
tomorrow?: EnergyByDay;
|
||||
dayAfterTomorrow?: EnergyByDay;
|
||||
timeseries?: TimeseriesEntry[];
|
||||
}
|
||||
|
|
@ -96,8 +96,8 @@ export default defineComponent({
|
|||
maximumFractionDigits: 0,
|
||||
}).format(value);
|
||||
},
|
||||
fmtCo2Short(gramms: number) {
|
||||
return `${this.fmtNumber(gramms, 0)} g`;
|
||||
fmtCo2Short(gramms?: number) {
|
||||
return gramms ? `${this.fmtNumber(gramms, 0)} g` : "?";
|
||||
},
|
||||
fmtCo2Medium(gramms: number) {
|
||||
return `${this.fmtNumber(gramms, 0)} g/kWh`;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import type { StaticPlan, RepeatingPlan } from "../components/ChargingPlans/types";
|
||||
import type { ForecastSlot, SolarDetails } from "../components/Forecast/types";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
@ -10,12 +11,20 @@ export interface State {
|
|||
offline: boolean;
|
||||
startup?: boolean;
|
||||
loadpoints: [];
|
||||
forecast?: any;
|
||||
forecast?: Forecast;
|
||||
currency?: CURRENCY;
|
||||
fatal?: {
|
||||
error: any;
|
||||
};
|
||||
}
|
||||
export interface LoadpointCompact {
|
||||
icon: string;
|
||||
title: string;
|
||||
charging: boolean;
|
||||
soc?: number;
|
||||
power: number;
|
||||
heating?: boolean;
|
||||
}
|
||||
|
||||
export enum CURRENCY {
|
||||
EUR = "EUR",
|
||||
|
|
@ -64,6 +73,13 @@ export interface Slot {
|
|||
selectable?: boolean | null;
|
||||
}
|
||||
|
||||
export interface Forecast {
|
||||
grid?: ForecastSlot[];
|
||||
co2?: ForecastSlot[];
|
||||
solar?: SolarDetails;
|
||||
planner?: ForecastSlot[];
|
||||
}
|
||||
|
||||
export interface SelectOption<T> {
|
||||
name: string;
|
||||
value: T;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { Rate } from "../types/evcc";
|
||||
import type { ForecastSlot } from "./forecast";
|
||||
import type { ForecastSlot } from "../components/Forecast/types";
|
||||
|
||||
function convertRate(slot: ForecastSlot): Rate {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -1,40 +1,6 @@
|
|||
import type { TimeseriesEntry, SolarDetails } from "../components/Forecast/types";
|
||||
import deepCopy from "./deepClone";
|
||||
|
||||
export interface TimeseriesEntry {
|
||||
val: number;
|
||||
ts: string;
|
||||
}
|
||||
|
||||
export interface ForecastSlot {
|
||||
start: string;
|
||||
end: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export function isForecastSlot(obj?: TimeseriesEntry | ForecastSlot): obj is ForecastSlot {
|
||||
return (obj as ForecastSlot).start !== undefined;
|
||||
}
|
||||
|
||||
export interface EnergyByDay {
|
||||
energy: number;
|
||||
complete: boolean;
|
||||
}
|
||||
|
||||
export interface SolarDetails {
|
||||
scale?: number;
|
||||
today?: EnergyByDay;
|
||||
tomorrow?: EnergyByDay;
|
||||
dayAfterTomorrow?: EnergyByDay;
|
||||
timeseries?: TimeseriesEntry[];
|
||||
}
|
||||
|
||||
export interface Forecast {
|
||||
grid?: ForecastSlot[];
|
||||
co2?: ForecastSlot[];
|
||||
planner?: ForecastSlot[];
|
||||
solar?: SolarDetails;
|
||||
}
|
||||
|
||||
export enum ForecastType {
|
||||
Solar = "solar",
|
||||
Price = "price",
|
||||
|
|
|
|||
|
|
@ -47,9 +47,10 @@ import Chart from "../components/Forecast/Chart.vue";
|
|||
import IconSelectItem from "../components/Helper/IconSelectItem.vue";
|
||||
import IconSelectGroup from "../components/Helper/IconSelectGroup.vue";
|
||||
import DynamicPriceIcon from "../components/MaterialIcon/DynamicPrice.vue";
|
||||
import { ForecastType, type ForecastSlot, type SolarDetails } from "../utils/forecast";
|
||||
import formatter from "../mixins/formatter.ts";
|
||||
import store from "../store.ts";
|
||||
import type { ForecastSlot, SolarDetails } from "../components/Forecast/types.ts";
|
||||
import { ForecastType } from "../utils/forecast.ts";
|
||||
|
||||
interface Forecast {
|
||||
grid?: ForecastSlot[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue