Skip to content

Commit 43a5dd8

Browse files
authored
Merge pull request #2407 from Brett-S-OWB/koala-new-chargemodes
Koala-Theme-Updates basierend auf Feedback
2 parents f028e97 + cd9142a commit 43a5dd8

25 files changed

Lines changed: 1157 additions & 319 deletions

packages/modules/web_themes/koala/source/src/components/BaseTable.vue

Lines changed: 142 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
:rows="mappedRows"
66
:columns="mappedColumns"
77
row-key="id"
8+
v-model:expanded="expanded"
89
:filter="filterModel"
910
:filter-method="customFilterMethod"
1011
virtual-scroll
@@ -16,7 +17,8 @@
1617
:pagination="{ rowsPerPage: 0 }"
1718
hide-bottom
1819
>
19-
<template v-slot:top v-if="searchInputVisible">
20+
<!-- search field ------------------------------------------------------->
21+
<template #top v-if="searchInputVisible">
2022
<div class="row full-width items-center q-mb-sm">
2123
<div class="col">
2224
<q-input
@@ -28,99 +30,196 @@
2830
class="search-field white-outline-input"
2931
input-class="text-white"
3032
>
31-
<template v-slot:append>
33+
<template #append>
3234
<q-icon name="search" color="white" />
3335
</template>
3436
</q-input>
3537
</div>
3638
</div>
3739
</template>
3840

39-
<!-- Dynamic slot for custom cell rendering -->
41+
<!-- header ----------------------------------------------------------->
42+
<template v-if="props.rowExpandable" #header="header">
43+
<q-tr :props="header">
44+
<!-- space for arrow column -->
45+
<q-th auto-width :props="{ ...header, col: {} }" />
46+
<!-- the other columns -->
47+
<q-th
48+
v-for="column in header.cols"
49+
:key="column.name"
50+
:props="{ ...header, col: column }"
51+
>
52+
{{ column.label }}
53+
</q-th>
54+
</q-tr>
55+
</template>
56+
57+
<!-- body ------------------------------------------------------------->
58+
<template v-if="props.rowExpandable" #body="rowProps: BodySlotProps<T>">
59+
<q-tr
60+
:key="`main-${rowProps.key}`"
61+
:props="rowProps"
62+
@click="onRowClick($event, rowProps.row)"
63+
class="clickable"
64+
>
65+
<q-td auto-width>
66+
<q-btn
67+
dense
68+
flat
69+
round
70+
size="sm"
71+
:icon="
72+
rowProps.expand ? 'keyboard_arrow_up' : 'keyboard_arrow_down'
73+
"
74+
@click.stop="rowProps.expand = !rowProps.expand"
75+
/>
76+
</q-td>
77+
78+
<template v-for="column in rowProps.cols" :key="column.name">
79+
<!-- custom body-cell slot -->
80+
<template v-if="$slots[`body-cell-${column.name}`]">
81+
<slot
82+
:name="`body-cell-${column.name}`"
83+
v-bind="{
84+
...rowProps,
85+
col: column,
86+
}"
87+
>
88+
</slot>
89+
</template>
90+
91+
<!-- all other column data -->
92+
<q-td
93+
v-else
94+
:props="{
95+
...rowProps,
96+
col: column,
97+
// cast necessary as field comes from q-table and is defined: field: string | ((row: any) => any);
98+
value: rowProps.row[column.field as string],
99+
}"
100+
>
101+
<!-- cast necessary as field comes from q-table and is defined: field: string | ((row: any) => any); -->
102+
{{ rowProps.row[column.field as string] }}
103+
</q-td>
104+
</template>
105+
</q-tr>
106+
107+
<!-- expansion row -->
108+
<q-tr
109+
v-show="rowProps.expand"
110+
:key="`xp-${rowProps.key}`"
111+
:props="rowProps"
112+
class="q-virtual-scroll--with-prev"
113+
>
114+
<q-td :colspan="rowProps.cols.length + 1">
115+
<slot name="row-expand" v-bind="rowProps"> </slot>
116+
</q-td>
117+
</q-tr>
118+
</template>
119+
120+
<!-- forward any other slots not related to table e.g top search field -------------------->
40121
<template
41-
v-for="(_, name) in $slots"
42-
:key="name"
43-
v-slot:[name]="slotData"
122+
v-for="slotName in forwardedSlotNames"
123+
:key="slotName"
124+
v-slot:[slotName]="slotProps"
44125
>
45-
<slot :name="name" v-bind="slotData"></slot>
126+
<slot :name="slotName" v-bind="slotProps"></slot>
46127
</template>
47128
</q-table>
48129
</div>
49130
</template>
50131

