Implement result filters [SCI-9013]

This commit is contained in:
Martin Artnik 2023-08-23 11:59:21 +02:00
parent 1253511020
commit b3965e5aca
11 changed files with 295 additions and 23 deletions

View file

@ -11,8 +11,13 @@ class ResultsController < ApplicationController
respond_to do |format|
format.json do
# API endpoint
@results = @my_module.results
apply_sort!
apply_filters!
render(
json: apply_sort(@my_module.results),
json: @results,
formats: :json
)
end
@ -76,7 +81,6 @@ class ResultsController < ApplicationController
end
def update_asset_view_mode
html = ''
ActiveRecord::Base.transaction do
@result.assets_view_mode = params[:assets_view_mode]
@result.save!(touch: false)
@ -117,23 +121,34 @@ class ResultsController < ApplicationController
params.require(:result).permit(:name)
end
def apply_sort(results)
def apply_sort!
case params[:sort]
when 'updated_at_asc'
results.order(updated_at: :asc)
@results = @results.order(updated_at: :asc)
when 'updated_at_desc'
results.order(updated_at: :desc)
@results = @results.order(updated_at: :desc)
when 'created_at_asc'
results.order(created_at: :asc)
@results = @results.order(created_at: :asc)
when 'created_at_desc'
results.order(created_at: :desc)
@results = @results.order(created_at: :desc)
when 'name_asc'
results.order(name: :asc)
@results = @results.order(name: :asc)
when 'name_desc'
results.order(name: :desc)
@results = @results.order(name: :desc)
end
end
def apply_filters!
if params[:query].present?
@results = @results.search(current_user, params[:archived] == 'true', params[:query], params[:page] || 1)
end
@results = @results.where('created_at >= ?', params[:created_at_from]) if params[:created_at_from]
@results = @results.where('created_at <= ?', params[:created_at_to]) if params[:created_at_to]
@results = @results.where('updated_at >= ?', params[:updated_at_from]) if params[:updated_at_from]
@results = @results.where('updated_at <= ?', params[:updated_at_to]) if params[:updated_at_to]
end
def load_my_module
@my_module = MyModule.readable_by_user(current_user).find(params[:my_module_id])
end

View file

