Add live filtering to chart page blocks
This commit is contained in:
parent
06d362d244
commit
62e62a9a8a
@ -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>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -80,6 +80,7 @@
|
||||
</div>
|
||||
|
||||
<b-card-body
|
||||
:body-class="bodyClass"
|
||||
class="p-0 flex-fill"
|
||||
:class="{ 'overflow-auto': scrollableBody }"
|
||||
>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -31,6 +31,12 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
|
||||
bodyClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
|
||||
headerClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user