Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
95f1a40
Add SoC update (VehicleCard) fix current decimal place display
Brett-S-OWB May 14, 2025
cda918b
Remove Soc module name from vehicle card / table
Brett-S-OWB May 14, 2025
a079062
Add alignment for table columns and dynamic slots
Brett-S-OWB May 14, 2025
771172d
Mobile Table alignment removed (not working)
Brett-S-OWB May 15, 2025
7e2c142
Formatting
Brett-S-OWB May 15, 2025
f9a45db
Add SoC bar graphic to battery card - format text
Brett-S-OWB May 15, 2025
d56429c
Add power data to table view / align columns
Brett-S-OWB May 16, 2025
5e3b5ae
Fix column alignment mobile view
Brett-S-OWB May 16, 2025
f410081
Simplify table slots using quasar
Brett-S-OWB May 16, 2025
bae3dcb
Edit chargepoint state icon component (for chargepoint & vehicle Id p…
Brett-S-OWB May 16, 2025
1c5da6b
Remove close button from charge point settings dialog (ok existing bo…
Brett-S-OWB May 16, 2025
6953ee0
q-table css
Brett-S-OWB May 16, 2025
ea3c206
Add DC charging sliders to charge point settings
Brett-S-OWB May 20, 2025
e59e4d6
formatting
Brett-S-OWB May 21, 2025
2644825
formatting
Brett-S-OWB May 21, 2025
6caef14
Add optional expansion row to BaseTable
Brett-S-OWB May 21, 2025
a3e6345
Add Vehicle connection status to vehicle table view - expansion row
Brett-S-OWB May 21, 2025
915e017
Rename table models
Brett-S-OWB May 21, 2025
d67b484
Fix css for mobile view table expansion row, wrap chips (connection s…
Brett-S-OWB May 22, 2025
eadf994
Add data to expansion rows in vehicle and chargepoint tables
Brett-S-OWB May 28, 2025
9476444
Add scrollable legend to history chart (standard legacy theme)
Brett-S-OWB May 30, 2025
6223884
Add scrollable legend to history chart (koala theme)
Brett-S-OWB Jun 2, 2025
ba88d15
Fix: change prop to static prop
Brett-S-OWB Jun 6, 2025
fd7bf3d
Typecasting explanation
Brett-S-OWB Jun 6, 2025
08bfc9c
Rename table models file
Brett-S-OWB Jun 6, 2025
4528e1f
Fix html structure
Brett-S-OWB Jun 6, 2025
f68927d
Fix: ensure flow of Typescript data types parent to Child - remove ty…
Brett-S-OWB Jun 6, 2025
c77ab74
Update Chargepoint table format (mobile view) - Add chargePointMode chip
Brett-S-OWB Jun 10, 2025
4b779c6
Improved naming conventions - update getValueObject (decimal place pa…
Brett-S-OWB Jun 11, 2025
88f5933
Naming convention fixes - getValueObject Method updated
Brett-S-OWB Jun 12, 2025
54f130c
remove comment
Brett-S-OWB Jun 13, 2025
1f3e93a
formatting
Brett-S-OWB Jun 16, 2025
cd9142a
Comment and formatting fixes
Brett-S-OWB Jun 16, 2025
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
185 changes: 142 additions & 43 deletions packages/modules/web_themes/koala/source/src/components/BaseTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
:rows="mappedRows"
:columns="mappedColumns"
row-key="id"
v-model:expanded="expanded"
:filter="filterModel"
:filter-method="customFilterMethod"
virtual-scroll
Expand All @@ -16,7 +17,8 @@
:pagination="{ rowsPerPage: 0 }"
hide-bottom
>
<template v-slot:top v-if="searchInputVisible">
<!-- search field ------------------------------------------------------->
<template #top v-if="searchInputVisible">
<div class="row full-width items-center q-mb-sm">
<div class="col">
<q-input
Expand All @@ -28,99 +30,196 @@
class="search-field white-outline-input"
input-class="text-white"
>
<template v-slot:append>
<template #append>
<q-icon name="search" color="white" />
</template>
</q-input>
</div>
</div>
</template>

<!-- Dynamic slot for custom cell rendering -->
<!-- header ----------------------------------------------------------->
<template v-if="props.rowExpandable" #header="header">
<q-tr :props="header">
<!-- space for arrow column -->
<q-th auto-width :props="{ ...header, col: {} }" />
<!-- the other columns -->
<q-th
v-for="column in header.cols"
:key="column.name"
:props="{ ...header, col: column }"
>
{{ column.label }}
</q-th>
</q-tr>
</template>

<!-- body ------------------------------------------------------------->
<template v-if="props.rowExpandable" #body="rowProps: BodySlotProps<T>">
<q-tr
:key="`main-${rowProps.key}`"
:props="rowProps"
@click="onRowClick($event, rowProps.row)"
class="clickable"
>
<q-td auto-width>
<q-btn
dense
flat
round
size="sm"
:icon="
rowProps.expand ? 'keyboard_arrow_up' : 'keyboard_arrow_down'
"
@click.stop="rowProps.expand = !rowProps.expand"
/>
</q-td>

<template v-for="column in rowProps.cols" :key="column.name">
<!-- custom body-cell slot -->
<template v-if="$slots[`body-cell-${column.name}`]">
<slot
:name="`body-cell-${column.name}`"
v-bind="{
...rowProps,
col: column,
}"
>
</slot>
</template>

<!-- all other column data -->
<q-td
v-else
:props="{
...rowProps,
col: column,
// cast necessary as field comes from q-table and is defined: field: string | ((row: any) => any);
value: rowProps.row[column.field as string],
}"
>
<!-- cast necessary as field comes from q-table and is defined: field: string | ((row: any) => any); -->
{{ rowProps.row[column.field as string] }}
</q-td>
</template>
</q-tr>

<!-- expansion row -->
<q-tr
v-show="rowProps.expand"
:key="`xp-${rowProps.key}`"
:props="rowProps"
class="q-virtual-scroll--with-prev"
>
<q-td :colspan="rowProps.cols.length + 1">
<slot name="row-expand" v-bind="rowProps"> </slot>
</q-td>
</q-tr>
</template>

<!-- forward any other slots not related to table e.g top search field -------------------->
<template
v-for="(_, name) in $slots"
:key="name"
v-slot:[name]="slotData"
v-for="slotName in forwardedSlotNames"
:key="slotName"
v-slot:[slotName]="slotProps"
>
<slot :name="name" v-bind="slotData"></slot>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
</q-table>
</div>
</template>

<script setup lang="ts">
import { computed, ComputedRef } from 'vue';
import { QTableColumn, QTableProps } from 'quasar';
<script setup lang="ts" generic="T extends Record<string, unknown>">
import { computed, ComputedRef, ref, useSlots } from 'vue';
import type { QTableColumn, QTableProps } from 'quasar';
import {
ColumnConfiguration,
BodySlotProps,
} from 'src/components/models/table-model';

/* ------------------------------------------------------------------ props */
const props = defineProps<{
items: number[];
rowData:
| ((item: number) => Record<string, unknown>)
| ComputedRef<(item: number) => Record<string, unknown>>;
columnConfig: {
fields: string[];
labels?: Record<string, string>;
};
rowData: ((item: number) => T) | ComputedRef<(item: number) => T>;
columnConfig: ColumnConfiguration[];
rowKey?: string;
searchInputVisible?: boolean;
tableHeight?: string;
filter?: string;
columnsToSearch?: string[];
rowExpandable?: boolean;
}>();

/* ------------------------------------------------------------------ state */
const expanded = ref<(string | number)[]>([]);
const slots = useSlots();

const forwardedSlotNames = computed(() => {
if (props.rowExpandable)
return Object.keys(slots).filter((name) => !name.startsWith('body'));
return Object.keys(slots);
});

const emit = defineEmits<{
(e: 'row-click', row: Record<string, unknown>): void;
(e: 'update:filter', value: string): void;
(event: 'row-click', row: T): void;
(event: 'update:filter', value: string): void;
}>();

/* ---------------------------------------------------------------- helpers */
const filterModel = computed({
get: () => props.filter || '',
set: (value) => emit('update:filter', value),
});

// Data can be passed to basetable as a normal function or computed property
const rowMapperFn = computed(() =>
typeof props.rowData === 'function' ? props.rowData : props.rowData.value,
const mappedRows = computed(() =>
props.items.map(
typeof props.rowData === 'function' ? props.rowData : props.rowData.value,
),
);

const mappedRows = computed(() => props.items.map(rowMapperFn.value));

const mappedColumns = computed<QTableColumn[]>(() => {
return props.columnConfig.fields.map((field) => ({
name: field,
label: props.columnConfig.labels?.[field] || field,
field,
align: 'left',
sortable: true,
headerStyle: 'font-weight: bold',
}));
});
const mappedColumns = computed<QTableColumn[]>(() =>
props.columnConfig
.filter((column) => !column.expandField) // main table columns only
.map((column) => ({
name: column.field,
field: column.field,
label: column.label,
align: column.align ?? 'left',
sortable: true,
headerStyle: 'font-weight: bold',
})),
);

const customFilterMethod: NonNullable<QTableProps['filterMethod']> = (
rows,
terms,
cols,
searchTerms,
columns,
) => {
if (!terms || terms.trim() === '') return rows;
const lowerTerms = terms.toLowerCase();
if (!searchTerms || searchTerms.trim() === '') return rows;
const lowerTerms = searchTerms.toLowerCase();
const fields =
props.columnsToSearch ||
cols.map((col) => (typeof col.field === 'string' ? col.field : ''));
columns.map((column) =>
typeof column.field === 'string' ? column.field : '',
);
return rows.filter((row) =>
fields.some((field) => {
const val = row[field];
return val && String(val).toLowerCase().includes(lowerTerms);
const value = row[field];
return value && String(value).toLowerCase().includes(lowerTerms);
}),
);
};

const onRowClick = (evt: Event, row: Record<string, unknown>) =>
emit('row-click', row);
const onRowClick = (evt: Event, row: T) => emit('row-click', row);
</script>

<style scoped>
.search-field {
width: 100%;
max-width: 18em;
}

.clickable {
cursor: pointer;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,10 @@
@click="dialog?.open()"
/>
</div>
<div class="row q-mt-sm text-subtitle2 justify-between no-wrap">
<div class="row">
<q-icon
:name="
soc === undefined || soc === null
? 'battery_0_bar'
: soc < 85
? `battery_${Math.floor(soc / 15)}_bar`
: 'battery_full'
"
size="sm"
color="primary"
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">
{{ power }}
</div>
<div class="row q-mt-sm text-subtitle2 justify-between full-width">
<div>Leistung:</div>
<div class="q-ml-sm">
{{ power }}
</div>
</div>
<div v-if="showSettings" class="row q-mt-md text-subtitle2">
Expand All @@ -57,18 +37,24 @@
</div>
</div>
<div class="text-subtitle1 text-weight-bold q-mt-md">Heute:</div>
<div class="row q-mt-sm text-subtitle2">
<div class="row q-mt-sm text-subtitle2 justify-between full-width">
<div>Geladen:</div>
<div class="q-ml-sm">
{{ dailyImportedEnergy }}
</div>
</div>
<div class="row q-mt-sm text-subtitle2">
<div class="row q-mt-sm text-subtitle2 justify-between full-width">
<div>Entladen:</div>
<div class="q-ml-sm">
{{ dailyExportedEnergy }}
</div>
</div>
<SliderDouble
class="q-mt-sm"
:current-value="soc"
:readonly="true"
limit-mode="none"
/>
</q-card-section>
</q-card>
<BatterySettingsDialog :battery-id="props.batteryId" ref="dialog" />
Expand All @@ -79,6 +65,7 @@ import { computed, ref } from 'vue';
import { useMqttStore } from 'src/stores/mqtt-store';
import BatterySettingsDialog from './BatterySettingsDialog.vue';
import { useBatteryModes } from 'src/composables/useBatteryModes.ts';
import SliderDouble from './SliderDouble.vue';

const props = defineProps<{
batteryId: number | undefined;
Expand Down Expand Up @@ -145,10 +132,7 @@ const dailyExportedEnergy = computed(() => {
});
</script>

<style lang="scss" scoped>
.rotate90Clockwise {
transform: rotate(90deg);
}
<style scoped>
.card-width {
min-width: 24em;
}
Expand Down
Loading