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,178 @@
<template>
<q-card
class="card-width"
:class="{ 'full-height': props.fullHeight }"
>
<q-card-section class="row no-wrap items-center justify-between">
<div class="row no-wrap items-center ellipsis">
<q-icon
name="circle"
:color="statusColor"
size="12px"
class="q-mr-sm"
:title="statusText"
/>
<div class="text-h6 text-bold ellipsis" :title="name">
{{ name }}
</div>
</div>
<q-space />
<q-btn
icon="settings"
flat
round
dense
:title="`Einstellungen ${name ?? ''}`"
@click="settingsVisible = true"
/>
</q-card-section>
<q-separator class="q-mt-sm" />

<q-card-section
class="row q-mt-sm text-subtitle2 justify-between full-width"
>
<div>Leistung:</div>
<div class="q-ml-sm">
{{ power }}
</div>
</q-card-section>

<q-card-section
class="row q-mt-sm text-subtitle2 justify-between full-width"
>
<div>Laufzeit:</div>
<div class="q-ml-sm">
{{ runTime }}
</div>
</q-card-section>

<q-card-section>
<ConsumerMessage :consumer-id="props.consumerId" />
</q-card-section>

<template v-if="isOnOffType">
<q-separator inset class="q-my-sm" />
<q-card-section
class="row items-center justify-between full-width text-subtitle2"
>
<div>Ein/Aus:</div>
<ToggleStandard
:value="onOff"
size="md"
@update:value="onOff = $event"
/>
</q-card-section>
</template>

<ConsumerSettings
:consumer-id="props.consumerId"
v-model="settingsVisible"
/>
</q-card>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';
import ConsumerSettings from './ConsumerSettings.vue';
import ConsumerMessage from './ConsumerMessage.vue';
import ToggleStandard from './ToggleStandard.vue';

const props = defineProps<{
consumerId: number;
fullHeight?: boolean;
}>();

const mqttStore = useMqttStore();

const settingsVisible = ref(false);

const name = computed(() => mqttStore.consumerName(props.consumerId));

const power = computed(
() => mqttStore.consumerPower(props.consumerId, 'textValue') as string,
);

const powerValue = computed(
() => (mqttStore.consumerPower(props.consumerId, 'value') as number) || 0,
);
const isRunning = computed(() => powerValue.value > 0);

const runTime = computed<string>(() => {
const seconds = mqttStore.consumerOnTime(props.consumerId);
if (seconds === undefined) {
return '--- h';
}
const totalMinutes = Math.floor(seconds / 60);
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
return `${hours}:${String(minutes).padStart(2, '0')} h`;
});

const faultState = computed(() => mqttStore.consumerFaultState(props.consumerId));

const statusText = computed(() => {
if (faultState.value > 0) {
return (
mqttStore.consumerFaultStr(props.consumerId) ??
mqttStore.consumerStateStr(props.consumerId)
);
}
return mqttStore.consumerStateStr(props.consumerId);
});

const statusColor = computed(() => {
if (faultState.value >= 2) {
return 'negative';
}
if (faultState.value === 1) {
return 'warning';
}
return isRunning.value ? 'positive' : 'grey-6';
});

const isOnOffType = computed(
() => mqttStore.consumerUsageType(props.consumerId) === 'suspendable_onoff',
);

const consumerMode = mqttStore.consumerMode(props.consumerId);

// Toggle maps to Sofort (on) / Stop (off). Any non-stop mode reads as "on".
const onOff = computed({
get: () => consumerMode.value !== undefined && consumerMode.value !== 'stop',
set: (value: boolean) => {
consumerMode.value = value ? 'instant_charging' : 'stop';
},
});
</script>

<style scoped lang="scss">
.card-width {
width: 100%;
border: none;
border-left: 4px solid var(--q-consumer);
border-radius: 15px;
}

.q-card__section {
padding-left: $space-base;
padding-right: $space-base;
padding-top: 0;
padding-bottom: 0;
}

.q-card__section:first-of-type {
padding-top: $space-base;
padding-bottom: 0;
}

.q-card__section:last-of-type {
padding-top: 0;
padding-bottom: $space-base;
}

.q-card__section:not(:first-of-type):not(:last-of-type) {
padding-top: 0;
padding-bottom: 0;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div class="consumer-grid-wrapper">
<div class="consumer-grid">
<ConsumerCard
v-for="item in consumerIds"
:key="item"
:consumer-id="item"
full-height
/>
</div>
</div>
</template>

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

import ConsumerCard from 'src/components/ConsumerCard.vue';

const mqttStore = useMqttStore();

const consumerIds = computed(() => mqttStore.consumerIds);
</script>

<style scoped lang="scss">
.consumer-grid-wrapper {
flex: 1 1 auto;
min-height: 0;
width: 100%;
overflow-y: auto;
}

.consumer-grid {
display: grid;
gap: 0.5em;
grid-template-columns: repeat(
auto-fit,
minmax(min(22em, calc(50% - 0.5em)), 22em)
);
justify-content: center;
align-content: start;
padding: 0.5em;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<template>
<BaseMessage
:show-message="showMessage"
:message="message"
:type="messageType"
/>
</template>

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

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

const mqttStore = useMqttStore();

const faultState = computed(() =>
mqttStore.consumerFaultState(props.consumerId),
);

const message = computed(() => {
const text =
faultState.value > 0
? mqttStore.consumerFaultStr(props.consumerId)
: mqttStore.consumerStateStr(props.consumerId);
return text ?? '';
});

const showMessage = computed(() => message.value !== '');

const messageType = computed<'info' | 'warning' | 'error'>(() => {
if (faultState.value >= 2) {
return 'error';
}
if (faultState.value === 1) {
return 'warning';
}
return 'info';
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<template>
<div v-if="isMobile" class="row q-pt-md full-width">
<q-btn-dropdown
class="col"
transition-show="scale"
transition-hide="scale"
transition-duration="500"
color="primary"
:label="currentModeLabel"
size="md"
dropdown-icon="none"
cover
push
>
<q-list>
<template v-for="(mode, index) in chargeModes" :key="mode.value">
<q-item
clickable
v-close-popup
@click="consumerMode = mode.value"
:active="consumerMode === mode.value"
active-class="bg-primary text-white"
>
<q-item-section class="text-center text-weight-bold">
<q-item-label>{{ mode.label.toLocaleUpperCase() }}</q-item-label>
</q-item-section>
</q-item>
<q-separator v-if="index < chargeModes.length - 1" />
</template>
</q-list>
</q-btn-dropdown>
</div>
<q-btn-group v-else class="row col q-mt-sm" spread>
<q-btn
v-for="mode in chargeModes"
:key="mode.value"
:color="consumerMode === mode.value ? 'primary' : 'grey'"
:label="mode.label"
size="sm"
@click="consumerMode = mode.value"
/>
</q-btn-group>
</template>

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

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

const isMobile = computed(() => Platform.is.mobile);
const { chargeModes } = useChargeModes();
const mqttStore = useMqttStore();

const consumerMode = mqttStore.consumerMode(props.consumerId);

const currentModeLabel = computed(
() => chargeModes.find((mode) => mode.value === consumerMode.value)?.label,
);
</script>

<style scoped>
:deep(.q-btn-dropdown__arrow-container) {
width: 0;
padding: 0;
margin: 0;
}
</style>
Loading
Loading