Apply all filters from current record list when exporting, and adjust Export UI/UX

This commit is contained in:
Jože Fortun
2023-07-11 11:18:44 +02:00
parent eca77959bc
commit ad9b252706
7 changed files with 623 additions and 694 deletions
@@ -30,7 +30,7 @@
>
<div class="text-nowrap flex-grow-1">
<div
class="wrap-with-vertical-gutters"
class="d-flex align-items-center flex-wrap wrap-with-vertical-gutters"
>
<template v-if="recordListModule.canCreateRecord">
<template v-if="inlineEditing">
@@ -50,7 +50,7 @@
<router-link
v-if="!options.hideAddButton"
data-test-id="button-add-record"
class="btn btn-lg btn-primary float-left mr-1"
class="btn btn-lg btn-primary mr-1"
:to="newRecordRoute"
>
+ {{ $t('recordList.addRecord') }}
@@ -59,7 +59,7 @@
v-if="!options.hideImportButton"
:module="recordListModule"
:namespace="namespace"
class="mr-1 float-left"
class="mr-1"
@importSuccessful="onImportSuccessful"
/>
</template>
@@ -68,14 +68,12 @@
<exporter-modal
v-if="options.allowExport && !inlineEditing"
:module="recordListModule"
:record-count="pagination.count"
:query="query"
:prefilter="prefilter"
:filter="filter.query"
:selection="selected"
:selected-all-records-count="selectedAllRecords ? pagination.count : 0"
:selected-all-records="selectedAllRecords"
:processing="processing"
:preselected-fields="fields.map(({ moduleField }) => moduleField)"
class="mr-1 float-left"
class="mr-1"
@export="onExport"
/>
@@ -84,7 +82,7 @@
:id="`${uniqueID}-filter-preset`"
variant="light"
size="lg"
class="dropdown-toggle"
class="mr-1"
>
{{ $t('recordList.filter.filters.label') }}
</b-button>
@@ -115,14 +113,14 @@
v-if="!options.hideConfigureFieldsButton"
:module="recordListModule"
:fields="fields"
class="mr-1 float-left"
class="mr-1"
@updateFields="onUpdateFields"
/>
</div>
</div>
<div
v-if="!options.hideSearch"
class="flex-grow-1 w-25"
class="d-flex align-items-center flex-grow-1 w-25"
>
<c-input-search
v-model.trim="query"
@@ -1395,7 +1393,7 @@ export default {
query: {
fields: e.fields,
// url.Make already URL encodes the the values, so the filter shouldn't be encoded
filter: this.selectedAllRecords ? '' : filter,
filter: this.selectedAllRecords ? this.bulkQuery : filter,
jwt: this.$auth.accessToken,
timezone: timezone ? timezone.tzCode : undefined,
},
@@ -1,664 +0,0 @@
<template>
<b-card
header-bg-variant="white"
footer-bg-variant="white"
>
<b-form-group
:label="$t('recordList.export.selectFields')"
:description="$t('recordList.export.limitations')"
label-class="text-primary"
>
<field-picker
v-if="module"
:module="module"
:system-fields="systemFields"
:disabled-types="disabledTypes"
:fields.sync="selectedFields"
style="height: 45vh;"
/>
</b-form-group>
<b-form-group>
<b-form-checkbox
v-model="forTimezone"
class="mb-1"
>
{{ $t('recordList.export.specifyTimezone') }}
</b-form-checkbox>
<vue-select
v-if="forTimezone"
v-model="exportTimezone"
:options="timezones"
:get-option-key="getOptionKey"
:calculate-position="calculateDropdownPosition"
:placeholder="$t('recordList.export.timezonePlaceholder')"
class="bg-white"
/>
</b-form-group>
<b-form-group>
<b-form-radio-group
v-model="rangeType"
:options="rangeTypeOptions"
stacked
/>
</b-form-group>
<template v-if="rangeType === 'range'">
<b-row
v-if="rangeType === 'range'"
>
<b-col
md="6"
>
<b-form-group
:label="$t('recordList.export.rangeBy')"
label-class="text-primary"
>
<b-form-select
v-model="rangeBy"
:options="rangeByOptions"
/>
</b-form-group>
</b-col>
<b-col
md="6"
>
<b-form-group
:label="$t('recordList.export.dateRange')"
label-class="text-primary"
>
<b-form-select
v-model="range"
:options="dateRangeOptions"
/>
</b-form-group>
</b-col>
</b-row>
</template>
<b-row
v-if="rangeType === 'range'"
class="mb-3"
>
<b-col
cols="6"
>
<b-form-input
v-model="start"
:state="dateRangeValid ? null : false"
type="date"
:max="end"
@keydown.prevent
/>
</b-col>
<b-col
cols="6"
>
<b-form-input
v-model="end"
:state="dateRangeValid ? null : false"
type="date"
:min="start"
@keydown.prevent
/>
</b-col>
</b-row>
<template
v-if="rangeType !== 'selection'"
>
<b-form-group
:label="$t('recordList.export.query.label')"
label-class="text-primary"
>
<b-form-input
v-model="exportQuery"
:placeholder="$t('recordList.export.query.placeholder')"
debounce="500"
/>
</b-form-group>
<b-form-group
:label="$t('recordList.export.filter.label')"
label-class="text-primary"
class="mb-0"
>
<b-form-textarea
v-model="exportFilter"
:placeholder="$t('recordList.export.filter.placeholder')"
debounce="500"
/>
</b-form-group>
</template>
<div
slot="footer"
class="d-flex"
>
<span
class="my-auto"
>
<b-spinner
v-if="processingCount"
small
/>
<span
v-else
>
{{ $t('recordList.export.recordCount', { count: getExportableCount || 0 }) }}
</span>
</span>
<span class="ml-auto">
<c-input-processing
v-if="allowJSON"
:processing="processing"
:disabled="exportDisabled"
variant="light"
size="lg"
@click="doExport('json')"
>
{{ $t('recordList.export.json') }}
</c-input-processing>
<c-input-processing
v-if="allowCSV"
:processing="processing"
:disabled="exportDisabled"
variant="light"
size="lg"
class="ml-2"
@click="doExport('csv')"
>
{{ $t('recordList.export.csv') }}
</c-input-processing>
</span>
</div>
</b-card>
</template>
<script>
import { compose, NoID } from '@cortezaproject/corteza-js'
import FieldPicker from 'corteza-webapp-compose/src/components/Common/FieldPicker'
import moment from 'moment'
import tz from 'compact-timezone-list'
import { evaluatePrefilter, queryToFilter } from 'corteza-webapp-compose/src/lib/record-filter'
import { VueSelect } from 'vue-select'
const fmtDate = (d) => d.format('YYYY-MM-DD')
export default {
i18nOptions: {
namespaces: 'block',
},
components: {
FieldPicker,
VueSelect,
},
props: {
allowJSON: {
type: Boolean,
default: true,
},
allowCSV: {
type: Boolean,
default: true,
},
module: {
type: compose.Module,
required: true,
},
preselectedFields: {
type: Array,
default: () => [],
},
recordCount: {
type: Number,
default: 0,
},
query: {
type: String,
required: false,
default: undefined,
},
prefilter: {
type: String,
required: false,
default: undefined,
},
selection: {
type: Array,
required: false,
default: () => [],
},
selectedAllRecordsCount: {
type: Number,
required: false,
default: 0,
},
filterRangeType: {
type: String,
default: 'all',
},
filterRangeBy: {
type: String,
default: 'createdAt',
},
dateRange: {
type: String,
default: 'lastMonth',
},
startDate: {
type: String,
default: null,
},
endDate: {
type: String,
default: null,
},
systemFields: {
type: Array,
default: () => ['ownedBy', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy'],
},
disabledTypes: {
type: Array,
default: () => ['User', 'Record', 'File'],
},
processing: {
type: Boolean,
default: false,
},
},
data () {
return {
fields: [],
forTimezone: false,
exportTimezone: undefined,
filter: {
rangeType: null,
query: this.query,
filter: this.prefilter,
rangeBy: null,
date: {
range: null,
start: null,
end: null,
},
},
rangeRecordCount: 0,
processingCount: false,
}
},
computed: {
timezones () {
return tz.map(({ label, tzCode, offset }) => ({ label, tzCode, offset }))
},
// These should be computed, because of i18n
rangeTypeOptions () {
const options = [
{
value: 'all',
text: this.$t('recordList.export.all'),
},
{
value: 'range',
text: this.$t('recordList.export.inRange'),
},
]
if (this.hasSelection) {
options.push({
value: 'selection',
text: this.$t('recordList.export.selection'),
})
}
return options
},
/**
* checks if the given date-range is valid
* @returns {Boolean}
*/
dateRangeValid () {
if (this.end < this.start) {
return false
}
return true
},
rangeByOptions () {
return [
{
value: 'createdAt',
text: this.$t('recordList.export.filter.createdAt'),
},
{
value: 'updatedAt',
text: this.$t('recordList.export.filter.updatedAt'),
},
]
},
dateRangeOptions () {
return [
{
value: 'lastMonth',
text: this.$t('recordList.export.filter.lastMonth'),
},
{
value: 'thisMonth',
text: this.$t('recordList.export.filter.thisMonth'),
},
{
value: 'lastWeek',
text: this.$t('recordList.export.filter.lastWeek'),
},
{
value: 'thisWeek',
text: this.$t('recordList.export.filter.thisWeek'),
},
{
value: 'today',
text: this.$t('recordList.export.filter.today'),
},
{
value: 'custom',
text: this.$t('recordList.export.filter.custom'),
},
]
},
hasSelection () {
return !!this.selection.length
},
getExportableCount () {
// when exporting selection, only selected records are applicable
if (this.rangeType === 'selection') {
if (this.selectedAllRecordsCount !== 0) {
return this.selectedAllRecordsCount
}
return this.selection.length
}
return this.rangeRecordCount
},
exportDisabled () {
return !this.dateRangeValid || this.fields.length === 0 || !this.getExportableCount
},
selectedFields: {
get () {
return this.fields
},
set (selectedFields) {
this.fields = selectedFields
},
},
rangeBy: {
get () {
return this.filter.rangeBy
},
set (rangeBy) {
this.filter.rangeBy = rangeBy
},
},
exportQuery: {
get () {
return this.filter.query
},
set (v) {
this.filter.query = v
},
},
exportFilter: {
get () {
return this.filter.filter
},
set (v) {
this.filter.filter = v
},
},
rangeType: {
get () {
return this.filter.rangeType
},
set (rangeType) {
this.filter.rangeType = rangeType
},
},
range: {
get () {
return this.filter.date.range
},
set (range) {
this.filter.date.range = range
if (range !== 'custom') {
this.filter.date.start = this.calcStart(moment(), range)
this.filter.date.end = this.calcEnd(moment(), range)
}
},
},
start: {
get () {
return this.filter.date.start
},
set (start) {
this.filter.date.start = start
this.filter.date.range = 'custom'
},
},
end: {
get () {
return this.filter.date.end
},
set (end) {
this.filter.date.end = end
this.filter.date.range = 'custom'
},
},
currentRange () {
if (this.rangeType === 'range') {
return { start: this.start, end: this.end, rangeBy: this.rangeBy }
}
return undefined
},
},
// Watchers needed for storybook
watch: {
filter: {
handler () {
this.$emit('change', this.filter)
this.getTotalCount()
},
deep: true,
},
preselectedFields: {
handler (value) {
if (!this.fields.length) {
this.fields = value.filter(f => this.disabledTypes.indexOf(f.kind) < 0)
}
},
immediate: true,
},
filterRangeType: {
immediate: true,
handler (value) {
this.filter.rangeType = value
},
},
filterRangeBy: {
handler (value) {
this.filter.rangeBy = value
},
immediate: true,
},
startDate: {
handler (value) {
this.start = value
},
},
endDate: {
handler (value) {
this.end = value
},
},
dateRange: {
handler (value) {
this.range = value
},
},
hasSelection: {
handler (h) {
if (h) {
this.rangeType = 'selection'
}
},
immediate: true,
},
},
mounted () {
if (this.startDate || this.endDate) {
this.start = this.startDate
this.end = this.endDate
} else {
this.range = this.dateRange
}
},
methods: {
calcStart (m, range) {
if (range === 'lastMonth') {
return fmtDate(m.subtract('1', 'months').startOf('month'))
} else if (range === 'thisMonth') {
return fmtDate(m.startOf('month'))
} else if (range === 'lastWeek') {
return fmtDate(m.subtract('1', 'week').startOf('week'))
} else if (range === 'thisWeek') {
return fmtDate(m.startOf('week'))
} else if (range === 'today') {
return fmtDate(m.startOf('day'))
} else {
throw new Error(this.$t('recordList.export.datePresetUndefined'))
}
},
calcEnd (m, range) {
if (range === 'lastMonth') {
return fmtDate(m.subtract('1', 'months').endOf('month'))
} else if (range === 'thisMonth') {
return fmtDate(m.endOf('month'))
} else if (range === 'lastWeek') {
return fmtDate(m.subtract('1', 'week').endOf('week'))
} else if (range === 'thisWeek') {
return fmtDate(m.endOf('week'))
} else if (range === 'today') {
return fmtDate(m.endOf('day'))
} else {
throw new Error(this.$t('recordList.export.datePresetUndefined'))
}
},
makeFilter ({ query, filter, rangeType, rangeBy, date }) {
if (filter) {
filter = evaluatePrefilter(filter, {
userID: (this.$auth.user || {}).userID || NoID,
})
}
query = queryToFilter(query, filter, this.module.fields)
if (rangeType === 'all') {
return query
}
if (rangeType === 'selection') {
// @todo improve with IN operator when supported
return this.selection.map(r => `recordID='${r}'`).join(' OR ')
}
let dateQuery, start, end
if (date.start) {
start = `DATE(${rangeBy})>='${date.start}'`
}
if (date.end) {
end = `DATE(${rangeBy})<='${date.end}'`
}
if (start && end) {
dateQuery = `(${start}) AND (${end})`
} else {
dateQuery = start || end
}
return query ? `(${query}) AND ${dateQuery}` : dateQuery
},
doExport (kind) {
this.$emit('export', {
ext: kind,
fields: encodeURIComponent(this.fields.map(({ name }) => name)),
filter: encodeURIComponent(this.makeFilter(this.filter)),
filterRaw: encodeURIComponent(this.filter),
timezone: encodeURIComponent(this.forTimezone ? this.exportTimezone : undefined),
})
},
getTotalCount () {
const { moduleID, namespaceID } = this.module || {}
if (moduleID && namespaceID) {
this.processingCount = true
const query = this.makeFilter(this.filter)
this.$ComposeAPI.recordList({ namespaceID, moduleID, query, limit: 1, incTotal: true })
.then(({ filter = {} }) => {
this.rangeRecordCount = filter.total || 0
})
.catch(() => {
this.rangeRecordCount = 0
})
.finally(() => {
this.processingCount = false
})
}
},
getOptionKey ({ tzCode }) {
return tzCode
},
},
}
</script>
@@ -1,45 +1,283 @@
<template>
<div>
<div class="d-inline-block">
<b-button
size="lg"
variant="light"
@click="toggleModal"
>
{{ $t('label.export') }}
{{ $t('general:label.export') }}
</b-button>
<b-modal
:visible="showExportModal"
size="lg"
:title="$t('label.export')"
hide-footer
body-class="p-0"
:title="$t('general:label.export')"
no-fade
scrollable
footer-class="d-flex align-items-center justify-content-between"
@hide="toggleModal"
>
<configurator
:processing="processing"
v-bind="$attrs"
v-on="$listeners"
/>
<b-form-group
:label="$t('recordList.export.selectFields')"
:description="$t('recordList.export.limitations')"
label-class="text-primary"
>
<field-picker
v-if="module"
:module="module"
:system-fields="systemFields"
:disabled-types="disabledTypes"
:fields.sync="selectedFields"
style="height: 45vh;"
/>
</b-form-group>
<b-form-group>
<b-form-radio-group
v-model="rangeType"
:options="rangeTypeOptions"
stacked
@change="getTotalCount()"
/>
</b-form-group>
<template v-if="rangeType === 'range'">
<b-row
v-if="rangeType === 'range'"
>
<b-col
md="6"
>
<b-form-group
:label="$t('recordList.export.rangeBy')"
label-class="text-primary"
>
<b-form-select
v-model="rangeBy"
:options="rangeByOptions"
@change="getTotalCount()"
/>
</b-form-group>
</b-col>
<b-col
md="6"
>
<b-form-group
:label="$t('recordList.export.dateRange')"
label-class="text-primary"
>
<b-form-select
v-model="range"
:options="dateRangeOptions"
@change="getTotalCount()"
/>
</b-form-group>
</b-col>
</b-row>
</template>
<b-row
v-if="rangeType === 'range'"
>
<b-col
md="6"
>
<b-form-group
:label="$t('recordList.export.filter.from')"
label-class="text-primary"
>
<c-input-date-time
v-model="start"
no-time
only-past
:labels="{
clear: $t('general:label.clear'),
none: $t('general:label.none'),
now: $t('general:label.now'),
today: $t('general:label.today'),
}"
/>
</b-form-group>
</b-col>
<b-col
md="6"
>
<b-form-group
:label="$t('recordList.export.filter.to')"
label-class="text-primary"
>
<c-input-date-time
v-model="end"
no-time
only-past
:labels="{
clear: $t('general:label.clear'),
none: $t('general:label.none'),
now: $t('general:label.now'),
today: $t('general:label.today'),
}"
/>
</b-form-group>
</b-col>
</b-row>
<template
v-if="rangeType !== 'selection'"
>
<b-form-group
:label="$t('recordList.export.filter.label')"
label-class="text-primary"
>
<b-form-textarea
v-model="exportFilter"
:placeholder="$t('recordList.export.filter.placeholder')"
debounce="500"
/>
</b-form-group>
</template>
<b-form-group>
<b-form-checkbox
v-model="forTimezone"
class="mb-2"
>
{{ $t('recordList.export.specifyTimezone') }}
</b-form-checkbox>
<vue-select
v-if="forTimezone"
v-model="exportTimezone"
:options="timezones"
:get-option-key="getOptionKey"
:calculate-position="calculateDropdownPosition"
:placeholder="$t('recordList.export.timezonePlaceholder')"
class="bg-white"
/>
</b-form-group>
<template #modal-footer>
<div>
<b-spinner
v-if="processingCount"
small
/>
<span
v-else
>
{{ $t('recordList.export.recordCount', { count: getExportableCount || 0 }) }}
</span>
</div>
<div>
<c-input-processing
v-if="allowJSON"
:processing="processing"
:disabled="exportDisabled"
variant="light"
size="lg"
@click="doExport('json')"
>
{{ $t('recordList.export.json') }}
</c-input-processing>
<c-input-processing
v-if="allowCSV"
:processing="processing"
:disabled="exportDisabled"
variant="light"
size="lg"
class="ml-2"
@click="doExport('csv')"
>
{{ $t('recordList.export.csv') }}
</c-input-processing>
</div>
</template>
</b-modal>
</div>
</template>
<script>
import Configurator from './Configurator'
import { compose } from '@cortezaproject/corteza-js'
import { components } from '@cortezaproject/corteza-vue'
import moment from 'moment'
import { throttle } from 'lodash'
import tz from 'compact-timezone-list'
import { VueSelect } from 'vue-select'
import FieldPicker from 'corteza-webapp-compose/src/components/Common/FieldPicker'
import { getFieldFilter } from 'corteza-webapp-compose/src/lib/record-filter'
const { CInputDateTime } = components
const fmtDate = (d) => d.format('YYYY-MM-DD')
export default {
i18nOptions: {
namespaces: 'general',
namespaces: 'block',
},
components: {
Configurator,
FieldPicker,
VueSelect,
CInputDateTime,
},
inheritAttrs: true,
props: {
allowJSON: {
type: Boolean,
default: true,
},
allowCSV: {
type: Boolean,
default: true,
},
module: {
type: compose.Module,
required: true,
},
preselectedFields: {
type: Array,
default: () => [],
},
filter: {
type: String,
required: false,
default: '',
},
selection: {
type: Array,
required: false,
default: () => [],
},
selectedAllRecords: {
type: Boolean,
default: false,
},
filterRangeType: {
type: String,
default: 'all',
},
filterRangeBy: {
type: String,
default: 'createdAt',
},
startDate: {
type: String,
default: null,
},
endDate: {
type: String,
default: null,
},
systemFields: {
type: Array,
default: () => ['ownedBy', 'createdAt', 'createdBy', 'updatedAt', 'updatedBy'],
},
disabledTypes: {
type: Array,
default: () => ['User', 'Record', 'File'],
},
processing: {
type: Boolean,
default: false,
@@ -49,13 +287,366 @@ export default {
data () {
return {
showExportModal: false,
fields: [],
forTimezone: false,
exportTimezone: undefined,
exportConfig: {
rangeType: 'all',
query: this.query,
filter: this.filter,
rangeBy: null,
date: {
range: 'lastMonth',
start: this.calcStart(moment(), 'lastMonth'),
end: this.calcEnd(moment(), 'lastMonth'),
},
},
rangeRecordCount: 0,
processingCount: false,
}
},
computed: {
timezones () {
return tz.map(({ label, tzCode, offset }) => ({ label, tzCode, offset }))
},
// These should be computed, because of i18n
rangeTypeOptions () {
const options = [
{
value: 'all',
text: this.$t('recordList.export.all'),
},
{
value: 'range',
text: this.$t('recordList.export.inRange'),
},
]
if (this.hasSelection) {
options.push({
value: 'selection',
text: this.$t('recordList.export.selection'),
})
}
return options
},
/**
* checks if the given date-range is valid
* @returns {Boolean}
*/
dateRangeValid () {
if (this.end < this.start) {
return false
}
return true
},
rangeByOptions () {
return [
{
value: 'createdAt',
text: this.$t('recordList.export.filter.createdAt'),
},
{
value: 'updatedAt',
text: this.$t('recordList.export.filter.updatedAt'),
},
]
},
dateRangeOptions () {
return [
{
value: 'lastMonth',
text: this.$t('recordList.export.filter.lastMonth'),
},
{
value: 'thisMonth',
text: this.$t('recordList.export.filter.thisMonth'),
},
{
value: 'lastWeek',
text: this.$t('recordList.export.filter.lastWeek'),
},
{
value: 'thisWeek',
text: this.$t('recordList.export.filter.thisWeek'),
},
{
value: 'today',
text: this.$t('recordList.export.filter.today'),
},
{
value: 'custom',
text: this.$t('recordList.export.filter.custom'),
},
]
},
hasSelection () {
return !!this.selection.length
},
getExportableCount () {
// when exporting selection, only selected records are applicable
if (this.rangeType === 'selection') {
return this.selectedAllRecords ? this.rangeRecordCount : this.selection.length
}
return this.rangeRecordCount
},
exportDisabled () {
return !this.dateRangeValid || this.fields.length === 0 || !this.getExportableCount
},
selectedFields: {
get () {
return this.fields
},
set (selectedFields) {
this.fields = selectedFields
},
},
rangeBy: {
get () {
return this.exportConfig.rangeBy
},
set (rangeBy) {
this.exportConfig.rangeBy = rangeBy
},
},
exportFilter: {
get () {
return this.exportConfig.filter
},
set (v) {
this.exportConfig.filter = v
this.getTotalCount()
},
},
rangeType: {
get () {
return this.exportConfig.rangeType
},
set (rangeType) {
this.exportConfig.rangeType = rangeType
},
},
range: {
get () {
return this.exportConfig.date.range
},
set (range) {
this.exportConfig.date.range = range
if (range !== 'custom') {
this.exportConfig.date.start = this.calcStart(moment(), range)
this.exportConfig.date.end = this.calcEnd(moment(), range)
}
},
},
start: {
get () {
return this.exportConfig.date.start
},
set (start) {
this.exportConfig.date.start = start
this.exportConfig.date.range = 'custom'
this.getTotalCount()
},
},
end: {
get () {
return this.exportConfig.date.end
},
set (end) {
this.exportConfig.date.end = end
this.exportConfig.date.range = 'custom'
this.getTotalCount()
},
},
currentRange () {
if (this.rangeType === 'range') {
return { start: this.start, end: this.end, rangeBy: this.rangeBy }
}
return undefined
},
},
watch: {
filter: {
immediate: true,
deep: true,
handler (filter) {
this.exportConfig.filter = filter
this.getTotalCount()
},
},
preselectedFields: {
handler (value) {
if (!this.fields.length) {
this.fields = value.filter(f => this.disabledTypes.indexOf(f.kind) < 0)
}
},
immediate: true,
},
filterRangeType: {
immediate: true,
handler (value) {
this.exportConfig.rangeType = value
},
},
filterRangeBy: {
immediate: true,
handler (value) {
this.exportConfig.rangeBy = value
},
},
startDate: {
handler (value) {
this.start = value
},
},
endDate: {
handler (value) {
this.end = value
},
},
hasSelection: {
immediate: true,
handler (h) {
if (h) {
this.rangeType = 'selection'
}
},
},
},
methods: {
toggleModal () {
this.showExportModal = !this.showExportModal
},
calcStart (m, range) {
if (range === 'lastMonth') {
return fmtDate(m.subtract('1', 'months').startOf('month'))
} else if (range === 'thisMonth') {
return fmtDate(m.startOf('month'))
} else if (range === 'lastWeek') {
return fmtDate(m.subtract('1', 'week').startOf('week'))
} else if (range === 'thisWeek') {
return fmtDate(m.startOf('week'))
} else if (range === 'today') {
return fmtDate(m.startOf('day'))
} else {
throw new Error(this.$t('recordList.export.datePresetUndefined'))
}
},
calcEnd (m, range) {
if (range === 'lastMonth') {
return fmtDate(m.subtract('1', 'months').endOf('month'))
} else if (range === 'thisMonth') {
return fmtDate(m.endOf('month'))
} else if (range === 'lastWeek') {
return fmtDate(m.subtract('1', 'week').endOf('week'))
} else if (range === 'thisWeek') {
return fmtDate(m.endOf('week'))
} else if (range === 'today') {
return fmtDate(m.endOf('day'))
} else {
throw new Error(this.$t('recordList.export.datePresetUndefined'))
}
},
makeFilter ({ filter, rangeType, rangeBy, date }) {
if (rangeType === 'all') {
return filter
}
if (rangeType === 'selection') {
return this.selectedAllRecords ? filter : this.selection.map(r => `recordID='${r}'`).join(' OR ')
}
let dateRangeQuery = ''
if (date.start && date.end) {
// If dates are the same, set range to that date
date = { ...date }
if (date.start === date.end) {
date.start = moment(date.start, 'YYYY-MM-DD HH:mm').utc().format()
date.end = moment(date.end, 'YYYY-MM-DD HH:mm').add(1, 'days').utc().format()
}
dateRangeQuery = getFieldFilter(rangeBy, 'DateTime', date, 'BETWEEN')
} else if (date.start) {
dateRangeQuery = getFieldFilter(rangeBy, 'DateTime', date.start, '>=')
} else if (date.end) {
dateRangeQuery = getFieldFilter(rangeBy, 'DateTime', date.end, '<=')
}
return filter && dateRangeQuery ? `(${filter}) AND ${dateRangeQuery}` : dateRangeQuery
},
doExport (kind) {
this.$emit('export', {
ext: kind,
fields: encodeURIComponent(this.fields.map(({ name }) => name)),
filter: encodeURIComponent(this.makeFilter(this.exportConfig)),
filterRaw: encodeURIComponent(this.exportConfig),
timezone: encodeURIComponent(this.forTimezone ? this.exportTimezone : undefined),
})
},
getTotalCount: throttle(function () {
const { moduleID, namespaceID } = this.module || {}
if (moduleID && namespaceID) {
const query = this.makeFilter(this.exportConfig)
this.processingCount = true
this.$ComposeAPI.recordList({ namespaceID, moduleID, query, limit: 1, incTotal: true })
.then(({ filter = {} }) => {
this.rangeRecordCount = filter.total || 0
})
.catch(e => {
this.rangeRecordCount = 0
this.toastErrorHandler(this.$t('notification:record.countFailed'))(e)
})
.finally(() => {
this.processingCount = false
})
}
}, 500),
getOptionKey ({ tzCode }) {
return tzCode
},
},
}
</script>
@@ -1,5 +1,5 @@
<template>
<div>
<div class="d-inline-block">
<b-button
size="lg"
variant="light"
@@ -15,6 +15,7 @@
hide-footer
body-class="p-0"
no-fade
scrollable
@hide="onModalHide"
>
<keep-alive>
+6 -4
View File
@@ -295,12 +295,12 @@ recordList:
all: Export all records
allow: Users will be able to export records
csv: CSV Export
dateRange: 'Select date range:'
dateRange: Select date range
query:
label: Search query
placeholder: Search...
filter:
label: Filter records
label: Record filter
placeholder: field1 = 1 AND field2 = 232
footnote: Simplified SQL condition (WHERE ...) syntax is supported. Variables like {{0}}, {{1}} and {{2}} are evaluated (when available)
createdAt: Record created
@@ -310,12 +310,14 @@ recordList:
thisMonth: This month
thisWeek: This week
today: Today
from: From
to: To
updatedAt: Record updated
inRange: Set date range
inRange: Date range
includeQuery: Filter by search query
json: JSON Export
limitations: 'CSV export limitation: only the first value in the multi value fields will be exported'
rangeBy: 'Set range by:'
rangeBy: Set range by
recordCount: '{{count}} records ready for export'
selectFields: 'Select fields you want to export:'
selection: Selected records
@@ -125,6 +125,7 @@ record:
invalidRecordVar: Can not use ${record...} variable in non-record pages
listLoadFailed: Could not load record list
loadFailed: Could not load record
countFailed: Failed to fetch number of avaiable records
moduleMismatch: Module incompatible, module mismatch
moduleNotDefined: Record module not defined
moduleOrPageNotSet: 'RecordList block error: module or page option not set'