Improve progress bar page block and number display
This commit is contained in:
@@ -1,15 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-group
|
||||
:label="$t('kind.number.displayType.label')"
|
||||
>
|
||||
<b-form-radio-group
|
||||
v-model="f.options.display"
|
||||
button-variant="outline-primary"
|
||||
:options="displayOptions"
|
||||
buttons
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-row>
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('kind.number.displayType.label')"
|
||||
>
|
||||
<b-form-radio-group
|
||||
v-model="f.options.display"
|
||||
button-variant="outline-primary"
|
||||
:options="displayOptions"
|
||||
buttons
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<label class="d-block mb-3">
|
||||
{{ $t('kind.number.precisionLabel') }} ({{ f.options.precision }})
|
||||
</label>
|
||||
<b-form-input
|
||||
v-model="f.options.precision"
|
||||
:placeholder="$t('kind.number.precisionPlaceholder')"
|
||||
type="range"
|
||||
min="0"
|
||||
max="6"
|
||||
size="lg"
|
||||
class="mt-1 mb-2"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<hr>
|
||||
|
||||
<b-row>
|
||||
<template v-if="f.options.display === 'number'">
|
||||
@@ -42,24 +69,6 @@
|
||||
</b-col>
|
||||
</template>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<label class="d-block mb-3">
|
||||
{{ $t('kind.number.precisionLabel') }} ({{ f.options.precision }})
|
||||
</label>
|
||||
<b-form-input
|
||||
v-model="f.options.precision"
|
||||
:placeholder="$t('kind.number.precisionPlaceholder')"
|
||||
type="range"
|
||||
min="0"
|
||||
max="6"
|
||||
size="lg"
|
||||
class="mt-1 mb-2"
|
||||
/>
|
||||
</b-col>
|
||||
|
||||
<template v-if="f.options.display === 'number'">
|
||||
<b-col
|
||||
cols="12"
|
||||
@@ -74,6 +83,49 @@
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
>
|
||||
<b-form-group
|
||||
v-if="f.options.display === 'number'"
|
||||
:label="$t('kind.number.examplesLabel')"
|
||||
>
|
||||
<table
|
||||
style="width: 100%;"
|
||||
>
|
||||
<tr>
|
||||
<th>{{ $t('kind.number.exampleInput') }}</th>
|
||||
<th>{{ $t('kind.number.exampleFormat') }}</th>
|
||||
<th>{{ $t('kind.number.exampleResult') }}</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>10000.234</td>
|
||||
<td>$0.00</td>
|
||||
<td>$10000.23</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>0.974878234</td>
|
||||
<td>0.000%</td>
|
||||
<td>97.488%</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>0%</td>
|
||||
<td>100%</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>238</td>
|
||||
<td>00:00:00</td>
|
||||
<td>0:03:58</td>
|
||||
</tr>
|
||||
</table>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</template>
|
||||
|
||||
<template v-if="f.options.display === 'progress'">
|
||||
@@ -82,11 +134,27 @@
|
||||
sm="6"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('kind.number.maximumValue')"
|
||||
:label="$t('kind.number.progress.minimumValue')"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="f.options.min"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('kind.number.progress.maximumValue')"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="f.options.max"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
@@ -118,6 +186,7 @@
|
||||
>
|
||||
{{ $t('kind.number.progress.show.value') }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-checkbox
|
||||
v-model="f.options.animated"
|
||||
>
|
||||
@@ -140,6 +209,7 @@
|
||||
>
|
||||
{{ $t('kind.number.progress.show.relative') }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-checkbox
|
||||
v-model="f.options.showProgress"
|
||||
>
|
||||
@@ -212,6 +282,8 @@
|
||||
</template>
|
||||
</b-row>
|
||||
|
||||
<hr>
|
||||
|
||||
<b-form-group
|
||||
:label=" $t('kind.number.liveExample')"
|
||||
class="mb-0 w-100"
|
||||
@@ -226,7 +298,7 @@
|
||||
<b-form-input
|
||||
v-model="liveExample"
|
||||
type="number"
|
||||
step="0.1"
|
||||
number
|
||||
/>
|
||||
</b-col>
|
||||
|
||||
@@ -241,42 +313,6 @@
|
||||
</b-col>
|
||||
</b-row>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
v-if="f.options.display === 'number'"
|
||||
:label="$t('kind.number.examplesLabel')"
|
||||
class="mt-3"
|
||||
>
|
||||
<table
|
||||
style="width: 100%;"
|
||||
>
|
||||
<tr>
|
||||
<th>{{ $t('kind.number.exampleInput') }}</th>
|
||||
<th>{{ $t('kind.number.exampleFormat') }}</th>
|
||||
<th>{{ $t('kind.number.exampleResult') }}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10000.234</td>
|
||||
<td>$0.00</td>
|
||||
<td>$10000.23</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>0.974878234</td>
|
||||
<td>0.000%</td>
|
||||
<td>97.488%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>0%</td>
|
||||
<td>100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>238</td>
|
||||
<td>00:00:00</td>
|
||||
<td>0:03:58</td>
|
||||
</tr>
|
||||
</table>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -360,7 +396,7 @@ export default {
|
||||
this.mock.field.apply({ name: 'mockField' })
|
||||
this.mock.module = new compose.Module({ fields: [this.mock.field] }, this.namespace)
|
||||
this.mock.record = new compose.Record(this.mock.module, { })
|
||||
this.liveExample = this.field.options.display === 'number' ? 123.45679 : 33
|
||||
this.liveExample = this.field.options.display === 'number' ? 123.45679 : 33.45679
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
v-model="value[ctx.index]"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
number
|
||||
class="mr-2"
|
||||
/>
|
||||
</b-input-group>
|
||||
@@ -56,6 +57,7 @@
|
||||
v-model="value"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-input-group>
|
||||
<errors :errors="errors" />
|
||||
|
||||
@@ -5,23 +5,22 @@
|
||||
<div v-if="field.options.display === 'number'" :class="classes">{{ formatted }}</div>
|
||||
|
||||
<div v-else>
|
||||
<b-progress
|
||||
<c-progress
|
||||
v-for="(v, i) in formatted"
|
||||
:key="i"
|
||||
:max="field.options.max"
|
||||
height="1.5rem"
|
||||
class="bg-light"
|
||||
:value="parseFloat(v)"
|
||||
:min="parseFloat(field.options.min)"
|
||||
:max="parseFloat(field.options.max)"
|
||||
:labeled="field.options.showValue"
|
||||
:relative="field.options.showRelative"
|
||||
:progress="field.options.showProgress"
|
||||
:striped="field.options.striped"
|
||||
:animated="field.options.animated"
|
||||
:variant="field.options.variant"
|
||||
:thresholds="field.options.thresholds"
|
||||
:class="{ 'mt-2': i }"
|
||||
>
|
||||
<b-progress-bar
|
||||
:value="v"
|
||||
:striped="field.options.striped"
|
||||
:animated="field.options.animated"
|
||||
:variant="getProgressVariant(v)"
|
||||
>
|
||||
{{ getProgressLabel(v) }}
|
||||
</b-progress-bar>
|
||||
</b-progress>
|
||||
style="height: 1.5rem;"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<errors :errors="errors" />
|
||||
@@ -30,14 +29,20 @@
|
||||
|
||||
<script>
|
||||
import base from './base'
|
||||
import { components } from '@cortezaproject/corteza-vue'
|
||||
const { CProgress } = components
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CProgress,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
computed: {
|
||||
formatted () {
|
||||
if (!this.value) {
|
||||
return
|
||||
if (this.value === undefined) {
|
||||
return this.field.options.display === 'number' ? undefined : [this.field.options.min]
|
||||
}
|
||||
|
||||
const value = this.field.isMulti ? this.value : [this.value]
|
||||
@@ -45,45 +50,9 @@ export default {
|
||||
if (this.field.options.display === 'number') {
|
||||
return value.map(v => this.field.formatValue(v)).join(this.field.options.multiDelimiter)
|
||||
} else {
|
||||
return value
|
||||
return value.length ? value : [this.field.options.min]
|
||||
}
|
||||
},
|
||||
|
||||
sortedVariants () {
|
||||
return [...this.field.options.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
getProgressLabel (value) {
|
||||
const { max, showValue, showRelative, showProgress } = this.field.options
|
||||
|
||||
if (!showValue) {
|
||||
return
|
||||
}
|
||||
|
||||
if (showRelative) {
|
||||
// https://stackoverflow.com/a/21907972/17926309
|
||||
value = `${Math.round(((value / max) * 100) * 100) / 100}%`
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
value = `${value} / ${showRelative ? '100' : max}${showRelative ? '%' : ''}`
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
getProgressVariant (value) {
|
||||
let progressVariant = this.field.options.variant
|
||||
|
||||
if (this.field.options.thresholds.length) {
|
||||
const { variant } = this.sortedVariants.find(t => value >= t.value) || {}
|
||||
progressVariant = variant || progressVariant
|
||||
}
|
||||
|
||||
return progressVariant
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,22 +13,22 @@
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="h-100"
|
||||
class="d-flex h-100"
|
||||
:class="{ 'p-2': block.style.wrap.kind === 'card' }"
|
||||
>
|
||||
<b-progress
|
||||
<c-progress
|
||||
:value="value"
|
||||
:min="min"
|
||||
:max="max"
|
||||
class="h-100 bg-light"
|
||||
>
|
||||
<b-progress-bar
|
||||
:value="value"
|
||||
:striped="options.display.striped"
|
||||
:animated="options.display.animated"
|
||||
:variant="progressVariant"
|
||||
>
|
||||
{{ progressLabel }}
|
||||
</b-progress-bar>
|
||||
</b-progress>
|
||||
:labeled="options.display.showValue"
|
||||
:relative="options.display.showRelative"
|
||||
:progress="options.display.showProgress"
|
||||
:striped="options.display.striped"
|
||||
:animated="options.display.animated"
|
||||
:variant="options.display.variant"
|
||||
:thresholds="options.display.thresholds"
|
||||
class="flex-fill h-100"
|
||||
/>
|
||||
</div>
|
||||
</wrap>
|
||||
</template>
|
||||
@@ -36,65 +36,26 @@
|
||||
<script>
|
||||
import base from './base'
|
||||
import { NoID } from '@cortezaproject/corteza-js'
|
||||
import { components } from '@cortezaproject/corteza-vue'
|
||||
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
const { CProgress } = components
|
||||
|
||||
export default {
|
||||
extends: base,
|
||||
|
||||
props: {
|
||||
components: {
|
||||
CProgress,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
return {
|
||||
processing: false,
|
||||
value: undefined,
|
||||
min: undefined,
|
||||
max: undefined,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
progress () {
|
||||
const { value = 0, max = 100 } = this
|
||||
return 100 * (value / max)
|
||||
},
|
||||
|
||||
progressLabel () {
|
||||
let { value } = this
|
||||
const { showValue, showRelative, showProgress } = this.options.display || {}
|
||||
|
||||
if (!showValue) {
|
||||
return
|
||||
}
|
||||
|
||||
if (showRelative) {
|
||||
// https://stackoverflow.com/a/21907972/17926309
|
||||
value = `${Math.round(((value / this.max) * 100) * 100) / 100}%`
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
value = `${value} / ${showRelative ? '100' : this.max}${showRelative ? '%' : ''}`
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
sortedVariants () {
|
||||
return [...this.options.display.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
|
||||
},
|
||||
|
||||
progressVariant () {
|
||||
const { variant } = this.options.display || {}
|
||||
let progressVariant = variant
|
||||
|
||||
if (this.options.display.thresholds.length) {
|
||||
const { variant } = this.sortedVariants.find(t => this.progress >= t.value) || {}
|
||||
progressVariant = variant || progressVariant
|
||||
}
|
||||
|
||||
return progressVariant
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'record.recordID': {
|
||||
immediate: true,
|
||||
@@ -102,6 +63,13 @@ export default {
|
||||
this.refresh()
|
||||
},
|
||||
},
|
||||
|
||||
options: {
|
||||
deep: true,
|
||||
handler () {
|
||||
this.update()
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
created () {
|
||||
@@ -134,6 +102,14 @@ export default {
|
||||
userID: (this.$auth.user || {}).userID || NoID,
|
||||
}),
|
||||
},
|
||||
minValue: {
|
||||
filter: evaluatePrefilter(this.options.minValue.filter, {
|
||||
record: this.record,
|
||||
recordID: (this.record || {}).recordID || NoID,
|
||||
ownerID: (this.record || {}).ownedBy || NoID,
|
||||
userID: (this.$auth.user || {}).userID || NoID,
|
||||
}),
|
||||
},
|
||||
maxValue: {
|
||||
filter: evaluatePrefilter(this.options.maxValue.filter, {
|
||||
record: this.record,
|
||||
@@ -144,10 +120,12 @@ export default {
|
||||
},
|
||||
}
|
||||
|
||||
return this.block.fetch(additionalOptions, this.$ComposeAPI, namespaceID).then(({ value, max }) => {
|
||||
this.value = value
|
||||
this.max = max
|
||||
}).catch(this.toastErrorHandler(this.$t('progress.fetch-failed')))
|
||||
return this.block.fetch(additionalOptions, this.$ComposeAPI, namespaceID)
|
||||
.then(({ value, min = 0, max = 100 }) => {
|
||||
this.min = min
|
||||
this.max = max
|
||||
this.value = value
|
||||
}).catch(this.toastErrorHandler(this.$t('progress.fetch-failed')))
|
||||
.finally(() => {
|
||||
this.processing = false
|
||||
})
|
||||
|
||||
@@ -9,6 +9,22 @@
|
||||
</h5>
|
||||
|
||||
<b-row>
|
||||
<b-col
|
||||
v-if="!options.value.moduleID"
|
||||
cols="12"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('progress.value.default.label')"
|
||||
:description="$t('progress.value.default.description')"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="options.value.default"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
>
|
||||
@@ -91,12 +107,132 @@
|
||||
|
||||
<hr>
|
||||
|
||||
<template>
|
||||
<h5 class="text-primary">
|
||||
{{ $t('progress.value.min') }}
|
||||
</h5>
|
||||
|
||||
<b-row>
|
||||
<b-col
|
||||
v-if="!options.minValue.moduleID"
|
||||
cols="12"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('progress.value.default.label')"
|
||||
:description="$t('progress.value.default.description')"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="options.minValue.default"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('progress.module.label')"
|
||||
>
|
||||
<vue-select
|
||||
v-model="options.minValue.moduleID"
|
||||
label="name"
|
||||
:placeholder="$t('progress.module.select')"
|
||||
:options="modules"
|
||||
:reduce="m => m.moduleID"
|
||||
class="bg-white"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<template v-if="options.minValue.moduleID">
|
||||
<b-col
|
||||
cols="12"
|
||||
>
|
||||
<b-form-group :label="$t('metric.edit.filterLabel')">
|
||||
<b-form-textarea
|
||||
v-model="options.minValue.filter"
|
||||
placeholder="(A > B) OR (A < C)"
|
||||
class="mb-1"
|
||||
/>
|
||||
<b-form-text>
|
||||
<i18next
|
||||
path="metric.edit.filterFootnote"
|
||||
tag="label"
|
||||
>
|
||||
<code>${recordID}</code>
|
||||
<code>${ownerID}</code>
|
||||
<code>${userID}</code>
|
||||
</i18next>
|
||||
</b-form-text>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('progress.field.label')"
|
||||
>
|
||||
<vue-select
|
||||
v-model="options.minValue.field"
|
||||
:placeholder="$t('progress.field.select')"
|
||||
:options="minValueModuleFields"
|
||||
:reduce="f => f.name"
|
||||
class="bg-white"
|
||||
@input="fieldChanged($event, options.minValue)"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('progress.aggregate.label')"
|
||||
>
|
||||
<vue-select
|
||||
v-model="options.minValue.operation"
|
||||
label="name"
|
||||
:disabled="!options.minValue.field || options.minValue.field === 'count'"
|
||||
:placeholder="$t('progress.aggregate.select')"
|
||||
:options="aggregationOperations"
|
||||
:reduce="a => a.operation"
|
||||
class="bg-white"
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
</template>
|
||||
</b-row>
|
||||
</template>
|
||||
|
||||
<hr>
|
||||
|
||||
<template>
|
||||
<h5 class="text-primary">
|
||||
{{ $t('progress.value.max') }}
|
||||
</h5>
|
||||
|
||||
<b-row>
|
||||
<b-col
|
||||
v-if="!options.maxValue.moduleID"
|
||||
cols="12"
|
||||
>
|
||||
<b-form-group
|
||||
:label="$t('progress.value.default.label')"
|
||||
:description="$t('progress.value.default.description')"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="options.maxValue.default"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-form-group>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
>
|
||||
@@ -307,60 +443,66 @@
|
||||
</b-col>
|
||||
</b-row>
|
||||
|
||||
<!-- Don't remove, makes sure preview doesn't cover content -->
|
||||
<div class="w-501 my-5 py-5 py-sm-3" />
|
||||
<hr>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="preview bg-white position-absolute p-3"
|
||||
>
|
||||
<h6 class="text-primary">
|
||||
{{ $t('progress.preview') }}
|
||||
</h6>
|
||||
<h6 class="text-primary">
|
||||
{{ $t('progress.preview') }}
|
||||
</h6>
|
||||
|
||||
<b-progress
|
||||
:max="preview.max"
|
||||
height="1.5rem"
|
||||
class="mb-2 bg-light"
|
||||
<b-row>
|
||||
<b-col
|
||||
cols="12"
|
||||
>
|
||||
<b-progress-bar
|
||||
:value="preview.value"
|
||||
:striped="options.display.striped"
|
||||
:animated="options.display.animated"
|
||||
:variant="progressVariant"
|
||||
>
|
||||
{{ progressLabel }}
|
||||
</b-progress-bar>
|
||||
</b-progress>
|
||||
<field-viewer
|
||||
value-only
|
||||
v-bind="mock"
|
||||
class="mb-2"
|
||||
/>
|
||||
</b-col>
|
||||
|
||||
<b-row>
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
{{ $t('progress.value.label') }}
|
||||
<b-form-input
|
||||
v-model="preview.value"
|
||||
:placeholder="$t('progress.value.label')"
|
||||
type="number"
|
||||
size="sm"
|
||||
/>
|
||||
</b-col>
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
{{ $t('progress.value.label') }}
|
||||
<b-form-input
|
||||
v-model="mock.record.values.mockField"
|
||||
:placeholder="$t('progress.value.label')"
|
||||
size="sm"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
{{ $t('progress.value.max') }}
|
||||
<b-form-input
|
||||
v-model="preview.max"
|
||||
:placeholder="$t('progress.value.max')"
|
||||
type="number"
|
||||
size="sm"
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</div>
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
{{ $t('progress.value.min') }}
|
||||
<b-form-input
|
||||
v-model="mock.field.options.min"
|
||||
:placeholder="$t('progress.value.min')"
|
||||
size="sm"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-col>
|
||||
|
||||
<b-col
|
||||
cols="12"
|
||||
sm="4"
|
||||
>
|
||||
{{ $t('progress.value.max') }}
|
||||
<b-form-input
|
||||
v-model="mock.field.options.max"
|
||||
:placeholder="$t('progress.value.max')"
|
||||
size="sm"
|
||||
type="number"
|
||||
number
|
||||
/>
|
||||
</b-col>
|
||||
</b-row>
|
||||
</template>
|
||||
</template>
|
||||
</b-tab>
|
||||
@@ -369,7 +511,9 @@
|
||||
<script>
|
||||
import base from './base'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { compose, validator } from '@cortezaproject/corteza-js'
|
||||
import { VueSelect } from 'vue-select'
|
||||
import FieldViewer from '../ModuleFields/Viewer'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
@@ -380,17 +524,13 @@ export default {
|
||||
|
||||
components: {
|
||||
VueSelect,
|
||||
FieldViewer,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
return {
|
||||
preview: {
|
||||
value: 15,
|
||||
max: 50,
|
||||
},
|
||||
|
||||
aggregationOperations: [
|
||||
{
|
||||
name: this.$t('metric.edit.operationSum'),
|
||||
@@ -420,6 +560,14 @@ export default {
|
||||
{ text: this.$t('progress.variant.light'), value: 'light' },
|
||||
{ text: this.$t('progress.variant.dark'), value: 'dark' },
|
||||
],
|
||||
|
||||
mock: {
|
||||
namespace: undefined,
|
||||
module: undefined,
|
||||
field: undefined,
|
||||
record: undefined,
|
||||
errors: new validator.Validated(),
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -431,61 +579,58 @@ export default {
|
||||
|
||||
sharedModuleFields () {
|
||||
return [
|
||||
{ name: 'count', label: 'Count' },
|
||||
{ name: 'count', label: this.$t('progress.count') },
|
||||
]
|
||||
},
|
||||
|
||||
valueModuleFields () {
|
||||
return [
|
||||
...this.sharedModuleFields,
|
||||
...this.moduleByID(this.options.value.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.name.localeCompare(b.name)),
|
||||
...this.moduleByID(this.options.value.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.label.localeCompare(b.label)),
|
||||
]
|
||||
},
|
||||
|
||||
minValueModuleFields () {
|
||||
return [
|
||||
...this.sharedModuleFields,
|
||||
...this.moduleByID(this.options.minValue.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.label.localeCompare(b.label)),
|
||||
]
|
||||
},
|
||||
|
||||
maxValueModuleFields () {
|
||||
return [
|
||||
...this.sharedModuleFields,
|
||||
...this.moduleByID(this.options.maxValue.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.name.localeCompare(b.name)),
|
||||
...this.moduleByID(this.options.maxValue.moduleID).fields.filter(f => f.kind === 'Number').sort((a, b) => a.label.localeCompare(b.label)),
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
progress () {
|
||||
const { value = 0, max = 100 } = this.preview
|
||||
return 100 * (value / max)
|
||||
watch: {
|
||||
options: {
|
||||
deep: true,
|
||||
handler ({ display = {} }) {
|
||||
if (this.mock.field) {
|
||||
this.mock.field.options = {
|
||||
...this.mock.field.options,
|
||||
...display,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
progressLabel () {
|
||||
let { value, max } = this.preview
|
||||
const { showValue, showRelative, showProgress } = this.options.display || {}
|
||||
|
||||
if (!showValue) {
|
||||
return
|
||||
}
|
||||
|
||||
if (showRelative) {
|
||||
// https://stackoverflow.com/a/21907972/17926309
|
||||
value = `${Math.round(((value / max) * 100) * 100) / 100}%`
|
||||
}
|
||||
|
||||
if (showProgress) {
|
||||
value = `${value} / ${showRelative ? '100' : max}${showRelative ? '%' : ''}`
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
progressVariant () {
|
||||
const { variant } = this.options.display || {}
|
||||
let progressVariant = variant
|
||||
|
||||
if (this.options.display.thresholds.length) {
|
||||
const sortedVariants = [...this.options.display.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
|
||||
const { variant } = sortedVariants.find(t => this.progress >= t.value) || {}
|
||||
progressVariant = variant || progressVariant
|
||||
}
|
||||
|
||||
return progressVariant
|
||||
},
|
||||
created () {
|
||||
this.mock.namespace = this.namespace
|
||||
this.mock.field = compose.ModuleFieldMaker({ kind: 'Number' })
|
||||
this.mock.field.apply({ name: 'mockField' })
|
||||
this.mock.field.options.display = 'progress'
|
||||
this.mock.field.options = {
|
||||
display: 'progress',
|
||||
...this.mock.field.options,
|
||||
...this.options.display,
|
||||
}
|
||||
this.mock.module = new compose.Module({ fields: [this.mock.field] }, this.namespace)
|
||||
this.mock.record = new compose.Record(this.mock.module, { mockField: 15 })
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -500,7 +645,7 @@ export default {
|
||||
},
|
||||
|
||||
fieldChanged (value, optionsType) {
|
||||
if (!value) {
|
||||
if (!value || value === 'count') {
|
||||
optionsType.operation = ''
|
||||
}
|
||||
},
|
||||
|
||||
@@ -17,6 +17,7 @@ interface NumberOptions extends Options {
|
||||
precision: number;
|
||||
multiDelimiter: string;
|
||||
display: string;
|
||||
min: number;
|
||||
max: number;
|
||||
showValue: boolean;
|
||||
showRelative: boolean;
|
||||
@@ -36,6 +37,7 @@ const defaults = (): Readonly<NumberOptions> => Object.freeze({
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
// Progress bar display options
|
||||
min: 0,
|
||||
max: 100,
|
||||
showValue: true,
|
||||
showRelative: true,
|
||||
@@ -60,7 +62,7 @@ export class ModuleFieldNumber extends ModuleField {
|
||||
super.applyOptions(o)
|
||||
|
||||
Apply(this.options, o, String, 'format', 'prefix', 'suffix', 'multiDelimiter', 'display', 'variant')
|
||||
Apply(this.options, o, Number, 'precision', 'max')
|
||||
Apply(this.options, o, Number, 'precision', 'min', 'max')
|
||||
Apply(this.options, o, Boolean, 'showValue', 'showRelative', 'showProgress', 'animated')
|
||||
|
||||
if (o.thresholds) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from 'lodash'
|
||||
import { PageBlock, PageBlockInput, Registry } from './base'
|
||||
import { dimensionFunctions } from '../chart/util'
|
||||
import { Compose as ComposeAPI } from '../../../api-clients'
|
||||
@@ -6,6 +7,7 @@ import { Apply } from '../../../cast'
|
||||
const kind = 'Progress'
|
||||
|
||||
interface ValueOptions {
|
||||
default: number;
|
||||
moduleID: string;
|
||||
filter: string;
|
||||
field: string;
|
||||
@@ -28,6 +30,7 @@ interface DisplayOptions {
|
||||
|
||||
interface Options {
|
||||
value: ValueOptions;
|
||||
minValue: ValueOptions;
|
||||
maxValue: ValueOptions;
|
||||
display: DisplayOptions;
|
||||
refreshRate: number;
|
||||
@@ -35,6 +38,15 @@ interface Options {
|
||||
|
||||
const defaults: Readonly<Options> = Object.freeze({
|
||||
value: {
|
||||
default: 0,
|
||||
moduleID: '',
|
||||
filter: '',
|
||||
field: '',
|
||||
operation: '',
|
||||
},
|
||||
|
||||
minValue: {
|
||||
default: 0,
|
||||
moduleID: '',
|
||||
filter: '',
|
||||
field: '',
|
||||
@@ -42,6 +54,7 @@ const defaults: Readonly<Options> = Object.freeze({
|
||||
},
|
||||
|
||||
maxValue: {
|
||||
default: 100,
|
||||
moduleID: '',
|
||||
filter: '',
|
||||
field: '',
|
||||
@@ -75,15 +88,19 @@ export class PageBlockProgress extends PageBlock {
|
||||
Apply(this.options, o, Number, 'refreshRate')
|
||||
|
||||
if (o.value) {
|
||||
this.options.value = o.value
|
||||
this.options.value = { ...this.options.value, ...o.value }
|
||||
}
|
||||
|
||||
if (o.minValue) {
|
||||
this.options.minValue = { ...this.options.minValue, ...o.minValue }
|
||||
}
|
||||
|
||||
if (o.maxValue) {
|
||||
this.options.maxValue = o.maxValue
|
||||
this.options.maxValue = { ...this.options.maxValue, ...o.maxValue }
|
||||
}
|
||||
|
||||
if (o.display) {
|
||||
this.options.display = o.display
|
||||
this.options.display = { ...this.options.display, ...o.display }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,52 +108,94 @@ export class PageBlockProgress extends PageBlock {
|
||||
* Helper function to fetch and parse reporter's reports.
|
||||
*/
|
||||
fetch (options: Options, api: ComposeAPI, namespaceID: string): Promise<object> {
|
||||
options = _.merge(this.options, options)
|
||||
const reports = []
|
||||
const dimensions = dimensionFunctions.convert({ modifier: 'YEAR', field: 'createdAt' })
|
||||
|
||||
let metrics = ''
|
||||
|
||||
// Construct value report
|
||||
const { field: valueField, operation: valueOperation = '' } = this.options.value
|
||||
if (valueOperation && valueField && valueField !== 'count') {
|
||||
metrics = `${valueOperation}(${valueField}) AS rp`
|
||||
}
|
||||
const valueReport = api.recordReport({ namespaceID, metrics, dimensions, ...this.options.value, ...options.value })
|
||||
|
||||
// Construct max value report
|
||||
metrics = ''
|
||||
if (options.value.moduleID && valueField) {
|
||||
if (valueOperation && valueField !== 'count') {
|
||||
metrics = `${valueOperation}(${valueField}) AS rp`
|
||||
}
|
||||
|
||||
reports.push(api.recordReport({ namespaceID, metrics, dimensions, ...options.value }))
|
||||
} else {
|
||||
reports.push(new Promise(resolve => resolve(options.value.default)))
|
||||
}
|
||||
|
||||
// Construct minValue report
|
||||
const { field: minValueField, operation: minValueOperation = '' } = this.options.minValue
|
||||
|
||||
if (options.minValue.moduleID && minValueField) {
|
||||
metrics = ''
|
||||
if (minValueOperation && minValueField !== 'count') {
|
||||
metrics = `${minValueOperation}(${minValueField}) AS rp`
|
||||
}
|
||||
|
||||
reports.push(api.recordReport({ namespaceID, metrics, dimensions, ...options.minValue }))
|
||||
} else {
|
||||
reports.push(new Promise(resolve => resolve(options.minValue.default)))
|
||||
}
|
||||
|
||||
// Construct minValue report
|
||||
const { field: maxValueField, operation: maxValueOperation = '' } = this.options.maxValue
|
||||
if (maxValueOperation && maxValueField && maxValueField !== 'count') {
|
||||
metrics = `${maxValueOperation}(${maxValueField}) AS rp`
|
||||
|
||||
if (options.maxValue.moduleID && maxValueField) {
|
||||
metrics = ''
|
||||
if (maxValueOperation && maxValueField !== 'count') {
|
||||
metrics = `${maxValueOperation}(${maxValueField}) AS rp`
|
||||
}
|
||||
|
||||
reports.push(api.recordReport({ namespaceID, metrics, dimensions, ...options.maxValue }))
|
||||
} else {
|
||||
reports.push(new Promise(resolve => resolve(options.maxValue.default)))
|
||||
}
|
||||
const maxValueReport = api.recordReport({ namespaceID, metrics, dimensions, ...this.options.maxValue, ...options.maxValue })
|
||||
|
||||
return Promise.all([valueReport, maxValueReport]).then(([v, m]: Array<any>) => {
|
||||
let value: number
|
||||
let max: number
|
||||
|
||||
let datasets = v.map((r: any) => r.rp !== undefined ? r.rp : r.count)
|
||||
if (valueOperation === 'max') {
|
||||
value = datasets.sort((a: number, b: number) => b - a)[0]
|
||||
} else if (valueOperation === 'min') {
|
||||
value = datasets.sort((a: number, b: number) => a - b)[0]
|
||||
} else if (valueOperation === 'avg') {
|
||||
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
|
||||
} else {
|
||||
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
|
||||
return Promise.all(reports).then(([value, min, max]: Array<any>) => {
|
||||
if (Array.isArray(value)) {
|
||||
const datasets = value.map((r: any) => r.rp !== undefined ? r.rp : r.count)
|
||||
if (valueOperation === 'max') {
|
||||
value = datasets.sort((a: number, b: number) => b - a)[0]
|
||||
} else if (valueOperation === 'min') {
|
||||
value = datasets.sort((a: number, b: number) => a - b)[0]
|
||||
} else if (valueOperation === 'avg') {
|
||||
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
|
||||
} else {
|
||||
value = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
|
||||
}
|
||||
}
|
||||
|
||||
datasets = m.map((r: any) => r.rp !== undefined ? r.rp : r.count)
|
||||
if (maxValueOperation === 'max') {
|
||||
max = datasets.sort((a: number, b: number) => b - a)[0]
|
||||
} else if (maxValueOperation === 'min') {
|
||||
max = datasets.sort((a: number, b: number) => a - b)[0]
|
||||
} else if (maxValueOperation === 'avg') {
|
||||
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
|
||||
} else {
|
||||
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
|
||||
if (Array.isArray(min)) {
|
||||
const datasets = min.map((r: any) => r.rp !== undefined ? r.rp : r.count)
|
||||
if (minValueOperation === 'max') {
|
||||
min = datasets.sort((a: number, b: number) => b - a)[0]
|
||||
} else if (minValueOperation === 'min') {
|
||||
min = datasets.sort((a: number, b: number) => a - b)[0]
|
||||
} else if (minValueOperation === 'avg') {
|
||||
min = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
|
||||
} else {
|
||||
min = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return { value, max }
|
||||
if (Array.isArray(max)) {
|
||||
const datasets = max.map((r: any) => r.rp !== undefined ? r.rp : r.count)
|
||||
if (maxValueOperation === 'max') {
|
||||
max = datasets.sort((a: number, b: number) => b - a)[0]
|
||||
} else if (maxValueOperation === 'min') {
|
||||
max = datasets.sort((a: number, b: number) => a - b)[0]
|
||||
} else if (maxValueOperation === 'avg') {
|
||||
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0) / datasets.length
|
||||
} else {
|
||||
max = datasets.reduce((acc: number, cur: number) => acc + cur, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return { value, min, max }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,4 +77,8 @@ export {
|
||||
CChart,
|
||||
} from './chart'
|
||||
|
||||
export {
|
||||
CProgress,
|
||||
} from './progress'
|
||||
|
||||
export { default as C3 } from './c3'
|
||||
|
||||
128
lib/vue/src/components/progress/CProgress.vue
Normal file
128
lib/vue/src/components/progress/CProgress.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<b-progress
|
||||
:max="maxValue"
|
||||
class="bg-light position-relative"
|
||||
>
|
||||
<b-progress-bar
|
||||
:value="progressValue < 0 ? 0 : progressValue"
|
||||
:striped="striped"
|
||||
:animated="animated"
|
||||
:variant="progressVariant"
|
||||
>
|
||||
<strong
|
||||
:class="textVariant"
|
||||
class="d-flex align-items-center justify-content-center position-absolute mb-0 w-100"
|
||||
>
|
||||
{{ progressLabel }}
|
||||
</strong>
|
||||
</b-progress-bar>
|
||||
</b-progress>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
},
|
||||
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100,
|
||||
},
|
||||
|
||||
labeled: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
relative: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
progress: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
striped: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
animated: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'success',
|
||||
},
|
||||
|
||||
thresholds: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
maxValue () {
|
||||
return Math.abs(this.max - this.min)
|
||||
},
|
||||
|
||||
progressValue () {
|
||||
if (this.value < this.min && this.max > this.min) {
|
||||
return this.value - this.min
|
||||
} else if (this.value > this.min && this.max < this.min) {
|
||||
return this.min - this.value
|
||||
}
|
||||
|
||||
return Math.abs(this.value - this.min)
|
||||
},
|
||||
|
||||
progressLabel () {
|
||||
let value = this.value
|
||||
|
||||
if (!this.labeled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.relative) {
|
||||
// https://stackoverflow.com/a/21907972/17926309
|
||||
value = `${Math.round((((this.progressValue) / this.maxValue) * 100) * 100) / 100}%`
|
||||
}
|
||||
|
||||
if (this.progress) {
|
||||
value = `${value} / ${this.relative ? '100' : this.max}${this.relative ? '%' : ''}`
|
||||
}
|
||||
|
||||
return value
|
||||
},
|
||||
|
||||
progressVariant () {
|
||||
const value = Math.round((((this.progressValue < 0 ? 0 : this.progressValue) / this.maxValue) * 100) * 100) / 100
|
||||
|
||||
let progressVariant = this.variant
|
||||
|
||||
if (this.thresholds.length) {
|
||||
const { variant } = this.sortedVariants.find(t => value >= t.value) || {}
|
||||
progressVariant = variant || progressVariant
|
||||
}
|
||||
|
||||
return progressVariant
|
||||
},
|
||||
|
||||
sortedVariants () {
|
||||
return [...this.thresholds].filter(t => t.value >= 0).sort((a, b) => b.value - a.value)
|
||||
},
|
||||
|
||||
textVariant () {
|
||||
return ['dark', 'primary'].includes(this.progressVariant) ? 'text-white' : 'text-dark'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
1
lib/vue/src/components/progress/index.ts
Normal file
1
lib/vue/src/components/progress/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as CProgress } from './CProgress.vue'
|
||||
@@ -491,6 +491,7 @@ report:
|
||||
|
||||
progress:
|
||||
add: + Add
|
||||
count: Count
|
||||
aggregate:
|
||||
label: Aggregation operation
|
||||
select: Select aggregation operation
|
||||
@@ -518,7 +519,11 @@ progress:
|
||||
variant: Set which variant to show if value is equal or larger than the threshold
|
||||
value:
|
||||
label: Value
|
||||
min: Minimum value
|
||||
max: Maximum value
|
||||
default:
|
||||
label: Default value
|
||||
description: This value will be used if metric is not configured
|
||||
variant:
|
||||
default: Dark
|
||||
danger: Danger
|
||||
|
||||
@@ -52,11 +52,11 @@ kind:
|
||||
exampleFormat: Format
|
||||
exampleInput: Input
|
||||
exampleResult: Result
|
||||
examplesLabel: 'Format examples:'
|
||||
examplesLabel: Format examples
|
||||
formatLabel: Format
|
||||
formatPlaceholder: Format
|
||||
label: Number
|
||||
liveExample: 'Live example:'
|
||||
liveExample: Live example
|
||||
precisionLabel: Precision
|
||||
precisionPlaceholder: Precision
|
||||
prefixLabel: Prefix
|
||||
@@ -67,8 +67,9 @@ kind:
|
||||
label: Display type
|
||||
number: Number
|
||||
progress: Progress bar
|
||||
maximumValue: Maximum value
|
||||
progress:
|
||||
maximumValue: Maximum value
|
||||
minimumValue: Minimum value
|
||||
animated: Animated
|
||||
show:
|
||||
value: Show value
|
||||
|
||||
Reference in New Issue
Block a user