3
0

Add support for multiple record list filters

This commit is contained in:
Kelani Tolulope
2023-03-08 17:48:59 +01:00
committed by Jože Fortun
parent 363afde876
commit 63f1647f7b
6 changed files with 224 additions and 11 deletions
@@ -24,13 +24,12 @@
class="py-2 d-print-none"
fluid
>
<b-row
no-gutters
class="justify-content-between wrap-with-vertical-gutters"
<div
class="d-flex flex-wrap justify-content-between wrap-with-vertical-gutters"
>
<div class="text-nowrap flex-grow-1">
<div class="text-nowrap">
<div
class="wrap-with-vertical-gutters"
class="wrap-with-vertical-gutters flex-wrap"
>
<template v-if="recordListModule.canCreateRecord">
<template v-if="inlineEditing">
@@ -77,6 +76,25 @@
@export="onExport"
/>
<b-dropdown
v-if="options.recordFilters.length"
size="lg"
variant="light"
:text="$t('filter.filter')"
>
<template
v-for="(dropdown, idx) in options.recordFilters"
>
<b-dropdown-item
v-if="checkFilterIsValid(dropdown.roles)"
:key="idx"
@click="updateFilter(dropdown.value, dropdown.title)"
>
{{ dropdown.title }}
</b-dropdown-item>
</template>
</b-dropdown>
<column-picker
v-if="!options.hideConfigureFieldsButton"
:module="recordListModule"
@@ -88,20 +106,31 @@
</div>
<div
v-if="!options.hideSearch && !inlineEditing"
class="flex-grow-1 w-25"
class="w-25"
>
<c-input-search
v-model.trim="query"
:placeholder="$t('general.label.search')"
/>
</div>
</b-row>
</div>
<div
v-if="drillDownFilter"
class="d-flex justify-content-end mt-1"
class="d-flex justify-content-between mt-1"
>
<b-form-tags
v-model="selectedFilters"
tag-variant="outline-primary"
tag-pills
input-class="d-none"
placeholder=""
style="width: fit-content"
class="my-2 border-0 p-0"
@input="removeFilter"
/>
<b-button
v-if="drillDownFilter"
variant="outline-light"
size="sm"
class="text-nowrap text-primary border-0"
@@ -259,6 +288,7 @@
:record-list-filter="recordListFilter"
@filter="onFilter"
/>
<b-button
v-if="field.sortable"
variant="link p-0 ml-1"
@@ -801,6 +831,7 @@ export default {
ctr: 0,
items: [],
showingDeletedRecords: false,
selectedFilters: [],
}
},
@@ -966,6 +997,10 @@ export default {
isReminderActionVisible () {
return !this.options.hideRecordReminderButton
},
authUserRoles () {
return this.$auth.user.roles
},
},
watch: {
@@ -1667,6 +1702,40 @@ export default {
return !expressions.value
},
updateFilter (filter, title) {
const lastFilterIdx = this.recordListFilter.length - 1
filter = filter.map((filter) => ({ ...filter, title }))
if (this.recordListFilter.length) {
this.recordListFilter[lastFilterIdx].groupCondition = 'AND'
}
this.recordListFilter = this.recordListFilter.concat(filter)
this.selectedFilters.push(title)
this.refresh(true)
},
removeFilter (currentFilters) {
this.recordListFilter = this.recordListFilter.filter(({ title }) => currentFilters.includes(title))
this.refresh(true)
},
checkFilterIsValid (filterRoles) {
if (!filterRoles.length) return true
let result = false
for (let i = 0; i < filterRoles.length; i++) {
const role = filterRoles[i]
if (this.authUserRoles.includes(role)) {
result = true
break
}
}
return result
},
},
}
</script>
@@ -246,6 +246,98 @@
</b-form-group>
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
breakpoint="md"
:label="$t('filter.recordFilter')"
>
<b-table-simple
v-if="recordListModule && recordListModule.fields.length"
borderless
>
<thead>
<tr>
<th>
{{ $t('filter.role') }}
</th>
<th>
{{ $t('filter.title') }}
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(filter, index) in options.recordFilters"
:key="index"
>
<td>
<vue-select
v-model="filter.roles"
:options="roleOptions"
:reduce="role => role.roleID"
:get-option-label="getRoleLabel"
append-to-body
:placeholder="$t('filter.searchRolePlaceholder')"
multiple
class="bg-white"
/>
</td>
<td>
<b-input-group>
<b-form-input
v-model="filter.title"
placeholder="Title"
type="text"
class="h-100"
/>
<b-input-group-append>
<b-button
variant="light"
class="d-flex align-items-center"
>
<record-list-filter
class="d-print-none"
:target="`record-filter-${index}`"
:namespace="namespace"
:module="recordListModule"
:selected-field="recordListModule.fields[0]"
:record-list-filter="filter.value"
@filter="(filter) => onFilter(filter, index)"
/>
</b-button>
<b-button
variant="light"
class="d-flex align-items-center"
>
<c-input-confirm
button-class="text-right"
@confirmed="options.recordFilters.splice(index, 1)"
/>
</b-button>
</b-input-group-append>
</b-input-group>
</td>
</tr>
</tbody>
</b-table-simple>
<b-button
variant="primary"
class="d-flex align-items-center px-0 text-decoration-none"
@click="addNewRecordListFilter"
>
<font-awesome-icon
:icon="['fas', 'plus']"
size="sm"
class="mr-1"
/>
{{ $t('general.label.add') }}
</b-button>
</b-form-group>
</b-col>
</b-row>
</div>
<hr>
@@ -610,9 +702,11 @@
<script>
import { mapGetters } from 'vuex'
import { NoID } from '@cortezaproject/corteza-js'
import { VueSelect } from 'vue-select'
import base from './base'
import AutomationTab from './Shared/AutomationTab'
import FieldPicker from 'corteza-webapp-compose/src/components/Common/FieldPicker'
import RecordListFilter from 'corteza-webapp-compose/src/components/Common/RecordListFilter'
import { components } from '@cortezaproject/corteza-vue'
const { CInputPresort, CInputCheckbox } = components
@@ -628,6 +722,8 @@ export default {
FieldPicker,
CInputPresort,
CInputCheckbox,
RecordListFilter,
VueSelect,
},
extends: base,
@@ -638,6 +734,7 @@ export default {
on: this.$t('general:label.yes'),
off: this.$t('general:label.no'),
},
roleOptions: [],
}
},
@@ -778,5 +875,38 @@ export default {
this.options.editFields = this.options.editFields.filter(a => fields.some(b => a.name === b.name))
},
},
mounted () {
this.fetchRoles()
},
methods: {
getRoleLabel ({ name }) {
return name
},
async fetchRoles () {
this.$SystemAPI.roleList().then(({ set: roles = [] }) => {
this.roleOptions = roles
})
},
onFilter (filter = [], index) {
this.options.recordFilters[index].value = filter
},
addNewRecordListFilter () {
this.options.recordFilters.push({
title: '',
value: [],
})
},
},
}
</script>
<style>
.w-fit {
width: fit-content;
}
</style>
@@ -67,7 +67,6 @@
<div
v-if="toolbarSet"
class="overflow-hidden"
>
<slot
name="toolbar"
@@ -58,6 +58,7 @@ interface Options {
bulkRecordEditEnabled: boolean;
inlineRecordEditEnabled: boolean;
recordFilters: Array<Record<string, any>>
}
const defaults: Readonly<Options> = Object.freeze({
@@ -107,6 +108,7 @@ const defaults: Readonly<Options> = Object.freeze({
bulkRecordEditEnabled: true,
inlineRecordEditEnabled: false
recordFilters: []
})
export class PageBlockRecordList extends PageBlock {
@@ -130,6 +132,10 @@ export class PageBlockRecordList extends PageBlock {
this.options.fields = o.fields
}
if (o.recordFilters) {
this.options.recordFilters = o.recordFilters
}
if (o.editFields) {
this.options.editFields = o.editFields
}
+3 -1
View File
@@ -40,7 +40,8 @@ interface OAuth2TokenResponse {
access_token: string;
refresh_token: string;
expires_in: number;
roles?: string;
name?: string;
handle?: string;
email?: string;
@@ -573,6 +574,7 @@ export class Auth {
name: oa2tkn.name,
handle: oa2tkn.handle,
email: oa2tkn.email,
roles: oa2tkn.roles ? [oa2tkn.roles] : [],
})
if (oa2tkn.preferred_language) {
@@ -758,3 +758,10 @@ tabs:
addBlock: Add new block
delete: Delete tab
label: Tabs
filter:
recordFilter: Record filter
role: Role
filter: Filter
title: Title
searchRolePlaceholder: Select role to apply filter to