Adjust preset record list filter styles and logic
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
<b-button
|
||||
:id="popoverTarget"
|
||||
:title="$t('recordList.filter.title')"
|
||||
variant="link p-0 ml-1"
|
||||
:class="[inFilter ? 'text-primary' : 'text-secondary']"
|
||||
:variant="variant"
|
||||
:class="[inFilter ? 'text-primary' : 'text-secondary', buttonClass]"
|
||||
:style="buttonStyle"
|
||||
@click.stop
|
||||
>
|
||||
<font-awesome-icon :icon="['fas', 'filter']" />
|
||||
@@ -261,18 +262,36 @@ export default {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
namespace: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
module: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
|
||||
recordListFilter: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'link',
|
||||
},
|
||||
|
||||
buttonClass: {
|
||||
type: String,
|
||||
default: 'p-0',
|
||||
},
|
||||
|
||||
buttonStyle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
data () {
|
||||
@@ -500,6 +519,7 @@ export default {
|
||||
this.componentFilter = [
|
||||
this.createDefaultFilterGroup(),
|
||||
]
|
||||
this.$emit('reset')
|
||||
|
||||
this.onSave(false)
|
||||
},
|
||||
@@ -537,40 +557,38 @@ export default {
|
||||
},
|
||||
|
||||
onOpen () {
|
||||
if (this.recordListFilter.length) {
|
||||
// Create record and fill its values property if value exists
|
||||
this.componentFilter = this.recordListFilter
|
||||
.filter(({ filter = [] }) => filter.some(f => f.name))
|
||||
.map(({ groupCondition, filter = [] }) => {
|
||||
filter = filter.map(({ value, ...f }) => {
|
||||
f.record = new compose.Record(this.mock.module, {})
|
||||
// Create record and fill its values property if value exists
|
||||
this.componentFilter = this.recordListFilter
|
||||
.filter(({ filter = [] }) => filter.some(f => f.name))
|
||||
.map(({ groupCondition, filter = [] }) => {
|
||||
filter = filter.map(({ value, ...f }) => {
|
||||
f.record = new compose.Record(this.mock.module, {})
|
||||
|
||||
if (this.isBetweenOperator(f.operator)) {
|
||||
if (this.getField(f.name).isSystem) {
|
||||
f.record[`${f.name}-start`] = value.start
|
||||
f.record[`${f.name}-end`] = value.end
|
||||
} else {
|
||||
f.record.values[`${f.name}-start`] = value.start
|
||||
f.record.values[`${f.name}-end`] = value.end
|
||||
}
|
||||
|
||||
const field = this.mock.module.fields.find(field => field.name === f.name)
|
||||
|
||||
this.mock.module.fields.push({ ...field, name: `${f.name}-end` })
|
||||
this.mock.module.fields.push({ ...field, name: `${f.name}-start` })
|
||||
} else if (Object.keys(f.record).includes(f.name)) {
|
||||
// If its a system field add value to root of record
|
||||
f.record[f.name] = value
|
||||
if (this.isBetweenOperator(f.operator)) {
|
||||
if (this.getField(f.name).isSystem) {
|
||||
f.record[`${f.name}-start`] = value.start
|
||||
f.record[`${f.name}-end`] = value.end
|
||||
} else {
|
||||
f.record.values[f.name] = value
|
||||
f.record.values[`${f.name}-start`] = value.start
|
||||
f.record.values[`${f.name}-end`] = value.end
|
||||
}
|
||||
|
||||
return f
|
||||
})
|
||||
const field = this.mock.module.fields.find(field => field.name === f.name)
|
||||
|
||||
return { groupCondition, filter }
|
||||
this.mock.module.fields.push({ ...field, name: `${f.name}-end` })
|
||||
this.mock.module.fields.push({ ...field, name: `${f.name}-start` })
|
||||
} else if (Object.keys(f.record).includes(f.name)) {
|
||||
// If its a system field add value to root of record
|
||||
f.record[f.name] = value
|
||||
} else {
|
||||
f.record.values[f.name] = value
|
||||
}
|
||||
|
||||
return f
|
||||
})
|
||||
}
|
||||
|
||||
return { groupCondition, filter }
|
||||
})
|
||||
|
||||
// If no filterGroups, add default
|
||||
if (!this.componentFilter.length) {
|
||||
|
||||
@@ -24,12 +24,13 @@
|
||||
class="py-2 d-print-none"
|
||||
fluid
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-wrap justify-content-between wrap-with-vertical-gutters"
|
||||
<b-row
|
||||
no-gutters
|
||||
class="justify-content-between wrap-with-vertical-gutters"
|
||||
>
|
||||
<div class="text-nowrap">
|
||||
<div class="text-nowrap flex-grow-1">
|
||||
<div
|
||||
class="wrap-with-vertical-gutters flex-wrap"
|
||||
class="wrap-with-vertical-gutters"
|
||||
>
|
||||
<template v-if="recordListModule.canCreateRecord">
|
||||
<template v-if="inlineEditing">
|
||||
@@ -77,20 +78,22 @@
|
||||
/>
|
||||
|
||||
<b-dropdown
|
||||
v-if="options.recordFilters.length"
|
||||
v-if="options.filterPresets.length"
|
||||
size="lg"
|
||||
variant="light"
|
||||
:text="$t('filter.filter')"
|
||||
right
|
||||
:text="$t('recordList.filter.filters.label')"
|
||||
>
|
||||
<template
|
||||
v-for="(dropdown, idx) in options.recordFilters"
|
||||
v-for="(f, idx) in options.filterPresets"
|
||||
>
|
||||
<b-dropdown-item
|
||||
v-if="checkFilterIsValid(dropdown.roles)"
|
||||
v-if="f.name && isUserRoleMember(f.roles)"
|
||||
:key="idx"
|
||||
@click="updateFilter(dropdown.value, dropdown.title)"
|
||||
:disabled="activeFilters.includes(f.name)"
|
||||
@click="updateFilter(f.filter, f.name)"
|
||||
>
|
||||
{{ dropdown.title }}
|
||||
{{ f.name }}
|
||||
</b-dropdown-item>
|
||||
</template>
|
||||
</b-dropdown>
|
||||
@@ -113,27 +116,37 @@
|
||||
:placeholder="$t('general.label.search')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</b-row>
|
||||
|
||||
<div
|
||||
class="d-flex justify-content-between mt-1"
|
||||
v-if="activeFilters.length || drillDownFilter"
|
||||
class="d-flex mt-2"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
<div
|
||||
v-if="activeFilters.length"
|
||||
class="d-flex align-items-center flex-wrap"
|
||||
>
|
||||
<span class="mr-1">
|
||||
{{ $t('recordList.filter.filters.active') }}
|
||||
</span>
|
||||
<b-form-tags
|
||||
v-model="activeFilters"
|
||||
tag-variant="secondary"
|
||||
tag-pills
|
||||
size="lg"
|
||||
input-class="d-none"
|
||||
tag-class="align-items-center"
|
||||
class="filter-tags border-0 p-0"
|
||||
style="width: fit-content"
|
||||
@input="removeFilter"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<b-button
|
||||
v-if="drillDownFilter"
|
||||
variant="outline-light"
|
||||
size="sm"
|
||||
class="text-nowrap text-primary border-0"
|
||||
class="ml-auto text-nowrap text-primary border-0"
|
||||
@click="setDrillDownFilter(undefined)"
|
||||
>
|
||||
{{ $t('recordList.drillDown.filter.remove') }}
|
||||
@@ -280,13 +293,14 @@
|
||||
>
|
||||
<record-list-filter
|
||||
v-if="!options.hideFiltering && field.filterable"
|
||||
class="d-print-none"
|
||||
:target="uniqueID"
|
||||
:selected-field="field.moduleField"
|
||||
:namespace="namespace"
|
||||
:module="recordListModule"
|
||||
:record-list-filter="recordListFilter"
|
||||
class="d-print-none ml-1"
|
||||
@filter="onFilter"
|
||||
@reset="activeFilters = []"
|
||||
/>
|
||||
|
||||
<b-button
|
||||
@@ -831,7 +845,7 @@ export default {
|
||||
ctr: 0,
|
||||
items: [],
|
||||
showingDeletedRecords: false,
|
||||
selectedFilters: [],
|
||||
activeFilters: [],
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1703,38 +1717,28 @@ export default {
|
||||
return !expressions.value
|
||||
},
|
||||
|
||||
updateFilter (filter, title) {
|
||||
updateFilter (filter, name) {
|
||||
const lastFilterIdx = this.recordListFilter.length - 1
|
||||
filter = filter.map((filter) => ({ ...filter, title }))
|
||||
filter = filter.map((filter) => ({ ...filter, name }))
|
||||
|
||||
if (this.recordListFilter.length) {
|
||||
this.recordListFilter[lastFilterIdx].groupCondition = 'AND'
|
||||
}
|
||||
|
||||
this.recordListFilter = this.recordListFilter.concat(filter)
|
||||
this.selectedFilters.push(title)
|
||||
this.activeFilters.push(name)
|
||||
this.refresh(true)
|
||||
},
|
||||
|
||||
removeFilter (currentFilters) {
|
||||
this.recordListFilter = this.recordListFilter.filter(({ title }) => currentFilters.includes(title))
|
||||
this.recordListFilter = this.recordListFilter.filter(({ name }) => !name || currentFilters.includes(name))
|
||||
this.refresh(true)
|
||||
},
|
||||
|
||||
checkFilterIsValid (filterRoles) {
|
||||
if (!filterRoles.length) return true
|
||||
isUserRoleMember (roles) {
|
||||
if (!roles.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
|
||||
return roles.some(roleID => this.authUserRoles.includes(roleID))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -249,93 +249,98 @@
|
||||
|
||||
<b-row>
|
||||
<b-col>
|
||||
<b-form-group
|
||||
breakpoint="md"
|
||||
:label="$t('filter.recordFilter')"
|
||||
>
|
||||
<b-table-simple
|
||||
v-if="recordListModule && recordListModule.fields.length"
|
||||
borderless
|
||||
<b-form-group
|
||||
:label="$t('recordList.filter.presets')"
|
||||
>
|
||||
<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"
|
||||
<b-table-simple
|
||||
v-if="recordListModule"
|
||||
borderless
|
||||
small
|
||||
responsive="lg"
|
||||
class="mb-1"
|
||||
>
|
||||
<b-thead>
|
||||
<b-tr>
|
||||
<b-th
|
||||
class="text-primary"
|
||||
style="min-width: 300px;"
|
||||
>
|
||||
{{ $t('recordList.filter.name.label') }}
|
||||
</b-th>
|
||||
<b-th
|
||||
class="text-primary"
|
||||
style="width: 45%; min-width: 250px;"
|
||||
>
|
||||
{{ $t('recordList.filter.role.label') }}
|
||||
</b-th>
|
||||
|
||||
<b-th
|
||||
style="width: 100px;"
|
||||
/>
|
||||
</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"
|
||||
>
|
||||
</b-tr>
|
||||
</b-thead>
|
||||
<b-tbody>
|
||||
<b-tr
|
||||
v-for="(filter, index) in options.filterPresets"
|
||||
:key="index"
|
||||
>
|
||||
<b-td>
|
||||
<b-input-group>
|
||||
<b-form-input
|
||||
v-model="filter.name"
|
||||
:placeholder="$t('recordList.filter.name.placeholder')"
|
||||
/>
|
||||
|
||||
<b-input-group-append class="border-0">
|
||||
<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"
|
||||
:record-list-filter="filter.filter"
|
||||
variant="primary"
|
||||
button-class="px-2 pt-2 text-white"
|
||||
button-style="padding-bottom: calc(0.5rem - 2px);"
|
||||
@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-input-group-append>
|
||||
</b-input-group>
|
||||
</b-td>
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
class="d-flex align-items-center px-0 text-decoration-none"
|
||||
@click="addNewRecordListFilter"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'plus']"
|
||||
<b-td>
|
||||
<vue-select
|
||||
v-model="filter.roles"
|
||||
:options="roleOptions"
|
||||
:get-option-label="getRoleLabel"
|
||||
:placeholder="$t('recordList.filter.role.placeholder')"
|
||||
:reduce="role => role.roleID"
|
||||
append-to-body
|
||||
multiple
|
||||
class="bg-white"
|
||||
/>
|
||||
</b-td>
|
||||
|
||||
<b-td
|
||||
class="text-center align-middle pr-2"
|
||||
>
|
||||
<c-input-confirm
|
||||
@confirmed="options.filterPresets.splice(index, 1)"
|
||||
/>
|
||||
</b-td>
|
||||
</b-tr>
|
||||
</b-tbody>
|
||||
</b-table-simple>
|
||||
|
||||
<b-button
|
||||
variant="primary"
|
||||
size="sm"
|
||||
class="mr-1"
|
||||
/>
|
||||
{{ $t('general.label.add') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
class="ml-1"
|
||||
@click="addFilterPreset"
|
||||
>
|
||||
{{ $t('recordList.filter.addFilter') }}
|
||||
</b-button>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
@@ -887,18 +892,19 @@ export default {
|
||||
|
||||
async fetchRoles () {
|
||||
this.$SystemAPI.roleList().then(({ set: roles = [] }) => {
|
||||
this.roleOptions = roles
|
||||
this.roleOptions = roles.filter(({ meta }) => !(meta.context && meta.context.resourceTypes))
|
||||
})
|
||||
},
|
||||
|
||||
onFilter (filter = [], index) {
|
||||
this.options.recordFilters[index].value = filter
|
||||
this.options.filterPresets[index].filter = filter
|
||||
},
|
||||
|
||||
addNewRecordListFilter () {
|
||||
this.options.recordFilters.push({
|
||||
title: '',
|
||||
value: [],
|
||||
addFilterPreset () {
|
||||
this.options.filterPresets.push({
|
||||
name: '',
|
||||
filter: [],
|
||||
roles: [],
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
@@ -65,7 +65,6 @@
|
||||
|
||||
<div
|
||||
v-if="toolbarSet"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<slot
|
||||
name="toolbar"
|
||||
|
||||
@@ -278,6 +278,11 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.b-form-tag {
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
// Supporting CSS to improve print-to-PDF option
|
||||
@media print {
|
||||
@page {
|
||||
|
||||
@@ -5,6 +5,13 @@ import { Module } from '../module'
|
||||
import { Button } from './types'
|
||||
|
||||
const kind = 'RecordList'
|
||||
|
||||
interface FilterPreset {
|
||||
name: string;
|
||||
filter: unknown[];
|
||||
roles: string[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
moduleID: string;
|
||||
prefilter: string;
|
||||
@@ -58,7 +65,7 @@ interface Options {
|
||||
|
||||
bulkRecordEditEnabled: boolean;
|
||||
inlineRecordEditEnabled: boolean;
|
||||
recordFilters: Array<Record<string, any>>
|
||||
filterPresets: FilterPreset[];
|
||||
}
|
||||
|
||||
const defaults: Readonly<Options> = Object.freeze({
|
||||
@@ -107,8 +114,8 @@ const defaults: Readonly<Options> = Object.freeze({
|
||||
showRefresh: false,
|
||||
|
||||
bulkRecordEditEnabled: true,
|
||||
inlineRecordEditEnabled: false
|
||||
recordFilters: []
|
||||
inlineRecordEditEnabled: false,
|
||||
filterPresets: []
|
||||
})
|
||||
|
||||
export class PageBlockRecordList extends PageBlock {
|
||||
@@ -132,8 +139,8 @@ export class PageBlockRecordList extends PageBlock {
|
||||
this.options.fields = o.fields
|
||||
}
|
||||
|
||||
if (o.recordFilters) {
|
||||
this.options.recordFilters = o.recordFilters
|
||||
if (o.filterPresets) {
|
||||
this.options.filterPresets = o.filterPresets
|
||||
}
|
||||
|
||||
if (o.editFields) {
|
||||
|
||||
@@ -40,8 +40,8 @@ interface OAuth2TokenResponse {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
expires_in: number;
|
||||
|
||||
roles?: string;
|
||||
|
||||
roles?: string[];
|
||||
name?: string;
|
||||
handle?: string;
|
||||
email?: string;
|
||||
@@ -574,7 +574,7 @@ export class Auth {
|
||||
name: oa2tkn.name,
|
||||
handle: oa2tkn.handle,
|
||||
email: oa2tkn.email,
|
||||
roles: oa2tkn.roles ? [oa2tkn.roles] : [],
|
||||
roles: oa2tkn.roles || [],
|
||||
})
|
||||
|
||||
if (oa2tkn.preferred_language) {
|
||||
|
||||
@@ -324,6 +324,16 @@ recordList:
|
||||
update: Update filter
|
||||
where: Where
|
||||
without: Without
|
||||
presets: Filter presets
|
||||
filters:
|
||||
label: Filters
|
||||
active: Active filters
|
||||
name:
|
||||
label: Name
|
||||
placeholder: Filter name
|
||||
role:
|
||||
label: Role(s)
|
||||
placeholder: Select role(s) to apply filter to
|
||||
hideRecordCloneButton: Hide clone record button
|
||||
hideRecordEditButton: Hide edit record button
|
||||
hideRecordPermissionsButton: Hide record permissions button
|
||||
@@ -707,61 +717,3 @@ geometry:
|
||||
lockBounds: Lock bounds
|
||||
topLeft: Bounds top left
|
||||
lowerRight: Bounds lower right
|
||||
|
||||
tabs:
|
||||
alertTitle: Set a title for your block
|
||||
title: Tabs
|
||||
addTab: + Add
|
||||
tab: Tab
|
||||
selectBlock: Choose a block
|
||||
noTabs: No tabs configured
|
||||
noBlock: No block configured
|
||||
displayTitle: Display Options
|
||||
preview: Live example
|
||||
newBlockModal: Add new block
|
||||
placeholder:
|
||||
block: Select a block to tab
|
||||
style:
|
||||
appearance:
|
||||
label: Appearance
|
||||
tabs: Tabs
|
||||
pills: Pills
|
||||
small: Small
|
||||
alignment:
|
||||
label: Alignment
|
||||
left: Left
|
||||
center: Center
|
||||
right: Right
|
||||
fillJustify:
|
||||
label: Fill or Justify
|
||||
fill: Fill
|
||||
justify: Justify
|
||||
none: None
|
||||
orientation:
|
||||
label: Orientation
|
||||
vertical: Vertical
|
||||
horizontal: Horizontal
|
||||
position:
|
||||
label: Position
|
||||
start: Start
|
||||
end: End
|
||||
table:
|
||||
columns:
|
||||
title:
|
||||
label: Title
|
||||
block:
|
||||
label: Block
|
||||
tooltip:
|
||||
edit: Edit block
|
||||
editDisabled: Editing tabbed blocks is disabled until you save this block
|
||||
addTab: Add tab
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user