51-
<script setup lang="ts">
52-
import { computed, ComputedRef } from 'vue';
53-
import { QTableColumn, QTableProps } from 'quasar';
132+
<script setup lang="ts" generic="T extends Record<string, unknown>">
133+
import { computed, ComputedRef, ref, useSlots } from 'vue';
134+
import type { QTableColumn, QTableProps } from 'quasar';
135+
import {
136+
ColumnConfiguration,
137+
BodySlotProps,
138+
} from 'src/components/models/table-model';
54139
140+
/* ------------------------------------------------------------------ props */
55141
const props = defineProps<{
56142
items: number[];
57-
rowData:
58-
| ((item: number) => Record<string, unknown>)
59-
| ComputedRef<(item: number) => Record<string, unknown>>;
60-
columnConfig: {
61-
fields: string[];
62-
labels?: Record<string, string>;
63-
};
143+
rowData: ((item: number) => T) | ComputedRef<(item: number) => T>;
144+
columnConfig: ColumnConfiguration[];
64145
rowKey?: string;
65146
searchInputVisible?: boolean;
66147
tableHeight?: string;
67148
filter?: string;
68149
columnsToSearch?: string[];
150+
rowExpandable?: boolean;
69151
}>();
70152
153+
/* ------------------------------------------------------------------ state */
154+
const expanded = ref<(string | number)[]>([]);
155+
const slots = useSlots();
156+
157+
const forwardedSlotNames = computed(() => {
158+
if (props.rowExpandable)
159+
return Object.keys(slots).filter((name) => !name.startsWith('body'));
160+
return Object.keys(slots);
161+
});
162+
71163
const emit = defineEmits<{
72-
(e: 'row-click', row: Record<string, unknown>): void;
73-
(e: 'update:filter', value: string): void;
164+
(event: 'row-click', row: T): void;
165+
(event: 'update:filter', value: string): void;
74166
}>();
75167
168+
/* ---------------------------------------------------------------- helpers */
76169
const filterModel = computed({
77170
get: () => props.filter || '',
78171
set: (value) => emit('update:filter', value),
79172
});
80173
81-
// Data can be passed to basetable as a normal function or computed property
82-
const rowMapperFn = computed(() =>
83-
typeof props.rowData === 'function' ? props.rowData : props.rowData.value,
174+
const mappedRows = computed(() =>
175+
props.items.map(
176+
typeof props.rowData === 'function' ? props.rowData : props.rowData.value,
177+
),
84178
);
85179
86-
const mappedRows = computed(() => props.items.map(rowMapperFn.value));
87-
88-
const mappedColumns = computed<QTableColumn[]>(() => {
89-
return props.columnConfig.fields.map((field) => ({
90-
name: field,
91-
label: props.columnConfig.labels?.[field] || field,
92-
field,
93-
align: 'left',
94-
sortable: true,
95-
headerStyle: 'font-weight: bold',
96-
}));
97-
});
180+
const mappedColumns = computed<QTableColumn[]>(() =>
181+
props.columnConfig
182+
.filter((column) => !column.expandField) // main table columns only
183+
.map((column) => ({
184+
name: column.field,
185+
field: column.field,
186+
label: column.label,
187+
align: column.align ?? 'left',
188+
sortable: true,
189+
headerStyle: 'font-weight: bold',
190+
})),
191+
);
98192
99193
const customFilterMethod: NonNullable<QTableProps['filterMethod']> = (
100194
rows,
101-
terms,
102-
cols,
195+
searchTerms,
196+
columns,
103197
) => {
104-
if (!terms || terms.trim() === '') return rows;
105-
const lowerTerms = terms.toLowerCase();
198+
if (!searchTerms || searchTerms.trim() === '') return rows;
199+
const lowerTerms = searchTerms.toLowerCase();
106200
const fields =
107201
props.columnsToSearch ||
108-
cols.map((col) => (typeof col.field === 'string' ? col.field : ''));
202+
columns.map((column) =>
203+
typeof column.field === 'string' ? column.field : '',
204+
);
109205
return rows.filter((row) =>
110206
fields.some((field) => {
111-
const val = row[field];
112-
return val && String(val).toLowerCase().includes(lowerTerms);
207+
const value = row[field];
208+
return value && String(value).toLowerCase().includes(lowerTerms);
113209
}),
114210
);
115211
};
116212
117-
const onRowClick = (evt: Event, row: Record<string, unknown>) =>
118-
emit('row-click', row);
213+
const onRowClick = (evt: Event, row: T) => emit('row-click', row);
119214
</script>
120215

121216
<style scoped>
122217
.search-field {
123218
width: 100%;
124219
max-width: 18em;
125220
}
221+
222+
.clickable {
223+
cursor: pointer;
224+
}
126225
</style>

packages/modules/web_themes/koala/source/src/components/BatteryCard.vue

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,10 @@
1818
@click="dialog?.open()"
1919
/>
2020
</div>
21-
<div class="row q-mt-sm text-subtitle2 justify-between no-wrap">
22-
<div class="row">
23-
<q-icon
24-
:name="
25-
soc === undefined || soc === null
26-
? 'battery_0_bar'
27-
: soc < 85
28-
? `battery_${Math.floor(soc / 15)}_bar`
29-
: 'battery_full'
30-
"
31-
size="sm"
32-
color="primary"
33-
class="rotate90Clockwise q-mr-sm"
34-
/>
35-
<div>SoC:</div>
36-
<div class="q-ml-sm">
37-
{{ soc === undefined || soc === null ? '___%' : soc + '%' }}
38-
</div>
39-
</div>
40-
<div class="row">
41-
<div>Leistung:</div>
42-
<div class="q-ml-sm">
43-
{{ power }}
44-
</div>
21+
<div class="row q-mt-sm text-subtitle2 justify-between full-width">
22+
<div>Leistung:</div>
23+
<div class="q-ml-sm">
24+
{{ power }}
4525
</div>
4626
</div>
4727
<div v-if="showSettings" class="row q-mt-md text-subtitle2">
@@ -57,18 +37,24 @@
5737
</div>
5838
</div>
5939
<div class="text-subtitle1 text-weight-bold q-mt-md">Heute:</div>
60-
<div class="row q-mt-sm text-subtitle2">
40+
<div class="row q-mt-sm text-subtitle2 justify-between full-width">
6141
<div>Geladen:</div>
6242
<div class="q-ml-sm">
6343
{{ dailyImportedEnergy }}
6444
</div>
6545
</div>
66-
<div class="row q-mt-sm text-subtitle2">
46+
<div class="row q-mt-sm text-subtitle2 justify-between full-width">
6747
<div>Entladen:</div>
6848
<div class="q-ml-sm">
6949
{{ dailyExportedEnergy }}
7050
</div>
7151
</div>
52+
<SliderDouble
53+
class="q-mt-sm"
54+
:current-value="soc"
55+
:readonly="true"
56+
limit-mode="none"
57+
/>
7258
</q-card-section>
7359
</q-card>
7460
<BatterySettingsDialog :battery-id="props.batteryId" ref="dialog" />
@@ -79,6 +65,7 @@ import { computed, ref } from 'vue';
7965
import { useMqttStore } from 'src/stores/mqtt-store';
8066
import BatterySettingsDialog from './BatterySettingsDialog.vue';
8167
import { useBatteryModes } from 'src/composables/useBatteryModes.ts';
68+
import SliderDouble from './SliderDouble.vue';
8269
8370
const props = defineProps<{
8471
batteryId: number | undefined;
@@ -145,10 +132,7 @@ const dailyExportedEnergy = computed(() => {
145132
});
146133
</script>
147134

148-
<style lang="scss" scoped>
149-
.rotate90Clockwise {
150-
transform: rotate(90deg);
151-
}
135+
<style scoped>
152136
.card-width {
153137
min-width: 24em;
154138
}

0 commit comments

Comments
 (0)