Expand the "product lookup" component to include autocomplete
This commit is contained in:
parent
441a6e5e0c
commit
a812181466
|
@ -531,33 +531,10 @@
|
||||||
<p class="label control">
|
<p class="label control">
|
||||||
Product
|
Product
|
||||||
</p>
|
</p>
|
||||||
<b-field :expanded="!productUUID">
|
<tailbone-product-lookup ref="productLookup"
|
||||||
<tailbone-autocomplete ref="productAutocomplete"
|
:selected-product="selectedProduct"
|
||||||
v-model="productUUID"
|
@selected="productLookupSelected">
|
||||||
placeholder="Enter UPC or brand, description etc."
|
</tailbone-product-lookup>
|
||||||
:assigned-label="productDisplay"
|
|
||||||
serviceUrl="${url('{}.product_autocomplete'.format(route_prefix))}"
|
|
||||||
@input="productChanged">
|
|
||||||
</tailbone-autocomplete>
|
|
||||||
</b-field>
|
|
||||||
|
|
||||||
<b-button type="is-primary"
|
|
||||||
v-if="!productUUID"
|
|
||||||
@click="productFullLookup()"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="search">
|
|
||||||
Full Lookup
|
|
||||||
</b-button>
|
|
||||||
|
|
||||||
<b-button v-if="productUUID"
|
|
||||||
type="is-primary"
|
|
||||||
tag="a" target="_blank"
|
|
||||||
:href="productURL"
|
|
||||||
:disabled="!productURL"
|
|
||||||
icon-pack="fas"
|
|
||||||
icon-left="external-link-alt">
|
|
||||||
View Product
|
|
||||||
</b-button>
|
|
||||||
</b-field>
|
</b-field>
|
||||||
|
|
||||||
<div v-if="productUUID">
|
<div v-if="productUUID">
|
||||||
|
@ -565,7 +542,6 @@
|
||||||
<div class="is-pulled-right has-text-centered">
|
<div class="is-pulled-right has-text-centered">
|
||||||
<img :src="productImageURL"
|
<img :src="productImageURL"
|
||||||
style="max-height: 150px; max-width: 150px; "/>
|
style="max-height: 150px; max-width: 150px; "/>
|
||||||
## <p>{{ productKey }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<b-field grouped>
|
<b-field grouped>
|
||||||
|
@ -957,11 +933,6 @@
|
||||||
</b-modal>
|
</b-modal>
|
||||||
% endif
|
% endif
|
||||||
|
|
||||||
<tailbone-product-lookup ref="productLookup"
|
|
||||||
@canceled="productLookupCanceled"
|
|
||||||
@selected="productLookupSelected">
|
|
||||||
</tailbone-product-lookup>
|
|
||||||
|
|
||||||
% if allow_past_item_reorder:
|
% if allow_past_item_reorder:
|
||||||
<b-modal :active.sync="pastItemsShowDialog">
|
<b-modal :active.sync="pastItemsShowDialog">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@ -1258,6 +1229,7 @@
|
||||||
pastItemsSelected: null,
|
pastItemsSelected: null,
|
||||||
% endif
|
% endif
|
||||||
productIsKnown: true,
|
productIsKnown: true,
|
||||||
|
selectedProduct: null,
|
||||||
productUUID: null,
|
productUUID: null,
|
||||||
productDisplay: null,
|
productDisplay: null,
|
||||||
productKey: null,
|
productKey: null,
|
||||||
|
@ -1544,6 +1516,18 @@
|
||||||
this.$refs.contactAutocomplete.clearSelection()
|
this.$refs.contactAutocomplete.clearSelection()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
productIsKnown(newval, oldval) {
|
||||||
|
// TODO: seems like this should be better somehow?
|
||||||
|
// e.g. maybe we should not be clearing *everything*
|
||||||
|
// in case user accidentally clicks, and then clicks
|
||||||
|
// "is known" again? and if we *should* clear all,
|
||||||
|
// why does that require 2 steps?
|
||||||
|
if (!newval) {
|
||||||
|
this.selectedProduct = null
|
||||||
|
this.clearProduct()
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
@ -1894,20 +1878,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
productFullLookup() {
|
|
||||||
this.showingItemDialog = false
|
|
||||||
let term = this.$refs.productAutocomplete.getUserInput()
|
|
||||||
this.$refs.productLookup.showDialog(term)
|
|
||||||
},
|
|
||||||
|
|
||||||
productLookupCanceled() {
|
|
||||||
this.showingItemDialog = true
|
|
||||||
},
|
|
||||||
|
|
||||||
productLookupSelected(selected) {
|
productLookupSelected(selected) {
|
||||||
|
// TODO: this still is a hack somehow, am sure of it.
|
||||||
|
// need to clean this up at some point
|
||||||
|
this.selectedProduct = selected
|
||||||
this.clearProduct()
|
this.clearProduct()
|
||||||
this.productChanged(selected.uuid)
|
this.productChanged(selected)
|
||||||
this.showingItemDialog = true
|
|
||||||
},
|
},
|
||||||
|
|
||||||
copyPendingProductAttrs(from, to) {
|
copyPendingProductAttrs(from, to) {
|
||||||
|
@ -1930,6 +1906,7 @@
|
||||||
this.customerPanelOpen = false
|
this.customerPanelOpen = false
|
||||||
this.editingItem = null
|
this.editingItem = null
|
||||||
this.productIsKnown = true
|
this.productIsKnown = true
|
||||||
|
this.selectedProduct = null
|
||||||
this.productUUID = null
|
this.productUUID = null
|
||||||
this.productDisplay = null
|
this.productDisplay = null
|
||||||
this.productKey = null
|
this.productKey = null
|
||||||
|
@ -1962,7 +1939,7 @@
|
||||||
this.itemDialogTabIndex = 0
|
this.itemDialogTabIndex = 0
|
||||||
this.showingItemDialog = true
|
this.showingItemDialog = true
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.productAutocomplete.focus()
|
this.$refs.productLookup.focus()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2027,6 +2004,16 @@
|
||||||
this.productIsKnown = !!row.product_uuid
|
this.productIsKnown = !!row.product_uuid
|
||||||
this.productUUID = row.product_uuid
|
this.productUUID = row.product_uuid
|
||||||
|
|
||||||
|
if (row.product_uuid) {
|
||||||
|
this.selectedProduct = {
|
||||||
|
uuid: row.product_uuid,
|
||||||
|
full_description: row.product_full_description,
|
||||||
|
url: row.product_url,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedProduct = null
|
||||||
|
}
|
||||||
|
|
||||||
// nb. must construct new object before updating data
|
// nb. must construct new object before updating data
|
||||||
// (otherwise vue does not notice the changes?)
|
// (otherwise vue does not notice the changes?)
|
||||||
let pending = {}
|
let pending = {}
|
||||||
|
@ -2131,11 +2118,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
productChanged(uuid) {
|
productChanged(product) {
|
||||||
if (uuid) {
|
if (product) {
|
||||||
let params = {
|
let params = {
|
||||||
action: 'get_product_info',
|
action: 'get_product_info',
|
||||||
uuid: uuid,
|
uuid: product.uuid,
|
||||||
}
|
}
|
||||||
// nb. it is possible for the handler to "swap"
|
// nb. it is possible for the handler to "swap"
|
||||||
// the product selection, i.e. user chooses a "per
|
// the product selection, i.e. user chooses a "per
|
||||||
|
@ -2144,6 +2131,8 @@
|
||||||
// received above is the correct one, but just use
|
// received above is the correct one, but just use
|
||||||
// whatever came back from handler
|
// whatever came back from handler
|
||||||
this.submitBatchData(params, response => {
|
this.submitBatchData(params, response => {
|
||||||
|
this.selectedProduct = response.data
|
||||||
|
|
||||||
this.productUUID = response.data.uuid
|
this.productUUID = response.data.uuid
|
||||||
this.productKey = response.data.key
|
this.productKey = response.data.key
|
||||||
this.productDisplay = response.data.full_description
|
this.productDisplay = response.data.full_description
|
||||||
|
|
|
@ -2,8 +2,49 @@
|
||||||
|
|
||||||
<%def name="tailbone_product_lookup_template()">
|
<%def name="tailbone_product_lookup_template()">
|
||||||
<script type="text/x-template" id="tailbone-product-lookup-template">
|
<script type="text/x-template" id="tailbone-product-lookup-template">
|
||||||
<div>
|
<div style="width: 100%;">
|
||||||
<b-modal :active.sync="showingDialog">
|
|
||||||
|
<b-field grouped>
|
||||||
|
|
||||||
|
<b-field :expanded="!selectedProduct">
|
||||||
|
<b-autocomplete ref="productAutocomplete"
|
||||||
|
v-if="!selectedProduct"
|
||||||
|
v-model="autocompleteValue"
|
||||||
|
placeholder="Enter UPC or brand, description etc."
|
||||||
|
:data="autocompleteOptions"
|
||||||
|
field="value"
|
||||||
|
:custom-formatter="option => option.label"
|
||||||
|
@typing="getAutocompleteOptions"
|
||||||
|
@select="autocompleteSelected"
|
||||||
|
style="width: 100%;">
|
||||||
|
</b-autocomplete>
|
||||||
|
<b-button v-if="selectedProduct"
|
||||||
|
@click="clearSelection(true)">
|
||||||
|
{{ selectedProduct.full_description }}
|
||||||
|
</b-button>
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-button type="is-primary"
|
||||||
|
v-if="!selectedProduct"
|
||||||
|
@click="lookupInit()"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="search">
|
||||||
|
Full Lookup
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
<b-button v-if="selectedProduct"
|
||||||
|
type="is-primary"
|
||||||
|
tag="a" target="_blank"
|
||||||
|
:href="selectedProduct.url"
|
||||||
|
:disabled="!selectedProduct.url"
|
||||||
|
icon-pack="fas"
|
||||||
|
icon-left="external-link-alt">
|
||||||
|
View Product
|
||||||
|
</b-button>
|
||||||
|
|
||||||
|
</b-field>
|
||||||
|
|
||||||
|
<b-modal :active.sync="lookupShowDialog">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
|
||||||
|
@ -157,6 +198,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</b-modal>
|
</b-modal>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
||||||
|
@ -166,9 +208,17 @@
|
||||||
|
|
||||||
const TailboneProductLookup = {
|
const TailboneProductLookup = {
|
||||||
template: '#tailbone-product-lookup-template',
|
template: '#tailbone-product-lookup-template',
|
||||||
|
props: {
|
||||||
|
selectedProduct: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showingDialog: false,
|
autocompleteValue: '',
|
||||||
|
autocompleteOptions: [],
|
||||||
|
|
||||||
|
lookupShowDialog: false,
|
||||||
|
|
||||||
searchTerm: null,
|
searchTerm: null,
|
||||||
searchTermLastUsed: null,
|
searchTermLastUsed: null,
|
||||||
|
@ -187,23 +237,67 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
showDialog(term) {
|
focus() {
|
||||||
|
if (!this.selectedProduct) {
|
||||||
|
this.$refs.productAutocomplete.focus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearSelection(focus) {
|
||||||
|
|
||||||
|
// clear data
|
||||||
|
this.autocompleteValue = ''
|
||||||
|
this.$emit('selected', null)
|
||||||
|
|
||||||
|
// maybe set focus to our (autocomplete) component
|
||||||
|
if (focus) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getAutocompleteOptions: debounce(function (entry) {
|
||||||
|
|
||||||
|
// since the `@typing` event from buefy component does not
|
||||||
|
// "self-regulate" in any way, we a) use `debounce` above,
|
||||||
|
// but also b) skip the search unless we have at least 3
|
||||||
|
// characters of input from user
|
||||||
|
if (entry.length < 3) {
|
||||||
|
this.data = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// and perform the search
|
||||||
|
let url = '${url(f'{route_prefix}.product_autocomplete')}'
|
||||||
|
this.$http.get(url + '?term=' + encodeURIComponent(entry))
|
||||||
|
.then(({ data }) => {
|
||||||
|
this.autocompleteOptions = data
|
||||||
|
}).catch((error) => {
|
||||||
|
this.autocompleteOptions = []
|
||||||
|
throw error
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
autocompleteSelected(option) {
|
||||||
|
this.$emit('selected', {
|
||||||
|
uuid: option.value,
|
||||||
|
full_description: option.label,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
lookupInit() {
|
||||||
this.searchResultSelected = null
|
this.searchResultSelected = null
|
||||||
|
this.lookupShowDialog = true
|
||||||
|
|
||||||
if (term !== undefined) {
|
this.$nextTick(() => {
|
||||||
this.searchTerm = term
|
|
||||||
// perform search if invoked with new term
|
this.searchTerm = this.autocompleteValue
|
||||||
if (term != this.searchTermLastUsed) {
|
if (this.searchTerm != this.searchTermLastUsed) {
|
||||||
this.searchTermLastUsed = null
|
this.searchTermLastUsed = null
|
||||||
this.performSearch()
|
this.performSearch()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.searchTerm = this.searchTermLastUsed
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showingDialog = true
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$refs.searchTermInput.focus()
|
this.$refs.searchTermInput.focus()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -214,17 +308,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelDialog() {
|
|
||||||
this.searchResultSelected = null
|
|
||||||
this.showingDialog = false
|
|
||||||
this.$emit('canceled')
|
|
||||||
},
|
|
||||||
|
|
||||||
selectResult() {
|
|
||||||
this.showingDialog = false
|
|
||||||
this.$emit('selected', this.searchResultSelected)
|
|
||||||
},
|
|
||||||
|
|
||||||
performSearch() {
|
performSearch() {
|
||||||
if (this.searchResultsLoading) {
|
if (this.searchResultsLoading) {
|
||||||
return
|
return
|
||||||
|
@ -255,6 +338,16 @@
|
||||||
this.searchResultsLoading = false
|
this.searchResultsLoading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectResult() {
|
||||||
|
this.lookupShowDialog = false
|
||||||
|
this.$emit('selected', this.searchResultSelected)
|
||||||
|
},
|
||||||
|
|
||||||
|
cancelDialog() {
|
||||||
|
this.searchResultSelected = null
|
||||||
|
this.lookupShowDialog = false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue