fixed getting wrong order results

This commit is contained in:
Luca Haid
2025-05-13 09:18:34 +02:00
parent d65e67571d
commit a6d577023f
4 changed files with 221 additions and 39 deletions
+198 -33
View File
@@ -1,43 +1,208 @@
Vue.component('tt-select', {
props: {
options: {type: Array, required: true},
label: {type: String, required: false},
required: {type: Boolean, default: false},
value: {type: [String, Number], required: false},
disabled: {type: Boolean, default: false},
suffix: {type: String, required: false},
sm: {type: Boolean, default: false},
row: {type: Boolean, default: false},
options: { type: Array, required: true },
label: { type: String, default: null },
required: { type: Boolean, default: false },
value: { type: [String, Number, Array], default: null },
disabled: { type: Boolean, default: false },
suffix: { type: String, default: null },
sm: { type: Boolean, default: false },
row: { type: Boolean, default: false },
multiple: { type: Boolean, default: false },
searchable: { type: Boolean, default: true }
},
data() {
return {
selectedOption: undefined,
open: false,
searchQuery: '',
// internal model: array if multiple, else primitive
selected: this.multiple
? (Array.isArray(this.value) ? [...this.value] : [])
: this.value
};
},
mounted() {
this.selectedOption = this.value;
},
watch: {
value(newValue) {
this.selectedOption = newValue;
},
},
template: `
<div class="form-group" :class="{'row': row}">
<label v-if="label"
:class="{'col-form-label': row, 'col-sm-4': row, 'col-form-label-sm': sm && row}"
:for="label">{{ label }}</label>
<select class="form-control" :class="{'form-control-sm': sm, 'col-sm-8': row}"
:required="required" v-model="selectedOption" :disabled="disabled"
@change="$emit('input', $event.target.value ? $event.target.value : undefined)">
<template v-for="option of options">
<option v-if="['string','number'].includes(typeof option)" :value="option" :disabled="option.disabled === true">{{ option }}
<template v-if="suffix"> {{ suffix }}</template>
</option>
<option v-else :value="option.value" :disabled="option.disabled === true">{{ option.text }}</option>
</template>
</select>
watch: {
// keep internal model in sync with external v-model
value(newVal) {
this.selected = this.multiple
? (Array.isArray(newVal) ? [...newVal] : [])
: newVal;
}
},
mounted() {
document.addEventListener('click', this.onClickOutside);
},
beforeDestroy() {
document.removeEventListener('click', this.onClickOutside);
},
computed: {
// normalize all options to { value, text, disabled }
normalizedOptions() {
return this.options.map(opt => {
if (opt !== null && typeof opt === 'object') {
return {
value: opt.value,
text: opt.text,
disabled: !!opt.disabled
};
}
return {
value: opt,
text: String(opt),
disabled: false
};
});
},
// filter by searchQuery
filteredOptions() {
if (!this.searchable || !this.searchQuery) {
return this.normalizedOptions;
}
const q = this.searchQuery.toLowerCase();
return this.normalizedOptions.filter(o =>
o.text.toLowerCase().includes(q)
);
},
// what to show on the button
displayText() {
if (this.multiple) {
const arr = Array.isArray(this.selected) ? this.selected : [];
const count = arr.length;
if (count === 0) return '';
if (count === 1) {
const o = this.normalizedOptions.find(x => x.value === arr[0]);
return o
? o.text + (this.suffix ? ' ' + this.suffix : '')
: '';
}
// more than one selected
return `${count} ausgewählt`;
}
// single select
const sel = this.normalizedOptions.find(o => o.value === this.selected);
return sel
? sel.text + (this.suffix ? ' ' + this.suffix : '')
: '';
}
},
methods: {
toggleDropdown() {
if (this.disabled) return;
this.open = !this.open;
},
onClickOutside(e) {
if (!this.$el.contains(e.target)) {
this.open = false;
}
},
isSelected(opt) {
return this.multiple
? Array.isArray(this.selected) && this.selected.includes(opt.value)
: this.selected === opt.value;
},
selectOption(opt) {
if (opt.disabled) return;
if (this.multiple) {
const arr = Array.isArray(this.selected) ? [...this.selected] : [];
const idx = arr.indexOf(opt.value);
if (idx > -1) arr.splice(idx, 1);
else arr.push(opt.value);
this.selected = arr;
this.$emit('input', arr); // Fixed: Always emit the array when multiple
} else {
this.selected = opt.value;
this.$emit('input', opt.value);
this.open = false;
}
}
},
template: `
<div class="form-group" :class="{ row: row }">
<label v-if="label"
:for="label"
:class="{
'col-form-label': row,
'col-sm-4': row,
'col-form-label-sm': sm && row
}">
{{ label }}
</label>
<div :class="row ? 'col-sm-8' : ''">
<div class="dropdown" :class="{ show: open }">
<button type="button"
class="btn btn-outline-secondary form-control"
:class="{ 'form-control-sm': sm, 'btn-sm': sm }"
:disabled="disabled"
@click.stop="toggleDropdown"
style="display:flex; align-items:center;">
<span style="
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
">
{{ displayText || 'Auswählen...' }}
</span>
<i class="fas fa-chevron-down ml-2"></i>
</button>
<div class="dropdown-menu p-2"
:class="{ show: open }"
style="
min-width: 100%;
max-width: 400px;
max-height: 300px;
overflow-y: auto;
">
<input v-if="searchable"
type="text"
class="form-control mb-2"
v-model="searchQuery"
:disabled="disabled"
placeholder="Suchen...">
<div v-for="opt in filteredOptions"
:key="opt.value"
class="dropdown-item px-2 py-1"
:class="{ disabled: opt.disabled }"
@click.stop>
<label class="d-flex align-items-center m-0 w-100">
<input v-if="multiple"
type="checkbox"
class="form-check-input mr-2"
:checked="isSelected(opt)"
:disabled="opt.disabled"
@change.prevent="selectOption(opt)">
<input v-else
type="radio"
class="form-check-input mr-2"
:name="label"
:value="opt.value"
:checked="isSelected(opt)"
:disabled="opt.disabled"
@change.prevent="selectOption(opt)">
<span>
{{ opt.text }}<span v-if="suffix"> {{ suffix }}</span>
</span>
</label>
</div>
</div>
</div>
</div>
</div>
`
});
});
+5 -4
View File
@@ -162,7 +162,7 @@ Vue.component('tt-table', {
<tt-input v-if="column.filter === 'search' && !disableFiltering" v-model="filters[column.key]" sm/>
<tt-icon-select v-else-if="column.filter === 'iconSelect' && !disableFiltering" :options="column.filterOptions" v-model="filters[column.key]" sm :multiple="column.filterOptions.length > 2 && this.ssr === true"/>
<tt-number-range v-else-if="column.filter === 'numberRange' && !disableFiltering" :returnText="!ssr" v-model="filters[column.key]" sm/>
<tt-select v-else-if="column.filter === 'select' && !disableFiltering" :options="[{text: 'Alle', value: undefined}, ...column.filterOptions]" v-model="filters[column.key]" sm/>
<tt-select v-else-if="column.filter === 'select' && !disableFiltering" :options="[{text: 'Alle', value: undefined}, ...column.filterOptions]" v-model="filters[column.key]" sm multiple/>
<tt-autocomplete v-else-if="column.filter === 'autocomplete' && !disableFiltering" :api-url="column.filterOptions" v-model="filters[column.key]" sm/>
<tt-date-picker v-else-if="(column.filter === 'date' || column.filter === 'datepicker') && !disableFiltering" v-model="filters[column.key]" sm/>
<!-- @formatter:on -->
@@ -562,14 +562,15 @@ Vue.component('tt-table', {
filters: {
handler: function () {
if (!this.isInitialised) return;
console.log('Filters changed:', this.filters);
// go through filters and if there is a set value in filters and the filter of the column is select or autocomplete then parse the value to int
for (const key in this.filters) {
if (this.filters[key] && (this.columns[key].filter === 'select' || this.columns[key].filter === 'autocomplete')) {
// only if first character is a number
if (!isNaN(this.filters[key][0])) {
this.filters[key] = parseInt(this.filters[key]);
}
// if (!isNaN(this.filters[key][0])) {
// this.filters[key] = parseInt(this.filters[key]);
// }
}
}