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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<template>
<div class="row justify-center">
<BatteryOverview />
</div>
<q-carousel
v-model="currentSlide"
swipeable
:animated="animated"
control-color="primary"
infinite
padding
:navigation="groupedBatteries.length > 1"
:arrows="groupedBatteries && $q.screen.gt.xs && groupedBatteries.length > 1"
class="full-width full-height q-mt-md"
transition-next="slide-left"
transition-prev="slide-right"
@mousedown.prevent
>
<q-carousel-slide
v-for="(group, index) in groupedBatteries"
:key="index"
:name="index"
class="row no-wrap justify-center carousel-slide"
>
<div
v-for="batteryId in group"
:key="batteryId"
class="battery-container"
>
<BatteryInformation :battery-id="batteryId" />
</div>
</q-carousel-slide>
</q-carousel>
</template>

<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';
import { useQuasar } from 'quasar';
import BatteryInformation from './BatteryInformation.vue';
import BatteryOverview from './BatteryOverview.vue';

const mqttStore = useMqttStore();
const $q = useQuasar();
const currentSlide = ref<number>(0);
const animated = ref<boolean>(true);

/**
* Group the batteries in chunks of 2 for large screens and 1 for small screens.
*/
const groupedBatteries = computed(() => {
const groupSize = $q.screen.width > 800 ? 2 : 1;
return mqttStore.batteryIds.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index / groupSize);
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = [];
}
resultArray[chunkIndex].push(item);
return resultArray;
}, [] as number[][]);
});

/**
* Update the current slide when the grouped batteries change.
* This may happen when the charge points are sorted or filtered or when the screen size changes.
* We try to keep the same battery in view when the slide changes.
*/
watch(
() => groupedBatteries.value,
async (newValue, oldValue) => {
const findSlide = (batteryId: number) => {
return newValue.findIndex((group) => group.includes(batteryId));
};

if (!oldValue || oldValue.length === 0) {
currentSlide.value = 0;
return;
}

// Prevent animation when the current slide is modified
animated.value = false;
currentSlide.value = Math.max(
findSlide(oldValue[currentSlide.value][0]),
0,
);
await nextTick();
animated.value = true;
},
{ immediate: true },
);
</script>

<style scoped>
.carousel-slide {
padding: 0;
}
.battery-container {
padding: 0.25em;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,31 +1,128 @@
<template>
<div class="q-pa-md">
<p>Speicher</p>
<div class="q-mt-md">
<q-list bordered>
<q-item>
<q-item-section>
<q-item-label>Leistung</q-item-label>
<q-item-label caption>22.0 kW</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label>Geladen</q-item-label>
<q-item-label caption>7.2 kWh</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label>Ladestand</q-item-label>
<q-item-label caption>30% (100 km)</q-item-label>
</q-item-section>
<q-item-section>
<q-item-label>Ladeziel</q-item-label>
<q-item-label caption>90% (360 km)</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</div>
<q-card class="full-height card-width">
<q-card-section>
<div class="row text-h6 items-center text-bold">
<q-icon name="battery_full" size="xs" class="q-mr-sm" color="warning" />
{{ batteryName }}
</div>
<div class="row q-mt-sm text-subtitle2 justify-between no-wrap">
<div class="row">
<q-icon
:name="
soc === 0 || soc === undefined || soc === null
? 'battery_0_bar'
: soc < 14
? 'battery_1_bar'
: soc < 29
? 'battery_2_bar'
: soc < 43
? 'battery_3_bar'
: soc < 57
? 'battery_4_bar'
: soc < 71
? 'battery_5_bar'
: soc < 85
? 'battery_6_bar'
: 'battery_full'
"
size="sm"
color="warning"
class="rotate90Clockwise q-mr-sm"
/>
<div>SoC:</div>
<div class="q-ml-sm">
{{ soc === undefined || soc === null ? '___%' : soc + '%' }}
</div>
</div>
<div class="row">
<div>Leistung:</div>
<div class="q-ml-sm" :class="powerClass">
{{
power < 0
? '>> ' + powerAbsolute
: power > 0
? '<< ' + powerAbsolute
: powerAbsolute
}}
</div>
</div>
</div>
<div class="text-subtitle1 text-weight-bold q-mt-sm">Heute:</div>
<div class="row q-mt-sm text-subtitle2">
<div>Geladen:</div>
<div class="q-ml-sm">
{{ dailyImportedEnergy }}
</div>
</div>
<div class="row q-mt-sm text-subtitle2">
<div>Entladen:</div>
<div class="q-ml-sm">
{{ dailyExportedEnergy }}
</div>
</div>
</q-card-section>
</q-card>
</template>

<script setup lang="ts"></script>
<script setup lang="ts">
import { computed } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';

const props = defineProps<{
batteryId: number;
}>();

const mqttStore = useMqttStore();

const batteryName = computed(() => mqttStore.batteryName(props.batteryId));

const soc = computed(() => mqttStore.batterySoc(props.batteryId));

//const power = computed(() => mqttStore.batteryPower(props.batteryId));

const power = computed(() => {
const power = mqttStore.batteryPower(props.batteryId, 'value');
return typeof power === 'number' ? power : 0;
});

const powerAbsolute = computed(() =>
mqttStore.batteryPower(props.batteryId, 'absoluteTextValue'),
);

const powerClass = computed(() => {
const value = power.value;
return value < 0
? 'text-negative'
: value > 0
? 'text-positive'
: 'text-neutral';
});

const dailyImportedEnergy = computed(() =>
mqttStore.batteryDailyImported(props.batteryId),
);

const dailyExportedEnergy = computed(() =>
mqttStore.batteryDailyExported(props.batteryId),
);
</script>

<style lang="scss" scoped>
.rotate90Clockwise {
transform: rotate(90deg);
}
.text-negative {
color: $red;
}

.text-positive {
color: $green;
}

.text-neutral {
color: $orange;
}
.card-width {
min-width: 24em;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<q-btn-group push rounded class="q-mt-md">
<q-btn
v-for="mode in batModes"
:key="mode.value"
:flat="batMode.value !== mode.value"
:outline="batMode.value === mode.value"
:glossy="batMode.value === mode.value"
:label="mode.label"
:icon="mode.icon"
:color="mode.color"
size="sm"
@click="batMode.value = mode.value"
>
<q-tooltip class="bg-secondary">{{ mode.tooltip }}</q-tooltip>
</q-btn>
</q-btn-group>
</template>

<script setup lang="ts">
import { useMqttStore } from 'src/stores/mqtt-store';
import { computed } from 'vue';

const mqttStore = useMqttStore();

const batModes = [
{
value: 'ev_mode',
label: 'Auto',
color: 'secondary',
icon: 'directions_car',
tooltip: 'Auto ',
},
{
value: 'bat_mode',
label: 'Speicher',
color: 'secondary',
icon: 'battery_charging_full',
tooltip: 'Speicher',
},
{
value: 'min_soc_bat_mode',
label: 'SoC',
color: 'secondary',
icon: 'battery_charging_full',
tooltip: 'Minimum Speicher SoC',
},
];

const batMode = computed(() => mqttStore.batteryMode());
</script>
Loading