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
Expand Up @@ -9,7 +9,7 @@
@update:model-value="handleSlideChange"
padding
:navigation="groupedItems.length > 1"
:arrows="groupedItems.length > 1 && $q.screen.gt.xs"
:arrows="showArrows"
class="carousel-height"
transition-next="slide-left"
transition-prev="slide-right"
Expand All @@ -35,23 +35,64 @@

<script setup lang="ts">
import { ref, computed, watch, nextTick, onMounted } from 'vue';
import { useQuasar } from 'quasar';
import { Screen } from 'quasar';

const props = defineProps<{
items: number[];
}>();

const $q = useQuasar();
const currentSlide = ref<number>(0);
const slideItemWidth = ref<number | null>(null);
const currentSlideItem = ref<number | null>(null);
const animated = ref<boolean>(true);
const carouselRef = ref<{ $el: HTMLElement } | null>(null);
const itemRef = ref<(HTMLElement | null)[]>([]);

const showArrows = computed(() => {
return groupedItems.value.length > 1 && Screen.gt.xs;
});

const storeSlideItemWidth = (width: number) => {
console.log('Storing slide item width:', width);
slideItemWidth.value = width;
};

const currentSlide = computed({
get: () => {
if (currentSlideItem.value == null) {
return 0; // Always show the first slide if a specific item is not set
}
// Find the index of the slide containing the current item
let currentSlide = groupedItems.value.findIndex((group) =>
group.includes(currentSlideItem.value),
);
if (currentSlide === -1) {
return 0; // Default to first slide if item not found
}
// Ensure the index is within bounds
return Math.min(currentSlide, groupedItems.value.length - 1);
},
set: (value: number) => {
// Update currentSlideItem to the first item of the new slide
currentSlideItem.value = groupedItems.value[value][0];
},
});

const groupSize = computed(() => {
if (!itemRef.value[0]) {
return; // Fallback if no item is present
let itemWidth = 300; // Default width
console.debug('Calculating group size...', itemRef.value);
if (slideItemWidth.value == null) {
if (
itemRef.value[0]?.clientWidth &&
itemRef.value[0].clientWidth != slideItemWidth.value
) {
storeSlideItemWidth(itemRef.value[0].clientWidth);
} else {
console.warn('Item width is not set, using default:', itemWidth);
}
} else {
itemWidth = slideItemWidth.value;
}
const itemWidth = itemRef.value[0]?.clientWidth ?? 300; // Fallback
console.debug('Item width:', itemWidth);
let carouselSlideWidth = 0;
let padding = 0;
const slideEl = carouselRef.value?.$el.querySelector('.q-carousel__slide');
Expand All @@ -61,7 +102,7 @@ const groupSize = computed(() => {
padding =
parseFloat(style.paddingLeft || '0') +
parseFloat(style.paddingRight || '0');
}
}
const maxGroupSize = Math.max(
1,
Math.floor((carouselSlideWidth - padding) / itemWidth),
Expand All @@ -74,7 +115,7 @@ const groupSize = computed(() => {
props.items.length <= maxGroupSize * 2 &&
props.items.length - maxGroupSize === 1
) {
// Check if all items would fit side by side
// Check if all items would fit side by side
if (props.items.length * itemWidth <= carouselSlideWidth) {
return props.items.length;
}
Expand All @@ -83,9 +124,10 @@ const groupSize = computed(() => {
});

const groupedItems = computed(() => {
const groupSizeValue = groupSize.value ? groupSize.value : props.items.length;
return props.items.reduce((resultArray, item, index) => {
const chunkIndex = Math.floor(index / groupSizeValue);
const chunkIndex = Math.floor(
index / (groupSize.value ? groupSize.value : props.items.length),
);
if (!resultArray[chunkIndex]) {
resultArray[chunkIndex] = [];
}
Expand Down
236 changes: 117 additions & 119 deletions packages/modules/web_themes/koala/source/src/components/BaseTable.vue
Original file line number Diff line number Diff line change
@@ -1,132 +1,130 @@
<template>
<div class="q-pa-md">
<q-table
class="sticky-header-table"
:class="{ 'custom-table-height': tableHeight }"
:rows="mappedRows"
:columns="mappedColumns"
row-key="id"
v-model:expanded="expanded"
:filter="filterModel"
:filter-method="customFilterMethod"
virtual-scroll
:virtual-scroll-item-size="48"
:virtual-scroll-sticky-size-start="30"
@row-click="onRowClick"
binary-state-sort
:pagination="{ rowsPerPage: 0 }"
hide-bottom
>
<!-- search field ------------------------------------------------------->
<template #top v-if="searchInputVisible">
<div class="row full-width items-center q-mb-sm">
<div class="col">
<q-input
v-model="filterModel"
dense
outlined
color="white"
placeholder="Suchen..."
class="search-field white-outline-input"
input-class="text-white"
>
<template #append>
<q-icon name="search" color="white" />
</template>
</q-input>
</div>
</div>
</template>

<!-- 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 }"
<q-table
class="sticky-header-table"
:class="{ 'custom-table-height': tableHeight }"
:rows="mappedRows"
:columns="mappedColumns"
row-key="id"
v-model:expanded="expanded"
:filter="filterModel"
:filter-method="customFilterMethod"
virtual-scroll
:virtual-scroll-item-size="48"
:virtual-scroll-sticky-size-start="30"
@row-click="onRowClick"
binary-state-sort
:pagination="{ rowsPerPage: 0 }"
hide-bottom
>
<!-- search field ------------------------------------------------------->
<template #top v-if="searchInputVisible">
<div class="row full-width items-center q-mb-sm">
<div class="col">
<q-input
v-model="filterModel"
dense
outlined
color="white"
placeholder="Suchen..."
class="search-field white-outline-input"
input-class="text-white"
>
{{ 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 #append>
<q-icon name="search" color="white" />
</template>

<!-- all other column data -->
<q-td
v-else
:props="{
</q-input>
</div>
</div>
</template>

<!-- 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,
// 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>
</slot>
</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>

<!-- 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>
</q-tr>
</template>

<!-- forward any other slots not related to table e.g top search field -------------------->
<template
v-for="slotName in forwardedSlotNames"
:key="slotName"
v-slot:[slotName]="slotProps"
</template>
</q-tr>

<!-- expansion row -->
<q-tr
v-show="rowProps.expand"
:key="`xp-${rowProps.key}`"
:props="rowProps"
class="q-virtual-scroll--with-prev"
>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
</q-table>
</div>
<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="slotName in forwardedSlotNames"
:key="slotName"
v-slot:[slotName]="slotProps"
>
<slot :name="slotName" v-bind="slotProps"></slot>
</template>
</q-table>
</template>

<script setup lang="ts" generic="T extends Record<string, unknown>">
Expand Down
Loading