3
0

Add live filtering to chart page blocks

This commit is contained in:
Kelani Tolulope 2024-07-09 14:10:00 +01:00 committed by Jože Fortun
parent 06d362d244
commit 62e62a9a8a
7 changed files with 294 additions and 53 deletions

View File

@ -1,6 +1,7 @@
<template>
<wrap
v-bind="$props"
body-class="position-relative"
v-on="$listeners"
@refreshBlock="refresh"
>
@ -12,8 +13,117 @@
:reporter="reporter"
@drill-down="drillDown"
/>
<template v-if="options.liveFilterEnabled">
<b-button
variant="outline-light"
class="chart__livefilter-btn position-absolute d-flex d-print-none border-0 px-1"
:class="[!!liveFilterValue && !liveFilterModal.show ? 'text-primary' : 'text-secondary']"
@click="showFilterModal"
>
<font-awesome-icon
:icon="['fas', 'filter']"
/>
</b-button>
<b-modal
v-model="liveFilterModal.show"
:title="$t('chart.filter.modal.title')"
:ok-title="$t('general:label.saveAndClose')"
centered
size="lg"
cancel-variant="light"
no-fade
@ok="updateLiveFilter"
@hide="liveFilterModal.show = undefined"
>
<b-form-group
v-if="originalFilter"
:label="$t('chart.filter.modal.originalFilter.label')"
label-class="text-primary"
>
<b-form-textarea
:value="originalFilter"
readonly
/>
</b-form-group>
<b-form-group
:label="$t('chart.filter.modal.liveFilter.label')"
label-class="text-primary"
>
<c-input-select
v-model="liveFilterModal.value"
:options="predefinedFilters"
label="text"
:reduce="filter => filter.value"
:placeholder="$t('chart.filter.modal.liveFilter.placeholder')"
/>
</b-form-group>
<div v-if="originalFilter && liveFilterModal.value">
<hr class="my-3">
<b-form-group
:label="$t('chart.filter.modal.filterPreview.label')"
label-class="text-primary"
>
<b-form-textarea
:value="liveFilterPreview"
readonly
/>
</b-form-group>
<b-form-group
:label="$t('chart.filter.modal.options.label')"
label-class="text-primary pb-0"
>
<b-form-radio
v-for="(option, ci) in filterOptions"
:key="ci"
v-model="liveFilterModal.option"
:value="option.value"
>
{{ $t(`chart.filter.modal.options.${option.label}`) }}
</b-form-radio>
</b-form-group>
</div>
<template #modal-footer="{ ok }">
<div
class="d-flex justify-content-between align-items-center w-100"
>
<b-button
type="button"
variant="light"
@click="resetLiveFilter()"
>
{{ $t('general:label.reset') }}
</b-button>
<div class="d-flex gap-1">
<b-button
variant="light"
type="button"
rounded
@click="cancelLiveFilter()"
>
{{ $t('general:label.cancel') }}
</b-button>
<b-button
variant="primary"
@click="ok()"
>
{{ $t('general:label.save') }}
</b-button>
</div>
</div>
</template>
</b-modal>
</template>
</wrap>
</template>
<script>
import { mapActions } from 'vuex'
import base from './base'
@ -22,10 +132,6 @@ import { NoID, compose } from '@cortezaproject/corteza-js'
import { evaluatePrefilter, isFieldInFilter } from 'corteza-webapp-compose/src/lib/record-filter'
export default {
i18nOptions: {
namespaces: 'notification',
},
components: {
ChartComponent,
},
@ -36,9 +142,41 @@ export default {
return {
chart: null,
originalFilter: undefined,
filter: undefined,
drillDownFilter: undefined,
liveFilterModal: {
show: false,
value: undefined,
option: 'AND',
},
predefinedFilters: [
...compose.chartUtil.predefinedFilters.map(pf => ({ ...pf, text: this.$t(`chart:edit.filter.${pf.text}`) })),
],
selectedFilter: undefined,
customDate: {
start: undefined,
end: undefined,
},
liveFilterValue: undefined,
liveFilterOption: 'AND',
checkboxLabel: {
on: this.$t('general:label.yes'),
off: this.$t('general:label.no'),
},
filterOptions: [
{ label: 'and', value: 'AND' },
{ label: 'or', value: 'OR' },
{ label: 'overwrite', value: '' },
],
}
},
@ -48,6 +186,10 @@ export default {
return this.options.drillDown && this.options.drillDown.enabled
},
liveFilterPreview () {
return this.getFilter(this.liveFilterModal.value, this.liveFilterModal.option)
},
},
watch: {
@ -121,13 +263,16 @@ export default {
this.filter.field = { name, label }
})
}
}).catch(this.toastErrorHandler(this.$t('chart.loadFailed')))
}).catch(this.toastErrorHandler(this.$t('notification:chart.loadFailed')))
},
reporter (r) {
this.filter = r
reporter (r = {}) {
if (!this.originalFilter) {
this.originalFilter = r.filter
this.filter = r
}
let filter = r.filter
let filter = this.getFilter()
if (filter) {
// If we use ${record} or ${ownerID} and there is no record, resolve empty
@ -152,6 +297,31 @@ export default {
return this.$ComposeAPI.recordReport({ namespaceID, ...r, filter })
},
getFilter (liveFilter = this.liveFilterValue, option = this.liveFilterOption) {
if (liveFilter) {
return this.originalFilter && option ? `(${this.originalFilter}) ${option} (${liveFilter})` : liveFilter
}
return this.originalFilter
},
updateLiveFilter () {
this.liveFilterValue = this.liveFilterModal.value
this.liveFilterOption = this.liveFilterModal.option
this.liveFilterModal.show = false
this.refresh()
},
resetLiveFilter () {
this.liveFilterModal.value = undefined
this.liveFilterModal.option = 'AND'
},
cancelLiveFilter () {
this.liveFilterModal.show = undefined
},
refresh () {
this.fetchChart({ force: true }).then(() => {
this.chart.config.noAnimation = true
@ -232,6 +402,13 @@ export default {
}
},
showFilterModal () {
this.liveFilterModal.value = this.liveFilterValue
this.liveFilterModal.option = this.liveFilterOption
this.liveFilterModal.show = true
},
setDefaultValues () {
this.chart = null
this.filter = undefined
@ -246,3 +423,11 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.chart__livefilter-btn {
right: 0.5rem;
top: 1.5rem;
z-index: 1;
}
</style>

View File

@ -30,51 +30,73 @@
</b-input-group>
</b-form-group>
<template v-if="isDrillDownAvailable">
<b-form-group
:label="$t('chart.drillDown.label')"
:description="$t('chart.drillDown.description')"
label-class="d-flex align-items-center text-primary"
class="mb-1"
<b-row v-if="selectedChart">
<b-col
cols="12"
lg="6"
>
<template #label>
{{ $t('chart.drillDown.label') }}
<template v-if="isDrillDownAvailable">
<b-form-group
:label="$t('chart.drillDown.label')"
:description="$t('chart.drillDown.description')"
label-class="d-flex align-items-center text-primary"
>
<template #label>
{{ $t('chart.drillDown.label') }}
<c-input-checkbox
v-model="options.drillDown.enabled"
switch
class="ml-1"
/>
<c-input-checkbox
v-model="options.drillDown.enabled"
switch
class="ml-1"
/>
</template>
<b-input-group>
<c-input-select
v-model="options.drillDown.blockID"
:options="drillDownOptions"
:get-option-key="getOptionKey"
:disabled="!options.drillDown.enabled"
:get-option-label="o => o.title || o.kind"
:reduce="option => option.blockID"
:clearable="true"
:placeholder="$t('chart.drillDown.openInModal')"
/>
<b-input-group-append>
<column-picker
ref="columnPicker"
:module="selectedChartModule"
:fields="selectedDrilldownFields"
:disabled="!!options.drillDown.blockID || !options.drillDown.enabled"
variant="extra-light"
size="md"
@updateFields="onUpdateFields"
>
<font-awesome-icon :icon="['fas', 'wrench']" />
</column-picker>
</b-input-group-append>
</b-input-group>
</b-form-group>
</template>
</b-col>
<b-input-group>
<c-input-select
v-model="options.drillDown.blockID"
:options="drillDownOptions"
:get-option-key="getOptionKey"
:disabled="!options.drillDown.enabled"
:get-option-label="o => o.title || o.kind"
:reduce="option => option.blockID"
:clearable="true"
:placeholder="$t('chart.drillDown.openInModal')"
<b-col
cols="12"
lg="6"
>
<b-form-group
label-class="d-flex align-items-center text-primary"
:label="$t('chart.enableLiveFilter')"
>
<c-input-checkbox
v-model="options.liveFilterEnabled"
switch
:labels="checkboxLabel"
/>
<b-input-group-append>
<column-picker
ref="columnPicker"
:module="selectedChartModule"
:fields="selectedDrilldownFields"
:disabled="!!options.drillDown.blockID || !options.drillDown.enabled"
variant="extra-light"
size="md"
@updateFields="onUpdateFields"
>
<font-awesome-icon :icon="['fas', 'wrench']" />
</column-picker>
</b-input-group-append>
</b-input-group>
</b-form-group>
</template>
</b-form-group>
</b-col>
</b-row>
</b-tab>
</template>
<script>
@ -96,6 +118,15 @@ export default {
extends: base,
data () {
return {
checkboxLabel: {
on: this.$t('general:label.yes'),
off: this.$t('general:label.no'),
},
}
},
computed: {
...mapGetters({
charts: 'chart/set',

View File

@ -80,6 +80,7 @@
</div>
<b-card-body
:body-class="bodyClass"
class="p-0 flex-fill"
:class="{ 'overflow-auto': scrollableBody }"
>

View File

@ -78,7 +78,7 @@
<div
class="card-body p-0 flex-fill"
:class="{ 'overflow-auto': scrollableBody }"
:class="{ 'overflow-auto': scrollableBody, bodyClass }"
style="flex-shrink: 10;"
>
<slot

View File

@ -31,6 +31,12 @@ export default {
default: '',
},
bodyClass: {
type: String,
required: false,
default: '',
},
headerClass: {
type: String,
required: false,

View File

@ -1,5 +1,5 @@
import { PageBlock, PageBlockInput, Registry } from './base'
import { Apply,NoID } from '../../../cast'
import { Apply, NoID } from '../../../cast'
import { Options as PageBlockRecordListOptions } from './record-list'
import { cloneDeep, merge } from 'lodash'
@ -17,6 +17,7 @@ interface Options {
showRefresh: boolean;
magnifyOption: string;
drillDown: DrillDown;
liveFilterEnabled: boolean;
}
const defaults: Readonly<Options> = Object.freeze({
@ -24,13 +25,14 @@ const defaults: Readonly<Options> = Object.freeze({
refreshRate: 0,
showRefresh: false,
magnifyOption: '',
liveFilterEnabled: false,
drillDown: {
enabled: false,
blockID: '',
recordListOptions: {
fields: [],
},
}
},
})
export class PageBlockChart extends PageBlock {
@ -50,14 +52,14 @@ export class PageBlockChart extends PageBlock {
Apply(this.options, o, String, 'chartID', 'magnifyOption')
Apply(this.options, o, Number, 'refreshRate')
Apply(this.options, o, Boolean, 'showRefresh')
Apply(this.options, o, Boolean, 'showRefresh', 'liveFilterEnabled')
if (o.drillDown) {
this.options.drillDown = merge({}, defaults.drillDown, o.drillDown)
}
}
resetDrillDown(): void {
resetDrillDown (): void {
this.options.drillDown = cloneDeep(defaults.drillDown)
}
}

View File

@ -87,6 +87,7 @@ chart:
addRadar: Radar chart
addGeneric: Generic chart
createFailed: Could not create a chart
enableLiveFilter: Enable live filter
columns:
name: Name
handle: Handle
@ -121,6 +122,21 @@ chart:
searchPlaceholder: Type here to search all charts in this namespace
openInBuilder: Open in chart builder
openChartList: Open chart list
filter:
modal:
title: Chart Filter
originalFilter:
label: Original filter
liveFilter:
label: Live filter
placeholder: Select a filter
filterPreview:
label: Filter Preview
options:
label: Filter options
and: Join filters with AND
or: Join filters with OR
overwrite: Overwrite original filter
content:
ok: Ok
label: Content