3
0

Adjust preset record list filter styles and logic

This commit is contained in:
Jože Fortun
2023-03-15 13:36:49 +01:00
parent 63f1647f7b
commit 1815e1daa8
8 changed files with 211 additions and 220 deletions

View File

@@ -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) {

View File

@@ -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))
},
},
}

View File

@@ -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: [],
})
},
},

View File

@@ -65,7 +65,6 @@
<div
v-if="toolbarSet"
class="overflow-hidden"
>
<slot
name="toolbar"

View File

@@ -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 {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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