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--------*/
|
||||
/*--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: ' *';
|
||||
color: #ef3232;
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="aka-box-content">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,7 +198,6 @@ export default {
|
||||
default: () => [],
|
||||
description: 'List of Contacts'
|
||||
},
|
||||
|
||||
addNew: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
|
@ -66,7 +66,7 @@ export default {
|
||||
|
||||
period: {
|
||||
type: [Number, String],
|
||||
default: 0,
|
||||
default: "0",
|
||||
description: "Payment period"
|
||||
},
|
||||
|
||||
@ -145,7 +145,6 @@ export default {
|
||||
if (this.model) {
|
||||
this.real_model = this.model;
|
||||
}
|
||||
|
||||
this.$emit('interface', this.real_model);
|
||||
},
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div :id="'select-item-button-' + _uid" class="product-select">
|
||||
<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 }}
|
||||
</button>
|
||||
</div>
|
||||
@ -21,19 +21,26 @@
|
||||
type="text"
|
||||
data-input="true"
|
||||
class="form-control"
|
||||
autocapitalize="default" autocorrect="ON"
|
||||
autocapitalize="default"
|
||||
autocorrect="ON"
|
||||
:placeholder="placeholder"
|
||||
:ref="'input-item-field-' + _uid"
|
||||
v-model="search"
|
||||
@input="onInput"
|
||||
@keydown.enter="onItemCreate"
|
||||
:ref="'input-item-field-' + _uid"
|
||||
@keydown.enter="inputEnterEvent"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<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-column item-select-info w-75">
|
||||
<b class="item-select-info-name"><span>{{ item.name }}</span></b>
|
||||
@ -202,12 +209,14 @@ export default {
|
||||
item_selected: false,
|
||||
item_list: false,
|
||||
},
|
||||
isItemMatched: false,
|
||||
form: {},
|
||||
add_new: {
|
||||
text: this.addNew.text,
|
||||
show: false,
|
||||
buttons: this.addNew.buttons,
|
||||
},
|
||||
newItems: [],
|
||||
add_new_html: '',
|
||||
money: {
|
||||
decimal: this.currency.decimal_mark,
|
||||
@ -220,6 +229,10 @@ export default {
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.setItemList(this.items);
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.dynamicCurrency.code != this.currency.code) {
|
||||
if (!this.dynamicCurrency.decimal) {
|
||||
@ -241,6 +254,8 @@ export default {
|
||||
setItemList(items) {
|
||||
this.item_list = [];
|
||||
|
||||
this.search.length === 0 ? this.isItemMatched = false : {}
|
||||
|
||||
// Option set sort_option data
|
||||
if (!Array.isArray(items)) {
|
||||
let index = 0;
|
||||
@ -277,7 +292,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onItemList() {
|
||||
showItems() {
|
||||
this.show.item_list = true;
|
||||
|
||||
setTimeout(function() {
|
||||
@ -286,8 +301,10 @@ export default {
|
||||
},
|
||||
|
||||
onInput() {
|
||||
this.isItemMatched = false;
|
||||
//to optimize performance we kept the condition that checks for if search exists or not
|
||||
if (!this.search) {
|
||||
this.isItemMatched = false; //to remove the style from matched item on input is cleared (option)
|
||||
return;
|
||||
}
|
||||
|
||||
@ -300,61 +317,70 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
window.axios.get(url + '/common/items?search="' + this.search + '" limit:10')
|
||||
.then(response => {
|
||||
this.item_list = [];
|
||||
|
||||
let items = response.data.data;
|
||||
|
||||
items.forEach(function (item, index) {
|
||||
this.item_list.push({
|
||||
index: index,
|
||||
key: item.id,
|
||||
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
||||
type: this.type,
|
||||
id: item.id,
|
||||
name: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
||||
description: (item.description) ? item.description : '',
|
||||
price: (item.price) ? item.price : (this.price == 'purchase_price') ? item.purchase_price : item.sale_price,
|
||||
tax_ids: (item.tax_ids) ? item.tax_ids : [],
|
||||
});
|
||||
}, this);
|
||||
})
|
||||
.catch(error => {
|
||||
|
||||
});
|
||||
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;
|
||||
},
|
||||
|
||||
onItemSeleted(index, item_id) {
|
||||
let item = '';
|
||||
inputEnterEvent() {
|
||||
this.isItemMatched
|
||||
? this.onItemSelected()
|
||||
: this.onItemCreate()
|
||||
},
|
||||
|
||||
this.item_list.forEach(function (item_list, item_index) {
|
||||
if (item_list.id == item_id) {
|
||||
item = item_list;
|
||||
}
|
||||
});
|
||||
async fetchMatchedItems() {
|
||||
await window.axios.get(url + '/common/items?search="' + this.search + '" limit:10')
|
||||
.then(response => {
|
||||
this.item_list = [];
|
||||
let items = response.data.data;
|
||||
|
||||
items.forEach(function (item, index) {
|
||||
this.item_list.push({
|
||||
index: index,
|
||||
key: item.id,
|
||||
value: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
||||
type: this.type,
|
||||
id: item.id,
|
||||
name: (item.title) ? item.title : (item.display_name) ? item.display_name : item.name,
|
||||
description: (item.description) ? item.description : '',
|
||||
price: (item.price) ? item.price : (this.price == 'purchase_price') ? item.purchase_price : item.sale_price,
|
||||
tax_ids: (item.tax_ids) ? item.tax_ids : [],
|
||||
});
|
||||
}, this);
|
||||
})
|
||||
.catch(error => {});
|
||||
},
|
||||
|
||||
onItemSelected(clickSelectedItem) {
|
||||
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.addItem(indexeditem, 'oldItem');
|
||||
},
|
||||
|
||||
addItem(item, itemType) {
|
||||
this.selected_items.push(item);
|
||||
|
||||
this.$emit('item', item);
|
||||
this.$emit('item', { item, itemType } );
|
||||
this.$emit('items', this.selected_items);
|
||||
|
||||
this.show.item_selected = false;
|
||||
this.show.item_list = false;
|
||||
|
||||
this.search = '';
|
||||
|
||||
// Set deault item list
|
||||
// Set default item list
|
||||
this.setItemList(this.items);
|
||||
},
|
||||
|
||||
onItemCreate() {
|
||||
let index = Object.keys(this.item_list).length;
|
||||
index++;
|
||||
|
||||
let item = {
|
||||
index: index,
|
||||
index: this.currentIndex,
|
||||
key: 0,
|
||||
value: this.search,
|
||||
type: this.type,
|
||||
@ -365,67 +391,9 @@ export default {
|
||||
tax_ids: [],
|
||||
};
|
||||
|
||||
this.selected_items.push(item);
|
||||
this.newItems.push(item);
|
||||
|
||||
this.$emit('item', item);
|
||||
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
|
||||
});
|
||||
*/
|
||||
this.addItem(item, 'newItem');
|
||||
},
|
||||
|
||||
onSubmit(event) {
|
||||
@ -552,14 +520,13 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
this.setItemList(this.items);
|
||||
},
|
||||
|
||||
computed: {
|
||||
sortedItems() {
|
||||
return this.sortItems();
|
||||
},
|
||||
currentIndex() {
|
||||
return this.$root.form.items.length;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
@ -589,3 +556,9 @@ export default {
|
||||
},
|
||||
};
|
||||
</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);
|
||||
}
|
||||
|
||||
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}
|
||||
|
27
resources/assets/js/views/common/documents.js
vendored
27
resources/assets/js/views/common/documents.js
vendored
@ -9,6 +9,7 @@ require('./../../bootstrap');
|
||||
import Vue from 'vue';
|
||||
|
||||
import DashboardPlugin from './../../plugins/dashboard-plugin';
|
||||
import { setPromiseTimeout } from './../../plugins/functions';
|
||||
|
||||
import Global from './../../mixins/global';
|
||||
|
||||
@ -48,8 +49,8 @@ const app = new Vue({
|
||||
tax: false,
|
||||
discounts: [],
|
||||
tax_id: [],
|
||||
|
||||
items: [],
|
||||
selected_items:[],
|
||||
taxes: [],
|
||||
page_loaded: false,
|
||||
currencies: [],
|
||||
@ -92,11 +93,16 @@ const app = new Vue({
|
||||
}
|
||||
|
||||
this.currency_symbol.symbol = default_currency_symbol;
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onRefFocus(ref) {
|
||||
let index = this.form.items.length - 1;
|
||||
|
||||
this.$refs['items-' + index + '-' + ref][0].focus();
|
||||
},
|
||||
|
||||
onCalculateTotal() {
|
||||
let global_discount = parseFloat(this.form.discount);
|
||||
let discount_total = 0;
|
||||
@ -302,8 +308,14 @@ const app = new Vue({
|
||||
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 item_taxes = [];
|
||||
|
||||
@ -351,6 +363,10 @@ const app = new Vue({
|
||||
// invoice_item_checkbox_sample: [],
|
||||
});
|
||||
|
||||
setTimeout(function() {
|
||||
this.onRefFocus(inputRef);
|
||||
}.bind(this), 100);
|
||||
|
||||
setTimeout(function() {
|
||||
this.onCalculateTotal();
|
||||
}.bind(this), 800);
|
||||
@ -499,6 +515,7 @@ const app = new Vue({
|
||||
methods: {
|
||||
onSubmit(event) {
|
||||
this.form = event;
|
||||
|
||||
this.form.response = {};
|
||||
|
||||
this.loading = true;
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-footer">
|
||||
<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>
|
||||
<div v-else>
|
||||
@stack('name_input_start')
|
||||
<input type="text"
|
||||
<input
|
||||
type="text"
|
||||
:ref="'items-' + index + '-name'"
|
||||
class="form-control"
|
||||
:name="'items.' + index + '.name'"
|
||||
autocomplete="off"
|
||||
@ -40,8 +42,7 @@
|
||||
data-item="name"
|
||||
v-model="row.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"
|
||||
v-if="form.errors.has('items.' + index + '.name')"
|
||||
v-html="form.errors.get('items.' + index + '.name')">
|
||||
@ -57,6 +58,7 @@
|
||||
@if (!$hideDescription)
|
||||
<textarea
|
||||
class="form-control"
|
||||
:ref="'items-' + index + '-description'"
|
||||
placeholder="{{ trans('items.enter_item_description') }}"
|
||||
style="height: 46px; overflow: hidden;"
|
||||
:name="'items.' + index + '.description'"
|
||||
@ -79,6 +81,7 @@
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
:ref="'items-' + index + '-quantity'"
|
||||
class="form-control text-center p-0 input-number-disabled"
|
||||
:name="'items.' + index + '.quantity'"
|
||||
autocomplete="off"
|
||||
@ -87,7 +90,6 @@
|
||||
v-model="row.quantity"
|
||||
@input="onCalculateTotal"
|
||||
@change="form.errors.clear('items.' + index + '.quantity')">
|
||||
|
||||
<div class="invalid-feedback d-block"
|
||||
v-if="form.errors.has('items.' + index + '.quantity')"
|
||||
v-html="form.errors.get('items.' + index + '.quantity')">
|
||||
|
@ -74,6 +74,7 @@
|
||||
@if (isset($attributes['row-input']))
|
||||
:row-input="{{ $attributes['row-input'] }}"
|
||||
@endif
|
||||
|
||||
></akaunting-money>
|
||||
|
||||
@stack($name . '_input_end')
|
||||
|
Loading…
x
Reference in New Issue
Block a user