Merge pull request #2215 from bengu-thon-mai-mochi/invoice-form-enhancements
Improve UI on add/edit items on invoice & bill pages
This commit is contained in:
commit
79de8ed409
2
public/css/akaunting-color.css
vendored
2
public/css/akaunting-color.css
vendored
@ -2124,7 +2124,7 @@ button.bg-red:focus
|
|||||||
|
|
||||||
/*--------Form Error Color--------*/
|
/*--------Form Error Color--------*/
|
||||||
/*--Required Sign Color--*/
|
/*--Required Sign Color--*/
|
||||||
div.required > .form-control-label:not(span):after, td.required:after
|
div.required > .form-control-label:not(span):after, td.required:after, button.document-contact-without-contact-box-btn > span.text-add-contact::after
|
||||||
{
|
{
|
||||||
content: ' *';
|
content: ' *';
|
||||||
color: #ef3232;
|
color: #ef3232;
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<div class="aka-box-content">
|
<div class="aka-box-content">
|
||||||
<div class="document-contact-without-contact-box">
|
<div class="document-contact-without-contact-box">
|
||||||
<button type="button" class="btn-aka-link aka-btn--fluid document-contact-without-contact-box-btn" @click="onContactList">
|
<button type="button" class="btn-aka-link aka-btn--fluid document-contact-without-contact-box-btn" @click="onContactList">
|
||||||
<i class="far fa-user fa-2x"></i> {{ addContactText }}
|
<i class="far fa-user fa-2x"></i> <span class="text-add-contact"> {{ addContactText }} </span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -198,7 +198,6 @@ export default {
|
|||||||
default: () => [],
|
default: () => [],
|
||||||
description: 'List of Contacts'
|
description: 'List of Contacts'
|
||||||
},
|
},
|
||||||
|
|
||||||
addNew: {
|
addNew: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: function () {
|
default: function () {
|
||||||
|
@ -66,7 +66,7 @@ export default {
|
|||||||
|
|
||||||
period: {
|
period: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
default: 0,
|
default: "0",
|
||||||
description: "Payment period"
|
description: "Payment period"
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -145,7 +145,6 @@ export default {
|
|||||||
if (this.model) {
|
if (this.model) {
|
||||||
this.real_model = this.model;
|
this.real_model = this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('interface', this.real_model);
|
this.$emit('interface', this.real_model);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div :id="'select-item-button-' + _uid" class="product-select">
|
<div :id="'select-item-button-' + _uid" class="product-select">
|
||||||
<div class="item-add-new">
|
<div class="item-add-new">
|
||||||
<button type="button" class="btn btn-link w-100" @click="onItemList">
|
<button type="button" class="btn btn-link w-100" @click="showItems">
|
||||||
<i class="fas fa-plus-circle"></i> {{ addItemText }}
|
<i class="fas fa-plus-circle"></i> {{ addItemText }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -21,19 +21,26 @@
|
|||||||
type="text"
|
type="text"
|
||||||
data-input="true"
|
data-input="true"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
autocapitalize="default" autocorrect="ON"
|
autocapitalize="default"
|
||||||
|
autocorrect="ON"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:ref="'input-item-field-' + _uid"
|
|
||||||
v-model="search"
|
v-model="search"
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@keydown.enter="onItemCreate"
|
:ref="'input-item-field-' + _uid"
|
||||||
|
@keydown.enter="inputEnterEvent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="aka-select-menu-options">
|
<ul class="aka-select-menu-options">
|
||||||
<div class="aka-select-menu-option" v-for="(item, index) in sortedItems" :key="index" @click="onItemSeleted(index, item.id)">
|
<div
|
||||||
|
class="aka-select-menu-option"
|
||||||
|
v-for="(item, index) in sortedItems"
|
||||||
|
:key="index"
|
||||||
|
:class="isItemMatched ? 'highlightItem' : ''"
|
||||||
|
@click="onItemSelected(item)"
|
||||||
|
>
|
||||||
<div class="item-select w-100">
|
<div class="item-select w-100">
|
||||||
<div class="item-select-column item-select-info w-75">
|
<div class="item-select-column item-select-info w-75">
|
||||||
<b class="item-select-info-name"><span>{{ item.name }}</span></b>
|
<b class="item-select-info-name"><span>{{ item.name }}</span></b>
|
||||||
@ -202,12 +209,14 @@ export default {
|
|||||||
item_selected: false,
|
item_selected: false,
|
||||||
item_list: false,
|
item_list: false,
|
||||||
},
|
},
|
||||||
|
isItemMatched: false,
|
||||||
form: {},
|
form: {},
|
||||||
add_new: {
|
add_new: {
|
||||||
text: this.addNew.text,
|
text: this.addNew.text,
|
||||||
show: false,
|
show: false,
|
||||||
buttons: this.addNew.buttons,
|
buttons: this.addNew.buttons,
|
||||||
},
|
},
|
||||||
|
newItems: [],
|
||||||
add_new_html: '',
|
add_new_html: '',
|
||||||
money: {
|
money: {
|
||||||
decimal: this.currency.decimal_mark,
|
decimal: this.currency.decimal_mark,
|
||||||
@ -220,6 +229,10 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.setItemList(this.items);
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.dynamicCurrency.code != this.currency.code) {
|
if (this.dynamicCurrency.code != this.currency.code) {
|
||||||
if (!this.dynamicCurrency.decimal) {
|
if (!this.dynamicCurrency.decimal) {
|
||||||
@ -241,6 +254,8 @@ export default {
|
|||||||
setItemList(items) {
|
setItemList(items) {
|
||||||
this.item_list = [];
|
this.item_list = [];
|
||||||
|
|
||||||
|
this.search.length === 0 ? this.isItemMatched = false : {}
|
||||||
|
|
||||||
// Option set sort_option data
|
// Option set sort_option data
|
||||||
if (!Array.isArray(items)) {
|
if (!Array.isArray(items)) {
|
||||||
let index = 0;
|
let index = 0;
|
||||||
@ -277,7 +292,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onItemList() {
|
showItems() {
|
||||||
this.show.item_list = true;
|
this.show.item_list = true;
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
@ -286,8 +301,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onInput() {
|
onInput() {
|
||||||
|
this.isItemMatched = false;
|
||||||
//to optimize performance we kept the condition that checks for if search exists or not
|
//to optimize performance we kept the condition that checks for if search exists or not
|
||||||
if (!this.search) {
|
if (!this.search) {
|
||||||
|
this.isItemMatched = false; //to remove the style from matched item on input is cleared (option)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,10 +317,23 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.axios.get(url + '/common/items?search="' + this.search + '" limit:10')
|
this.fetchMatchedItems().then(() => this.item_list.length > 0 ? this.isItemMatched = true : this.isItemMatched = false );
|
||||||
|
|
||||||
|
this.$emit('input', this.search);
|
||||||
|
|
||||||
|
this.isItemMatched === true && this.search.length > 0 ? this.isItemMatched = true : this.isItemMatched = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
inputEnterEvent() {
|
||||||
|
this.isItemMatched
|
||||||
|
? this.onItemSelected()
|
||||||
|
: this.onItemCreate()
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchMatchedItems() {
|
||||||
|
await window.axios.get(url + '/common/items?search="' + this.search + '" limit:10')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.item_list = [];
|
this.item_list = [];
|
||||||
|
|
||||||
let items = response.data.data;
|
let items = response.data.data;
|
||||||
|
|
||||||
items.forEach(function (item, index) {
|
items.forEach(function (item, index) {
|
||||||
@ -320,41 +350,37 @@ export default {
|
|||||||
});
|
});
|
||||||
}, this);
|
}, this);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {});
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$emit('input', this.search);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onItemSeleted(index, item_id) {
|
onItemSelected(clickSelectedItem) {
|
||||||
let item = '';
|
let item;
|
||||||
|
const firstMatchedItem = this.item_list[0];
|
||||||
|
const isClickSelectedItem = clickSelectedItem ? true : false;
|
||||||
|
isClickSelectedItem ? item = clickSelectedItem : item = firstMatchedItem;
|
||||||
|
const indexeditem = { ...item, index: this.currentIndex };
|
||||||
|
|
||||||
this.item_list.forEach(function (item_list, item_index) {
|
this.addItem(indexeditem, 'oldItem');
|
||||||
if (item_list.id == item_id) {
|
},
|
||||||
item = item_list;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
addItem(item, itemType) {
|
||||||
this.selected_items.push(item);
|
this.selected_items.push(item);
|
||||||
|
|
||||||
this.$emit('item', item);
|
this.$emit('item', { item, itemType } );
|
||||||
this.$emit('items', this.selected_items);
|
this.$emit('items', this.selected_items);
|
||||||
|
|
||||||
this.show.item_selected = false;
|
this.show.item_selected = false;
|
||||||
this.show.item_list = false;
|
this.show.item_list = false;
|
||||||
|
|
||||||
this.search = '';
|
this.search = '';
|
||||||
|
|
||||||
// Set deault item list
|
// Set default item list
|
||||||
this.setItemList(this.items);
|
this.setItemList(this.items);
|
||||||
},
|
},
|
||||||
|
|
||||||
onItemCreate() {
|
onItemCreate() {
|
||||||
let index = Object.keys(this.item_list).length;
|
|
||||||
index++;
|
|
||||||
|
|
||||||
let item = {
|
let item = {
|
||||||
index: index,
|
index: this.currentIndex,
|
||||||
key: 0,
|
key: 0,
|
||||||
value: this.search,
|
value: this.search,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
@ -365,67 +391,9 @@ export default {
|
|||||||
tax_ids: [],
|
tax_ids: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
this.selected_items.push(item);
|
this.newItems.push(item);
|
||||||
|
|
||||||
this.$emit('item', item);
|
this.addItem(item, 'newItem');
|
||||||
this.$emit('items', this.selected_items);
|
|
||||||
|
|
||||||
this.setItemList(this.items);
|
|
||||||
|
|
||||||
this.show.item_selected = false;
|
|
||||||
this.show.item_list = false;
|
|
||||||
this.search = '';
|
|
||||||
|
|
||||||
/*
|
|
||||||
let add_new = this.add_new;
|
|
||||||
|
|
||||||
window.axios.get(this.createRoute)
|
|
||||||
.then(response => {
|
|
||||||
add_new.show = true;
|
|
||||||
add_new.html = response.data.html;
|
|
||||||
|
|
||||||
this.add_new_html = Vue.component('add-new-component', function (resolve, reject) {
|
|
||||||
resolve({
|
|
||||||
template: '<div><akaunting-modal-add-new :show="add_new.show" @submit="onSubmit" @cancel="onCancel" :buttons="add_new.buttons" :title="add_new.text" :is_component=true :message="add_new.html"></akaunting-modal-add-new></div>',
|
|
||||||
|
|
||||||
components: {
|
|
||||||
[Select.name]: Select,
|
|
||||||
[Option.name]: Option,
|
|
||||||
[OptionGroup.name]: OptionGroup,
|
|
||||||
[ColorPicker.name]: ColorPicker,
|
|
||||||
AkauntingModalAddNew,
|
|
||||||
AkauntingModal,
|
|
||||||
AkauntingMoney,
|
|
||||||
AkauntingRadioGroup,
|
|
||||||
AkauntingSelect,
|
|
||||||
AkauntingDate,
|
|
||||||
},
|
|
||||||
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
add_new: add_new,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
onSubmit(event) {
|
|
||||||
this.$emit('submit', event);
|
|
||||||
},
|
|
||||||
|
|
||||||
onCancel(event) {
|
|
||||||
this.$emit('cancel', event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e);
|
|
||||||
})
|
|
||||||
.finally(function () {
|
|
||||||
// always executed
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit(event) {
|
onSubmit(event) {
|
||||||
@ -552,14 +520,13 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
|
||||||
this.setItemList(this.items);
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
sortedItems() {
|
sortedItems() {
|
||||||
return this.sortItems();
|
return this.sortItems();
|
||||||
},
|
},
|
||||||
|
currentIndex() {
|
||||||
|
return this.$root.form.items.length;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@ -589,3 +556,9 @@ export default {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.highlightItem:first-child {
|
||||||
|
background-color: #F5F7FA;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
10
resources/assets/js/plugins/functions.js
vendored
10
resources/assets/js/plugins/functions.js
vendored
@ -15,4 +15,12 @@ function getQueryVariable(variable) {
|
|||||||
return(false);
|
return(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {getQueryVariable}
|
//This function wraps setTimeout function in a promise in order to display dom manipulations on root components asynchronously & fast
|
||||||
|
const setPromiseTimeout = time =>
|
||||||
|
new Promise(resolve =>
|
||||||
|
setTimeout(() =>
|
||||||
|
resolve(time)
|
||||||
|
, time)
|
||||||
|
);
|
||||||
|
|
||||||
|
export {getQueryVariable, setPromiseTimeout}
|
||||||
|
25
resources/assets/js/views/common/documents.js
vendored
25
resources/assets/js/views/common/documents.js
vendored
@ -9,6 +9,7 @@ require('./../../bootstrap');
|
|||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||||
|
import { setPromiseTimeout } from './../../plugins/functions';
|
||||||
|
|
||||||
import Global from './../../mixins/global';
|
import Global from './../../mixins/global';
|
||||||
|
|
||||||
@ -48,8 +49,8 @@ const app = new Vue({
|
|||||||
tax: false,
|
tax: false,
|
||||||
discounts: [],
|
discounts: [],
|
||||||
tax_id: [],
|
tax_id: [],
|
||||||
|
|
||||||
items: [],
|
items: [],
|
||||||
|
selected_items:[],
|
||||||
taxes: [],
|
taxes: [],
|
||||||
page_loaded: false,
|
page_loaded: false,
|
||||||
currencies: [],
|
currencies: [],
|
||||||
@ -92,11 +93,16 @@ const app = new Vue({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.currency_symbol.symbol = default_currency_symbol;
|
this.currency_symbol.symbol = default_currency_symbol;
|
||||||
}
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
onRefFocus(ref) {
|
||||||
|
let index = this.form.items.length - 1;
|
||||||
|
|
||||||
|
this.$refs['items-' + index + '-' + ref][0].focus();
|
||||||
|
},
|
||||||
|
|
||||||
onCalculateTotal() {
|
onCalculateTotal() {
|
||||||
let global_discount = parseFloat(this.form.discount);
|
let global_discount = parseFloat(this.form.discount);
|
||||||
let discount_total = 0;
|
let discount_total = 0;
|
||||||
@ -302,8 +308,14 @@ const app = new Vue({
|
|||||||
return totals_taxes;
|
return totals_taxes;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Select Item added form
|
|
||||||
onSelectedItem(item){
|
onSelectedItem(item){
|
||||||
|
this.onAddItem(item);
|
||||||
|
},
|
||||||
|
|
||||||
|
// addItem to list
|
||||||
|
onAddItem(payload) {
|
||||||
|
let { item, itemType } = payload;
|
||||||
|
let inputRef = `${itemType === 'newItem' ? 'name' : 'description'}`; // indication for which input to focus first
|
||||||
let total = 1 * item.price;
|
let total = 1 * item.price;
|
||||||
let item_taxes = [];
|
let item_taxes = [];
|
||||||
|
|
||||||
@ -351,6 +363,10 @@ const app = new Vue({
|
|||||||
// invoice_item_checkbox_sample: [],
|
// invoice_item_checkbox_sample: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
this.onRefFocus(inputRef);
|
||||||
|
}.bind(this), 100);
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
this.onCalculateTotal();
|
this.onCalculateTotal();
|
||||||
}.bind(this), 800);
|
}.bind(this), 800);
|
||||||
@ -499,6 +515,7 @@ const app = new Vue({
|
|||||||
methods: {
|
methods: {
|
||||||
onSubmit(event) {
|
onSubmit(event) {
|
||||||
this.form = event;
|
this.form = event;
|
||||||
|
|
||||||
this.form.response = {};
|
this.form.response = {};
|
||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="row save-buttons">
|
<div class="row save-buttons">
|
||||||
|
@ -32,7 +32,9 @@
|
|||||||
<span class="aka-text aka-text--body" tabindex="0" v-html="row.name" v-if="row.item_id"></span>
|
<span class="aka-text aka-text--body" tabindex="0" v-html="row.name" v-if="row.item_id"></span>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
@stack('name_input_start')
|
@stack('name_input_start')
|
||||||
<input type="text"
|
<input
|
||||||
|
type="text"
|
||||||
|
:ref="'items-' + index + '-name'"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
:name="'items.' + index + '.name'"
|
:name="'items.' + index + '.name'"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@ -40,8 +42,7 @@
|
|||||||
data-item="name"
|
data-item="name"
|
||||||
v-model="row.name"
|
v-model="row.name"
|
||||||
@input="onBindingItemField(index, 'name')"
|
@input="onBindingItemField(index, 'name')"
|
||||||
@change="form.errors.clear('items.' + index + '.name')">
|
@change="form.errors.clear('items.' + index + '.name')"/>
|
||||||
|
|
||||||
<div class="invalid-feedback d-block"
|
<div class="invalid-feedback d-block"
|
||||||
v-if="form.errors.has('items.' + index + '.name')"
|
v-if="form.errors.has('items.' + index + '.name')"
|
||||||
v-html="form.errors.get('items.' + index + '.name')">
|
v-html="form.errors.get('items.' + index + '.name')">
|
||||||
@ -57,6 +58,7 @@
|
|||||||
@if (!$hideDescription)
|
@if (!$hideDescription)
|
||||||
<textarea
|
<textarea
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
:ref="'items-' + index + '-description'"
|
||||||
placeholder="{{ trans('items.enter_item_description') }}"
|
placeholder="{{ trans('items.enter_item_description') }}"
|
||||||
style="height: 46px; overflow: hidden;"
|
style="height: 46px; overflow: hidden;"
|
||||||
:name="'items.' + index + '.description'"
|
:name="'items.' + index + '.description'"
|
||||||
@ -79,6 +81,7 @@
|
|||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
|
:ref="'items-' + index + '-quantity'"
|
||||||
class="form-control text-center p-0 input-number-disabled"
|
class="form-control text-center p-0 input-number-disabled"
|
||||||
:name="'items.' + index + '.quantity'"
|
:name="'items.' + index + '.quantity'"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
@ -87,7 +90,6 @@
|
|||||||
v-model="row.quantity"
|
v-model="row.quantity"
|
||||||
@input="onCalculateTotal"
|
@input="onCalculateTotal"
|
||||||
@change="form.errors.clear('items.' + index + '.quantity')">
|
@change="form.errors.clear('items.' + index + '.quantity')">
|
||||||
|
|
||||||
<div class="invalid-feedback d-block"
|
<div class="invalid-feedback d-block"
|
||||||
v-if="form.errors.has('items.' + index + '.quantity')"
|
v-if="form.errors.has('items.' + index + '.quantity')"
|
||||||
v-html="form.errors.get('items.' + index + '.quantity')">
|
v-html="form.errors.get('items.' + index + '.quantity')">
|
||||||
|
@ -74,6 +74,7 @@
|
|||||||
@if (isset($attributes['row-input']))
|
@if (isset($attributes['row-input']))
|
||||||
:row-input="{{ $attributes['row-input'] }}"
|
:row-input="{{ $attributes['row-input'] }}"
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
></akaunting-money>
|
></akaunting-money>
|
||||||
|
|
||||||
@stack($name . '_input_end')
|
@stack($name . '_input_end')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user