@ -1,6 +1,13 @@
<template>
<div class="results-wrapper">
<ResultsToolbar :sort="sort" @setSort="setSort" @newResult="createResult" @expandAll="expandAll" @collapseAll="collapseAll" class="mb-3" />
<ResultsToolbar :sort="sort"
@setSort="setSort"
@setFilters="setFilters"
@newResult="createResult"
@expandAll="expandAll"
@collapseAll="collapseAll"
class="mb-3"
/>
<div class="results-list">
<Result v-for="result in results" :key="result.id" :result="result" />
</div>
@ -21,7 +28,8 @@
data() {
return {
results: [],
sort: 'created_at_desc'
sort: 'created_at_desc',
filters: {}
}
},
created() {
@ -30,18 +38,26 @@
methods: {
loadResults() {
axios.get(
`${this.url}?sort=${this.sort}`,
`${this.url}`,
{
params: {
sort: this.sort,
...this.filters
},
headers: {
'Accept': 'application/json'
}
}
},
).then((response) => this.results = response.data.data);
},
setSort(sort) {
this.sort = sort;
this.loadResults();
},
setFilters(filters) {
this.filters = filters;
this.loadResults();
},
createResult() {
axios.post(
`${this.url}`,

View file

@ -14,9 +14,7 @@
{{ i18n.t('my_modules.results.collapse_label') }}
</button>
<button class="btn btn-light icon-btn mr-3">
<i class="sn-icon sn-icon-filter"></i>
</button>
<FilterDropdown :filters="filters" @applyFilters="setFilters" />
<div class="dropdown">
<button class="dropdown-toggle btn btn-light icon-btn mr-3" id="sortDropdown" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
@ -44,17 +42,47 @@
'name_desc'
];
import FilterDropdown from '../shared/filters/filter_dropdown.vue';
export default {
name: 'ResultsToolbar',
props: {
sort: { type: String, required: true }
sort: { type: String, required: true },
},
data() {
return {
filters: null
}
},
components: { FilterDropdown },
created() {
this.filters = [
{
key: 'query',
type: 'Text',
label: this.i18n.t('my_modules.results.filters.query.label'),
placeholder: this.i18n.t('my_modules.results.filters.query.placeholder')
},
{
key: 'created_at',
type: 'DateRange',
label: this.i18n.t('my_modules.results.filters.created_at.label')
},
{
key: 'updated_at',
type: 'DateRange',
label: this.i18n.t('my_modules.results.filters.updated_at.label')
}
];
this.sorts = SORTS;
},
methods: {
setSort(sort) {
this.$emit('setSort', sort);
},
setFilters(filters) {
this.$emit('setFilters', filters);
}
}
}

View file

@ -5,17 +5,19 @@
type="datetime"
class="form-control calendar-input sci-input-field"
:id="this.selectorId"
placeholder='dd/mm/yyyy'
:placeholder="placeholder || 'dd/mm/yyyy'"
/>
<i class="sn-icon sn-icon-calendar"></i>
</div>
</template>
import '../../../../vendor/assets/javascripts/bootstrap-datetimepicker';
<script>
import '../../../../vendor/assets/javascripts/bootstrap-datetimepicker';
export default {
name: 'DatePicker',
props: {
placeholder: { type: String },
selectorId: { type: String, required: true },
useCurrent: { type: Boolean, default: true },
defaultValue: { type: Date, default: null }
@ -26,7 +28,7 @@ import '../../../../vendor/assets/javascripts/bootstrap-datetimepicker';
useCurrent: this.useCurrent,
ignoreReadonly: this.ignoreReadOnly,
locale: this.i18n.locale,
format: this.dateFormat,
format: $('body').data('datetime-picker-format'),
date: this.defaultValue
}
);

View file

@ -1,7 +1,17 @@
<template>
<div class="date-time-picker">
<DatePicker v-if="!timeOnly" @change="updateDate" :selectorId="`${this.selectorId}_Date`" :defaultValue="defaultValue" />
<TimePicker v-if="!dateOnly" @change="updateTime" :selectorId="`${this.selectorId}_Time`" :defaultValue="getTime(defaultValue)" />
<DatePicker v-if="!timeOnly"
@change="updateDate"
:placeholder="placeholder"
:selectorId="`${this.selectorId}_Date`"
:defaultValue="defaultValue"
/>
<TimePicker v-if="!dateOnly"
@change="updateTime"
:placeholder="placeholder"
:selectorId="`${this.selectorId}_Time`"
:defaultValue="getTime(defaultValue)"
/>
</div>
</template>
@ -15,7 +25,8 @@
dateOnly: { type: Boolean, default: false },
timeOnly: { type: Boolean, default: false },
selectorId: { type: String, required: true },
defaultValue: {type: Date, required: false }
defaultValue: { type: Date, required: false },
placeholder: { type: String }
},
data() {
return {

View file

@ -0,0 +1,81 @@
<template>
<div class="relative">
<div ref="dropdown" class="filter-container dropdown" :class="{ 'filters-applied': appliedDotIsShown }">
<button class="btn btn-light icon-btn filter-button" :title="i18n.t('filters_modal.title')" data-toggle="dropdown"><i class="sn-icon sn-icon-filter"></i></button>
<div class="dropdown-menu dropdown-menu-right filter-dropdown" @click.stop.self>
<div class="header !-mb-2">
<div class="title">{{ i18n.t('filters_modal.title') }}</div>
<button @click="toggleDropdown" type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="body p-3">
<div v-for="filter in filters" :key="filter.key + resetFilters" class="mt-4">
<Component :is="`${filter.type}Filter`" :filter="filter" :value="filterValues[filter.key]" @update="updateFilter" />
</div>
</div>
<div class="footer">
<div class="buttons">
<div @click.prevent="clearFilters" class="btn btn-secondary">
{{ i18n.t('filters_modal.clear_btn') }}
</div>
<div @click.prevent="applyFilters" class="btn btn-primary">
{{ i18n.t('filters_modal.show_btn.one') }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import TextFilter from './inputs/text_filter.vue'
import SelectFilter from './inputs/select_filter.vue';
import DateRangeFilter from './inputs/date_range_filter.vue';
import CheckboxFilter from './inputs/checkbox_filter.vue';
export default {
name: 'FilterDropdown',
props: {
filters: { type: Array, required: true }
},
components: { TextFilter, SelectFilter, DateRangeFilter, CheckboxFilter },
data() {
return {
filterValues: {},
filtersApplied: false,
resetFilters: false
}
},
computed: {
appliedDotIsShown() {
return this.filtersApplied && Object.keys(this.filterValues).length !== 0;
}
},
methods: {
updateFilter(params) {
if (params.value !== '' && params.value !== undefined && params.value !== null) {
this.$set(this.filterValues, params.key, params.value);
} else {
this.$delete(this.filterValues, params.key);
}
},
applyFilters() {
this.filtersApplied = true;
this.$emit('applyFilters', this.filterValues);
this.toggleDropdown();
},
clearFilters() {
this.filterValues = {};
this.filtersApplied = false;
// This changes filter keys in the v-for, so they get fully reloaded,
// which prevents perserved state issues with datepickers
this.resetFilters = !this.resetFilters;
this.$emit('applyFilters', this.filterValues);
this.toggleDropdown();
},
toggleDropdown() {
this.$refs.dropdown.click();
}
}
}
</script>

View file

@ -0,0 +1,14 @@
<template>
<div>TODO</div>
</template>
<script>
export default {
name: 'CheckboxFilter',
props: {
filter: { type: Object, required: true }
},
methods: {
}
}
</script>

View file

@ -0,0 +1,52 @@
<template>
<div>
<label>{{ filter.label }}</label>
<div class="flex center justify-between">
<DateTimePicker
@change="updateDateFrom"
:placeholder="i18n.t('From')"
:dateOnly="true"
:selectorId="`DatePicker${filter.key}`"
/>
<div class="px-2 mt-2"> </div>
<DateTimePicker
@change="updateDateTo"
:placeholder="i18n.t('To')"
:dateOnly="true"
:selectorId="`DatePickerTo${filter.key}`"
/>
</div>
</div>
</template>
<script>
import DateTimePicker from '../../date_time_picker.vue'
export default {
name: 'DateRangeFilter',
props: {
filter: { type: Object, required: true }
},
components: { DateTimePicker },
data() {
return {
dateFrom: null,
dateTo: null
}
},
methods: {
updateDateFrom(value) {
this.dateFrom = value;
this.updateFilter();
},
updateDateTo(value) {
this.dateTo = value;
this.updateFilter();
},
updateFilter() {
this.$emit('update', { key: `${this.filter.key}_from`, value: this.dateFrom });
this.$emit('update', { key: `${this.filter.key}_to`, value: this.dateTo });
}
}
}
</script>

View file

@ -0,0 +1,15 @@
<template>
<div>TODO</div>
</template>
<script>
import Select from '../../select.vue';
export default {
name: 'SelectFilter',
props: {
filter: { type: Object, required: true }
},
components: { Select }
}
</script>

View file

@ -0,0 +1,28 @@
<template>
<div>
<label>{{ filter.label }}</label>
<div class="sci-input-container">
<input class="sci-input-field"
type="text"
:id="filter.key"
:placeholder="filter.placeholder"
:value="value"
@input="update($event.target.value)">
</div>
</div>
</template>
<script>
export default {
name: 'TextFilter',
props: {
filter: { type: Object, required: true },
value: { type: String }
},
methods: {
update(value) {
this.$emit('update', { key: this.filter.key, value: value });
}
}
}
</script>

View file

@ -1265,6 +1265,14 @@ en:
created_at_desc: "Created last"
name_asc: "Name A to Z"
name_desc: "Name Z to A"
filters:
query:
label: 'Contains text'
placeholder: 'Enter search terms'
created_at:
label: 'Created date'
updated_at:
label: 'Updated date'
options:
comment_title: "Comments"
no_comments: "No comments"
@ -3703,6 +3711,8 @@ en:
Added: 'Added'
by: 'by'
name: 'name'
From: 'From'
To: 'To'
marvinjs:
new_sketch: "New Chemical Drawing"