Skip to content

Instantly share code, notes, and snippets.

@highoncarbs
Created September 22, 2023 07:58
Show Gist options
  • Save highoncarbs/39d4293133176ff4fc949d48b492a539 to your computer and use it in GitHub Desktop.
Save highoncarbs/39d4293133176ff4fc949d48b492a539 to your computer and use it in GitHub Desktop.
<template>
<div class="container">
<p class="is-size-5 has-text-weight-semibold">Formula Builder</p>
<br />
<!-- Entry Form -->
<div class="">
<div class="">
<div class="">
<div class="control">
<b-field label="Salary Formula Builder" :type="{ 'is-danger': form.errors.name }">
<b-input type="text" v-model="form.name" ref="name" placeholder="Enter Salary Formula Name" />
</b-field>
</div>
<br />
<div class="mb-3">
<div class="level is-mobile">
<div class="level-left">
<!-- <b-field grouped class="level-item">
<b-select v-model="" placeholder="Select condition">
<option :value="row.value" v-for="(row, index) in operators.conditions">{{ row.name }}</option>
</b-select>
<p class="control">
<button @click.prevent="addRowPlus" class="button is-link">
Add new group
</button>
</p>
</b-field> -->
<!-- <b-field grouped class="level-item">
<b-select placeholder="Select a name">
<option :value="row.value" v-for="(row, index) in operators.conditions">{{ row.name }}</option>
</b-select>
<p class="control">
<button @click.prevent="addGroupPlus" class="button is-link">
Add New Group
</button>
</p>
</b-field> -->
<!-- <button class="button is-link level-item">
Add Condition
</button>
<button class="button is-light level-item">
Add Formula
</button> -->
</div>
</div>
</div>
<table class="table is-fullwidth">
<tr class="box pt-2 box-ss "
v-for="( row, index ) in structure_list "
:key="index">
<div class="">
<div class="level py-0 my-0 is-mobile">
<div class="level-left">
<div class="level-item has-text-weight-semibold">
{{ index + 1 }}.
</div>
</div>
<div class="level-right">
<button class="button level-item is-text">
<b-icon icon="content-duplicate"> </b-icon>
</button>
<button @click.prevent="deleteRow(index)" class="button level-item is-text">
<b-icon icon="close"> </b-icon>
</button>
</div>
</div>
<b-field grouped>
<b-field label-position="on-border" label="Select Master" :type="{ 'is-danger': errors.first_master }">
<b-autocomplete open-on-focus select-on-click-outside keep-first ref="first_master" field="name"
v-model="row.query_first_master" @select="(option) => row.first_master = option"
icon-right="chevron-down" :data="data_master_filter" @typing="filteredMasterArray(index, 'first')"
icon="magnify" placeholder="Select Master">
<template #header>
<span class="has-text-link is-clickable" @click="showAddData('location', index)">
<span>Add new...</span>
</span>
</template>
</b-autocomplete>
</b-field>
<b-field label="Operator" label-position="on-border" :type="{ 'is-danger': errors.percentage }">
<b-select expanded v-model="row.operator">
<option :value="row.value" v-for="(row, index) in operators.math">{{ row.name }}</option>
</b-select>
</b-field>
<b-field label="Data Type" label-position="on-border" :type="{ 'is-danger': errors.percentage }">
<b-select expanded v-model="row.option_for_second">
<option value="value">Value</option>
<option value="master">Master</option>
</b-select>
</b-field>
<b-field v-if="row.option_for_second == 'value'" label-position="on-border" label="Value"
:type="{ 'is-danger': errors.percentage }">
<b-input type="number" step="0.01" v-model="row.second_value"></b-input>
</b-field>
<b-field v-if="row.option_for_second == 'master'" label-position="on-border"
label="Select Second Master" :type="{ 'is-danger': errors.second_master }">
<b-autocomplete open-on-focus select-on-click-outside keep-first ref="first_master" field="name"
v-model="row.query_second_master" @select="(option) => row.second_master = option"
icon-right="chevron-down" :data="data_master_filter" @typing="filteredMasterArray(index, 'second')"
icon="magnify" placeholder="Select Master">
<template #header>
<span class="has-text-link is-clickable" @click="showAddData('location', index)">
<span>Add new...</span>
</span>
</template>
</b-autocomplete>
</b-field>
</b-field>
<!-- <b-select v-model="row.append_operator" placeholder="Select condition">
<option :value="row.value" v-for="(row, index) in operators.conditions">{{ row.name }}</option>
</b-select> -->
<!-- <p class="heading">Add rule to this group</p> -->
<b-field grouped>
<b-field label-position="on-border" label="Select Conditions">
<b-select v-model="row.append_operator" aria-label="">
<option :value="con.value" v-for="(con, index_con) in operators.conditions">
{{ con.name }}
</option>
</b-select>
</b-field>
<p class="control">
<button @click.prevent="addInnerRowPlus(index)" class="button is-link">
Add Rule
</button>
</p>
</b-field>
<tr class="box pt-2 box-ss "
v-for="( row_inn, index_inner ) in row.inner "
:key="index_inner">
<div class="">
<div class="level py-0 my-0 is-mobile">
<div class="level-left">
<div class="level-item has-text-weight-semibold">
<span class="has-text-grey">
{{ index + 1 }}
</span>
.
<span class="has-text-grey">
{{ index_inner + 1 }}
</span>
</div>
</div>
<div class="level-right">
<button class="button level-item is-text">
<b-icon icon="content-duplicate"> </b-icon>
</button>
<button @click.prevent="deleteInnerRow(index, index_inner)" class="button level-item is-text">
<b-icon icon="close"> </b-icon>
</button>
</div>
</div>
<b-field grouped>
<b-field label-position="on-border" label="Select Master" :type="{ 'is-danger': errors.first_master }">
<b-autocomplete open-on-focus select-on-click-outside keep-first ref="first_master" field="name"
v-model="row_inn.query_first_master" @select="(option) => row_inn.first_master = option"
icon-right="chevron-down" :data="data_master_filter"
@typing="filteredMasterInnerArray(index, index_inner, 'first')" icon="magnify"
placeholder="Select Master">
<!-- <template #header>
<span class="has-text-link is-clickable" @click="showAddData('location', index)">
<span>Add new...</span>
</span>
</template> -->
</b-autocomplete>
</b-field>
<b-field label="Operator" label-position="on-border" :type="{ 'is-danger': errors.percentage }">
<b-select expanded v-model="row_inn.operator">
<option :value="row_op.value" v-for="(row_op, index) in operators.math">{{ row_op.name }}</option>
</b-select>
</b-field>
<b-field label="Data Type" label-position="on-border" :type="{ 'is-danger': errors.percentage }">
<b-select expanded v-model="row_inn.option_for_second">
<option value="value">Value</option>
<option value="master">Master</option>
</b-select>
</b-field>
<b-field v-if="row_inn.option_for_second == 'value'" label-position="on-border" label="Value"
:type="{ 'is-danger': errors.percentage }">
<b-input type="number" step="0.01" v-model="row_inn.second_value"></b-input>
</b-field>
<b-field v-if="row_inn.option_for_second == 'master'" label-position="on-border"
label="Select Second Master" :type="{ 'is-danger': errors.second_master }">
<b-autocomplete open-on-focus select-on-click-outside keep-first ref="first_master" field="name"
v-model="row_inn.query_second_master" @select="(option) => row_inn.second_master = option"
icon-right="chevron-down" :data="data_master_filter"
@typing="filteredMasterInnerArray(index, index_inner, 'second')" icon="magnify"
placeholder="Select Master">
<template #header>
<span class="has-text-link is-clickable" @click="showAddData('location', index)">
<span>Add new...</span>
</span>
</template>
</b-autocomplete>
</b-field>
</b-field>
<b-field grouped>
<b-field label-position="on-border" label="Select Conditions">
<b-select v-model="row_inn.append_operator" aria-label="">
<option :value="con.value" v-for="(con, index_con) in operators.conditions">
{{ con.name }}
</option>
</b-select>
</b-field>
<p class="control">
<button @click.prevent="addSubInnerRowPlus(index, index_inner)" class="button is-link">
Add Sub-Rule
</button>
</p>
</b-field>
<!-- 2nd Level -->
<tr class="box pt-2 box-ss "
v-for="( row_inn_inn, index_inner_inn ) in row_inn.inner "
:key="index_inner">
<div class="">
<div class="level py-0 my-0 is-mobile">
<div class="level-left">
<div class="level-item has-text-weight-semibold">
<span class="has-text-grey">
{{ index + 1 }}
</span>
.
<span class="has-text-grey">
{{ index_inner + 1 }}
</span>
.
<span class="has-text-grey">
{{ index_inner_inn + 1 }}
</span>
</div>
</div>
<div class="level-right">
<button class="button level-item is-text">
<b-icon icon="content-duplicate"> </b-icon>
</button>
<button @click.prevent="deleteSubInnerRow(index, index_inner, index_inner_inn)"
class="button level-item is-text">
<b-icon icon="close"> </b-icon>
</button>
</div>
</div>
<b-field grouped>
<b-field label-position="on-border" label="Select Master" :type="{ 'is-danger': errors.first_master }">
<b-autocomplete open-on-focus select-on-click-outside keep-first ref="first_master" field="name"
v-model="row_inn_inn.query_first_master" @select="(option) => row_inn_inn.first_master = option"
icon-right="chevron-down" :data="data_master_filter"
@typing="filteredMasterSubInnerArray(index, index_inner, index_inner_inn, 'first')" icon="magnify"
placeholder="Select Master">
<!-- <template #header>
<span class="has-text-link is-clickable" @click="showAddData('location', index)">
<span>Add new...</span>
</span>
</template> -->
</b-autocomplete>
</b-field>
<b-field label="Operator" label-position="on-border" :type="{ 'is-danger': errors.percentage }">
<b-select expanded v-model="row_inn_inn.operator">
<option :value="row_op.value" v-for="(row_op, index) in operators.math">{{ row_op.name }}</option>
</b-select>
</b-field>
<b-field label="Data Type" label-position="on-border" :type="{ 'is-danger': errors.percentage }">
<b-select expanded v-model="row_inn_inn.option_for_second">
<option value="value">Value</option>
<option value="master">Master</option>
</b-select>
</b-field>
<b-field v-if="row_inn_inn.option_for_second == 'value'" label-position="on-border" label="Value"
:type="{ 'is-danger': errors.percentage }">
<b-input type="number" step="0.01" v-model="row_inn_inn.second_value"></b-input>
</b-field>
<b-field v-if="row_inn_inn.option_for_second == 'master'" label-position="on-border"
label="Select Second Master" :type="{ 'is-danger': errors.second_master }">
<b-autocomplete open-on-focus select-on-click-outside keep-first ref="first_master" field="name"
v-model="row_inn_inn.query_second_master" @select="(option) => row_inn_inn.second_master = option"
icon-right="chevron-down" :data="data_master_filter"
@typing="filteredMasterSubInnerArray(index, index_inner, index_inner_inn, 'second')" icon="magnify"
placeholder="Select Master">
<template #header>
<span class="has-text-link is-clickable" @click="showAddData('location', index)">
<span>Add new...</span>
</span>
</template>
</b-autocomplete>
</b-field>
</b-field>
<!-- <b-field grouped>
<p class="control">
<button @click.prevent="addSubInnerRowPlus(index, index_inner)" class="button is-link">
Add Rule
</button>
</p>
</b-field> -->
</div>
</tr>
</div>
</tr>
</div>
</tr>
</table>
<div class="notification">
{{ getComputedStringFormula }}
</div>
<div class=" ">
<pre class="notification is-light">
{{ getComputedFormula }}
</pre>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
layout: "admin",
data() {
return {
view: true,
form: {
errors: {},
id: null,
name: null,
},
structure_list: [
{
position: null,
first_master: null,
query_first_master: "",
operator: 'and',
option_for_second: 'value',
second_value: "",
query_second_master: "",
second_master: null,
second_value: null,
append_operator: null,
errors: {},
inner: [],
}
],
errors: {},
isSubmitting: false,
data: [],
loading: false,
loading_set: false,
modal: false,
data_master_filter: [],
data_master: [],
query_search: "",
query_location_second: "",
add_to_formula: null,
operators: {
conditions: [
{
name: 'All should be true',
value: 'and'
},
{
name: 'Any should be true',
value: 'or'
}
],
math: [
{
name: 'Equals',
value: '='
},
{
name: 'Add',
value: '+'
},
{
name: 'Subtract',
value: '-'
},
{
name: 'Multiply',
value: '*'
},
{
name: 'Divide',
value: '/'
},
{
name: 'Greater Than',
value: '>'
},
{
name: 'Less Than',
value: '<'
},
{
name: 'Greater Than Equal to',
value: '>='
},
{
name: 'Less Than Equal to',
value: '<='
},
],
}
};
},
computed: {
getComputedStringFormula() {
// Operator - For the rules
// Rules - under this operator ( first against second , type of rule - master or value )
// Operator - for the rules
// Rules - under this
// Operator - for the rules
// Rules - under this
let json_dump = {
'operator': 'and',
'rules': [],
}
let formula = "";
// let formula = json_dump.rules[0].first_master.name + ' ' + json_dump.rules[0].operator + ' ' + json_dump.rules[0].second_master ;
this.structure_list.forEach(row => {
let rule_obj = {
'operator': null,
'rules': [],
'first': null,
'second': null,
'compare_operator': null,
'second_value_type': null,
}
// Top level
if (row.first_master) {
rule_obj.first = row.first_master
formula = formula + ' ' + row.first_master.name
}
if (row.operator) {
rule_obj.compare_operator = row.operator
formula = formula + ' ' + row.operator
}
if (row.option_for_second) {
rule_obj.second_value_type = row.option_for_second
if (row.option_for_second == 'value') {
if (row.second_value) {
rule_obj.second = row.second_value
formula = formula + ' ' + row.second_value
}
}
if (row.option_for_second == 'master') {
if (row.second_master) {
rule_obj.second = row.second_master
formula = formula + ' ' + row.second_master.name
}
}
}
if (row.append_operator) {
rule_obj.operator = row.append_operator
formula = formula + ' ::' + row.append_operator + '::'
}
row.inner.forEach(inner_row => {
let inner_rule_obj = {
'operator': null,
'rules': [],
'first': null,
'second': null,
'compare_operator': null,
'second_value_type': null,
}
if (inner_row.first_master) {
inner_rule_obj.first = inner_row.first_master
formula = formula + ' ' + inner_row.first_master.name
}
if (inner_row.operator) {
inner_rule_obj.compare_operator = inner_row.operator
formula = formula + ' ' + inner_row.operator
}
if (inner_row.option_for_second) {
inner_rule_obj.second_value_type = inner_row.option_for_second
if (inner_row.option_for_second == 'value') {
if (inner_row.second_value) {
inner_rule_obj.second = inner_row.second_value
formula = formula + ' ' + inner_row.second_value
}
}
if (inner_row.option_for_second == 'master') {
if (inner_row.second_master) {
inner_rule_obj.second = inner_row.second_master
formula = formula + ' ' + inner_row.second_master.name
}
}
}
if (inner_row.append_operator) {
inner_rule_obj.operator = inner_row.append_operator
formula = formula + ' ::' + inner_row.append_operator + '::'
}
inner_row.inner.forEach(sub => {
let sub_inner_rule_obj = {
'first': null,
'second': null,
'compare_operator': null,
'second_value_type': null,
}
if (sub.first_master) {
sub_inner_rule_obj.first = sub.first_master
formula = formula + ' ' + sub.first_master.name
}
if (sub.operator) {
sub_inner_rule_obj.compare_operator = sub.operator
formula = formula + ' ' + sub.operator
}
if (sub.option_for_second) {
sub_inner_rule_obj.second_value_type = sub.option_for_second
if (sub.option_for_second == 'value') {
if (sub.second_value) {
sub_inner_rule_obj.second = sub.second_value
formula = formula + ' ' + sub.second_value
}
}
if (sub.option_for_second == 'master') {
if (sub.second_master) {
sub_inner_rule_obj.second = sub.second_master
formula = formula + ' ' + sub.second_master.name
}
}
}
if (sub.append_operator) {
sub_inner_rule_obj.operator = sub.append_operator
formula = formula + ' ::' + sub.append_operator + '::'
}
inner_rule_obj.rules.push(sub_inner_rule_obj)
})
rule_obj.rules.push(inner_rule_obj)
})
json_dump.rules.push(rule_obj)
});
// console.log(json_dump)
return this.formula;
},
getComputedFormula() {
// Operator - For the rules
// Rules - under this operator ( first against second , type of rule - master or value )
// Operator - for the rules
// Rules - under this
// Operator - for the rules
// Rules - under this
let json_dump = {
'operator': 'and',
'rules': [],
}
let formula = "";
this.structure_list.forEach(row => {
let rule_obj = {
'operator': null,
'rules': [],
'first': null,
'second': null,
'compare_operator': null,
'second_value_type': null,
}
// Top level
if (row.first_master) {
rule_obj.first = row.first_master
formula = formula + ' ' + row.first_master.name
}
if (row.operator) {
rule_obj.compare_operator = row.operator
formula = formula + ' ' + row.operator
}
if (row.option_for_second) {
rule_obj.second_value_type = row.option_for_second
if (row.option_for_second == 'value') {
if (row.second_value) {
rule_obj.second = row.second_value
formula = formula + ' ' + row.second_value
}
}
if (row.option_for_second == 'master') {
if (row.second_master) {
rule_obj.second = row.second_master
formula = formula + ' ' + row.second_master.name
}
}
}
if (row.append_operator) {
rule_obj.operator = row.append_operator
formula = formula + ' ::' + row.append_operator + '::'
}
row.inner.forEach(inner_row => {
let inner_rule_obj = {
'operator': null,
'rules': [],
'first': null,
'second': null,
'compare_operator': null,
'second_value_type': null,
}
if (inner_row.first_master) {
inner_rule_obj.first = inner_row.first_master
formula = formula + ' ' + inner_row.first_master.name
}
if (inner_row.operator) {
inner_rule_obj.compare_operator = inner_row.operator
formula = formula + ' ' + inner_row.operator
}
if (inner_row.option_for_second) {
inner_rule_obj.second_value_type = inner_row.option_for_second
if (inner_row.option_for_second == 'value') {
if (inner_row.second_value) {
inner_rule_obj.second = inner_row.second_value
formula = formula + ' ' + inner_row.second_value
}
}
if (inner_row.option_for_second == 'master') {
if (inner_row.second_master) {
inner_rule_obj.second = inner_row.second_master
formula = formula + ' ' + inner_row.second_master.name
}
}
}
if (inner_row.append_operator) {
inner_rule_obj.operator = inner_row.append_operator
formula = formula + ' ::' + inner_row.append_operator + '::'
}
inner_row.inner.forEach(sub => {
let sub_inner_rule_obj = {
'first': null,
'second': null,
'compare_operator': null,
'second_value_type': null,
}
if (sub.first_master) {
sub_inner_rule_obj.first = sub.first_master
formula = formula + ' ' + sub.first_master.name
}
if (sub.operator) {
sub_inner_rule_obj.compare_operator = sub.operator
formula = formula + ' ' + sub.operator
}
if (sub.option_for_second) {
sub_inner_rule_obj.second_value_type = sub.option_for_second
if (sub.option_for_second == 'value') {
if (sub.second_value) {
sub_inner_rule_obj.second = sub.second_value
formula = formula + ' ' + sub.second_value
}
}
if (sub.option_for_second == 'master') {
if (sub.second_master) {
sub_inner_rule_obj.second = sub.second_master
formula = formula + ' ' + sub.second_master.name
}
}
}
if (sub.append_operator) {
sub_inner_rule_obj.operator = sub.append_operator
formula = formula + ' ::' + sub.append_operator + '::'
}
inner_rule_obj.rules.push(sub_inner_rule_obj)
})
rule_obj.rules.push(inner_rule_obj)
})
json_dump.rules.push(rule_obj)
});
console.log(json_dump)
return JSON.stringify(json_dump, null, 2);
}
},
mounted() {
this.$refs.name.focus();
this.getData();
},
methods: {
deleteSubInnerRow(index, index_inner, index_inner_inn) {
console.log()
this.structure_list[index].inner[index_inner].inner.splice(index_inner_inn, 1)
},
deleteInnerRow(index, index_inner) {
this.structure_list[index].inner.splice(index_inner, 1)
},
deleteRow(index) {
this.structure_list.splice(index, 1)
},
filteredMasterArray(index, master_pos) {
let data_point = this.structure_list[index];
let query = "";
if (master_pos == 'first') {
query = data_point.query_first_master;
}
if (master_pos == 'second') {
query = data_point.query_second_master;
}
if (query != "") {
this.data_master_filter = this.data_master.filter((option) => {
return (
option.name
.toString()
.toLowerCase()
.indexOf(query.toLowerCase()) >= 0
);
});
}
return this.data_master_filter;
},
filteredMasterInnerArray(index, index_inner, master_pos) {
let data_point = this.structure_list[index].inner[index_inner];
let query = "";
if (master_pos == 'first') {
query = data_point.query_first_master;
}
if (master_pos == 'second') {
query = data_point.query_second_master;
}
if (query != "") {
this.data_master_filter = this.data_master.filter((option) => {
return (
option.name
.toString()
.toLowerCase()
.indexOf(query.toLowerCase()) >= 0
);
});
}
return this.data_master_filter;
},
filteredMasterSubInnerArray(index, index_inner, index_inner_inn, master_pos) {
let data_point = this.structure_list[index].inner[index_inner].inner[index_inner_inn];
let query = "";
if (master_pos == 'first') {
query = data_point.query_first_master;
}
if (master_pos == 'second') {
query = data_point.query_second_master;
}
if (query != "") {
this.data_master_filter = this.data_master.filter((option) => {
return (
option.name
.toString()
.toLowerCase()
.indexOf(query.toLowerCase()) >= 0
);
});
}
return this.data_master_filter;
},
addRowPlus() {
let obj =
{
position: null,
first_master: null,
query_first_master: "",
operator: null,
option_for_second: 'value',
query_second_master: "",
second_master: null,
second_value: null,
append_operator: null,
inner: [],
errors: {},
}
this.structure_list.push(obj)
this.add_to_formula = null
},
addInnerRowPlus(index) {
let obj =
{
position: null,
first_master: null,
query_first_master: "",
operator: null,
option_for_second: 'value',
query_second_master: "",
second_master: null,
second_value: null,
append_operator: null,
inner: [],
errors: {},
}
this.structure_list[index].inner.push(obj)
this.add_to_formula = null
},
addSubInnerRowPlus(index, index_inner) {
let obj =
{
position: null,
first_master: null,
query_first_master: "",
operator: null,
option_for_second: 'value',
query_second_master: "",
second_master: null,
second_value: null,
append_operator: null,
inner: [],
errors: {},
}
this.structure_list[index].inner[index_inner].inner.push(obj)
},
addRow(index) {
let obj = {
position: null,
first_master: null,
percentage: null,
second_master: null,
}
this.structure_list.push(obj)
},
getData(e) {
let self = this;
this.loading = true;
this.$axios
.get("/masters/get/location")
.then(function (response) {
self.data_master = response.data;
})
.catch(function (error) {
this.loading = false;
self.$buefy.snackbar.open({
duration: 4000,
message: "Unable to fetch data",
type: "is-light",
position: "is-top-right",
actionText: "Close",
queue: true,
onAction: () => {
self.isActive = false;
},
});
})
.finally(() => {
this.loading = false;
this.loading_set = true;
});
},
},
};
</script>
<style>
.box-ss {
border: 1px solid #dbdbdb;
box-shadow: none;
border-top: 3px solid lightblue;
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment