Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 enhance: Indexed DB integration to sync products over counters and improve frontend products search #126

Open
wants to merge 34 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0de5dec
enhance: Frontend products search improvement
devAsadNur Sep 20, 2022
4c5571a
Added ProductsLog class for WPDB CRUD operations
devAsadNur Sep 26, 2022
8b3e12f
Created REST API for product log CRUD operations
devAsadNur Sep 28, 2022
d9ebc1a
Added frontend manual product refresh mechanism and linked up Idexed …
devAsadNur Oct 3, 2022
b49af7d
Added installer class and created database tables
devAsadNur Oct 3, 2022
2740026
Adding mechanism for removing left over product logs by cron schedule
devAsadNur Oct 4, 2022
3a83eea
Added upgrader for product log tables creation and cron scheduling
devAsadNur Oct 4, 2022
c59fc16
Added capabilities security check toProductsLog database CRUD methods
devAsadNur Oct 4, 2022
6958e22
Replaced cron with action scheduler for clean up product logs
devAsadNur Oct 7, 2022
ba8accf
Reverted accidental changes to the
devAsadNur Oct 7, 2022
6daf361
Refactored products log database methods and API
devAsadNur Oct 14, 2022
b675ef6
Removed pagination from frontend product log fetcher and refactored a…
devAsadNur Oct 17, 2022
621fc13
Integration of updating products Indexed DB automatically using Heart…
devAsadNur Oct 17, 2022
8eaf467
Added toast popup for refreashing product status
devAsadNur Oct 17, 2022
7eb58db
Integrated product refresh button
devAsadNur Oct 18, 2022
b7c6b2e
Stored products log fetching timestamp record to local storage and fi…
devAsadNur Oct 19, 2022
e5a744c
Added tooltip for the Refresh Products button and fixed products name…
devAsadNur Oct 19, 2022
4f9f226
Fixed console error at product adding to the cart. Set Heartbeat API …
devAsadNur Oct 21, 2022
47181e4
Removed WC action scheduler implementation
devAsadNur Oct 21, 2022
210059f
Hidden zero stock and zero price product from search result. Product …
devAsadNur Oct 24, 2022
61333b1
fix: typo of serachInput
devAsadNur May 10, 2023
182feef
refactor: Optimized some codes of product searching
devAsadNur May 10, 2023
ef6d01c
update: Installer and upgrader database table creation SQL based on P…
devAsadNur May 10, 2023
fcbfc82
update: Upgrader version to 1.2.7 from 1.2.5
devAsadNur May 10, 2023
52528f8
Merge branch 'develop' into enhance/frontend-products-search-improvement
devAsadNur May 10, 2023
09f0bce
update: Products IndexedDB database errors
devAsadNur May 25, 2023
c2acc01
Added args to the ProductsLogController API get method
devAsadNur May 28, 2024
96fc7f8
fix: Removed some unnecessery codes form Home component
devAsadNur May 28, 2024
070741b
update: Upgrader version to 1.3.0
devAsadNur May 28, 2024
19bb681
Merge remote-tracking branch 'origin/develop' into enhance/frontend-p…
devAsadNur May 28, 2024
397a5c9
fix: Frontend product title search was not working
devAsadNur Jun 3, 2024
3e76019
Merge branch 'develop' into enhance/frontend-products-search-improvement
devAsadNur Sep 24, 2024
8e9d7c6
enhance: Improved frontend product search
devAsadNur Sep 24, 2024
e78a3cc
enhance: Improved POS product searching mechanism
devAsadNur Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions assets/less/style.less
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,22 @@ div:focus, span:focus, button:focus, input:focus {
}
}

.refresh-products-tooltip-wrapper {
.tooltip-inner {
padding: 5px 10px !important;
background-color: rgba(0, 0, 0, .6) !important;

.refresh-products-tooltip-text {
color: #fff;
font-size: 13px !important;
}
}

.tooltip-arrow {
border-color: rgba(0, 0, 0, .6) !important;
}
}

.wepos-multiselect {
min-height: 35px !important;
font-size: 13px !important;
Expand Down
20 changes: 10 additions & 10 deletions assets/src/frontend/components/CustomerSearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</g>
</g>
</svg>
<input type="text" ref="customerSearch" name="customer_search" id="customer-search" :placeholder="__( 'Search customer', 'wepos' )" v-model="serachInput" @focus.prevent="triggerFocus" @keyup="searchCustomer">
<input type="text" ref="customerSearch" name="customer_search" id="customer-search" :placeholder="__( 'Search customer', 'wepos' )" v-model="searchInput" @focus.prevent="triggerFocus" @keyup="searchCustomer">
<span class="add-new-customer flaticon-add" @click.prevent="addNewCustomer()"></span>
<div class="search-result" v-show="showCustomerResults">
<div v-if="customers.length">
Expand Down Expand Up @@ -181,7 +181,7 @@ export default {
phone: '',
},
showCustomerResults: false,
serachInput: '',
searchInput: '',
showNewCustomerModal: false,
stateList: [],
selectedState: null,
Expand Down Expand Up @@ -226,7 +226,7 @@ export default {
},

'orderdata.customer_id'(newVal) {
this.serachInput = newVal ? this.orderdata.billing.first_name + ' ' + this.orderdata.billing.last_name : '';
this.searchInput = newVal ? this.orderdata.billing.first_name + ' ' + this.orderdata.billing.last_name : '';
}

},
Expand All @@ -238,7 +238,7 @@ export default {
},
searchClose() {
this.showCustomerResults = false;
this.serachInput = '';
this.searchInput = '';
this.showNewCustomerModal= false;
this.$refs.customerSearch.blur();
},
Expand Down Expand Up @@ -278,8 +278,8 @@ export default {
this.showNewCustomerModal = false;
},
searchCustomer() {
if ( this.serachInput ) {
wepos.api.get( wepos.rest.root + wepos.rest.posversion + '/customers?search=' + this.serachInput )
if ( this.searchInput ) {
wepos.api.get( wepos.rest.root + wepos.rest.posversion + '/customers?search=' + this.searchInput )
.done(response => {
this.customers = response;
});
Expand All @@ -289,7 +289,7 @@ export default {
},
selectCustomer( customer ) {
this.$emit( 'onCustomerSelected', customer );
this.serachInput = customer.first_name + ' ' + customer.last_name;
this.searchInput = customer.first_name + ' ' + customer.last_name;
this.showCustomerResults = false;
},
createCustomer() {
Expand Down Expand Up @@ -318,7 +318,7 @@ export default {

wepos.api.post( wepos.rest.root + wepos.rest.posversion + '/customers', customerData )
.done(response => {
this.serachInput = response.first_name + ' ' + response.last_name;
this.searchInput = response.first_name + ' ' + response.last_name;
this.$emit( 'onCustomerSelected', response );
$contentWrap.unblock();
this.closeNewCustomerModal();
Expand Down Expand Up @@ -369,13 +369,13 @@ export default {
},
created() {
this.eventBus.$on( 'emptycart', ( orderdata ) => {
this.serachInput = '';
this.searchInput = '';
} );

var orderdata = JSON.parse( localStorage.getItem( 'orderdata' ) );

if ( orderdata.customer_id != 'undefined' && orderdata.customer_id != 0 ) {
this.serachInput = orderdata.billing.first_name + ' ' + orderdata.billing.last_name;
this.searchInput = orderdata.billing.first_name + ' ' + orderdata.billing.last_name;
}
}
};
Expand Down
128 changes: 123 additions & 5 deletions assets/src/frontend/components/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@
</template>
</multiselect>
</div>

<div class="refresh-products">
<v-popover popover-base-class="refresh-products-tooltip-wrapper tooltip popover" placement="auto" trigger="hover">
<span class="refresh-icon list-view" :class="{ active: productLogsLoading }" @click.prevent="refreshProducts">
<svg xmlns="http://www.w3.org/2000/svg" data-name="Isolation Mode" viewBox="0 0 24 24" width="14" height="14"><path d="M12 2.99a9.03 9.03 0 0 1 6.36 2.65l-2.37 2.37h5.83a1.15 1.15 0 0 0 1.14-1.14V1.04l-2.49 2.49A11.98 11.98 0 0 0 0 12h2.99A9.02 9.02 0 0 1 12 2.99ZM21.01 12a9 9 0 0 1-15.37 6.36l2.37-2.37H2a.96.96 0 0 0-.95.95v6.02l2.49-2.49A11.98 11.98 0 0 0 24 12Z"/></svg>
</span>
<template slot="popover">
<span class="refresh-products-tooltip-text">{{ __( 'Refresh Products', 'wepos' ) }}</span>
</template>
</v-popover>
</div>
<div class="toggle-view">
<div class="product-toggle">
<span class="toggle-icon list-view flaticon-menu-button-of-three-horizontal-lines" @click="productView = 'list'" :class="{ active: productView == 'list'}"></span>
Expand Down Expand Up @@ -592,6 +601,7 @@ export default {
filteredProducts: [],
totalPages: 1,
page: 1,
productLogsLoading: false,
showOverlay: false,
selectedVariationProduct: {},
attributeDisabled: true,
Expand Down Expand Up @@ -681,6 +691,9 @@ export default {
}
}
return [];
},
productsStorageUpdatedOn() {
return localStorage.getItem( 'productsStorageUpdatedOn' );
}
},

Expand Down Expand Up @@ -922,9 +935,54 @@ export default {
}).then( ( response, status, xhr ) => {
this.fetchProducts();
});
} else {
this.productLoading = false;
} else if ( this.isProductsStorageUpdateRequired() ) {
// Remove existing products from the IndexedDB storage.
wepos.productIndexedDb.deleteAllProducts();

// Insert products to the IndexedDB storage.
wepos.productIndexedDb.insertProducts( this.products );

// Store products IndexedDB updating time to Local Storage.
localStorage.setItem( 'productsStorageUpdatedOn', dayjs().unix() );
Comment on lines +1046 to +1054
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize the handling of IndexedDB operations.

Consider abstracting IndexedDB operations into a separate method to improve code readability and maintainability.

- wepos.productIndexedDb.deleteAllProducts();
- wepos.productIndexedDb.insertProducts(this.products);
- localStorage.setItem('productsStorageUpdatedOn', dayjs().unix());
+ this.updateIndexedDBProducts();

And then define updateIndexedDBProducts method in the methods section.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
} else if ( this.isProductsStorageUpdateRequired() ) {
// Remove existing products from the IndexedDB storage.
wepos.productIndexedDb.deleteAllProducts();
// Insert products to the IndexedDB storage.
wepos.productIndexedDb.insertProducts( this.products );
// Store products IndexedDB updating time to Local Storage.
localStorage.setItem( 'productsStorageUpdatedOn', dayjs().unix() );
} else if ( this.isProductsStorageUpdateRequired() ) {
this.updateIndexedDBProducts();
```
And then define `updateIndexedDBProducts` method in the methods section:
```suggestion
methods: {
updateIndexedDBProducts() {
// Remove existing products from the IndexedDB storage.
wepos.productIndexedDb.deleteAllProducts();
// Insert products to the IndexedDB storage.
wepos.productIndexedDb.insertProducts(this.products);
// Store products IndexedDB updating time to Local Storage.
localStorage.setItem('productsStorageUpdatedOn', dayjs().unix());
},
// other methods...
}

}

this.productLoading = false;
},
isProductsStorageUpdateRequired() {
if ( ! this.productsStorageUpdatedOn || this.productsStorageUpdatedOn < dayjs().subtract( 7, 'days' ).unix() ) {
return true;
}
Comment on lines +1060 to +1062
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure correct comparison by parsing productsStorageUpdatedOn to a number

The value of productsStorageUpdatedOn retrieved from localStorage is a string. Comparing it directly to a number may lead to incorrect results due to type coercion. To ensure proper comparison, parse productsStorageUpdatedOn to an integer before the comparison.

Apply this diff to fix the comparison:

isProductsStorageUpdateRequired() {
    if ( ! this.productsStorageUpdatedOn || 
-         this.productsStorageUpdatedOn < dayjs().subtract( 7, 'days' ).unix() ) {
+         parseInt(this.productsStorageUpdatedOn, 10) < dayjs().subtract( 7, 'days' ).unix() ) {
        return true;
    }
    return false;
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ( ! this.productsStorageUpdatedOn || this.productsStorageUpdatedOn < dayjs().subtract( 7, 'days' ).unix() ) {
return true;
}
if ( ! this.productsStorageUpdatedOn ||
parseInt(this.productsStorageUpdatedOn, 10) < dayjs().subtract( 7, 'days' ).unix() ) {
return true;
}


return false;
},
refreshProducts() {
this.fetchProductLogs( wepos.current_cashier.counter_id );
},
fetchProductLogs( counterId ) {
let fetchingToast = {
title: this.__( 'Products already updated!', 'wepos' ),
type: 'info'
}

this.productLogsLoading = true;

wepos.api.get( wepos.rest.root + wepos.rest.posversion + '/product/logs/' + counterId )
.done( ( response, status, xhr ) => {
if ( response.length > 0 ) {
wepos.productLogs.updateProductsToIndexedDb( response );
wepos.productLogs.updateProductLogsData( counterId );

fetchingToast.title = this.__( 'Products refreshed successfully!', 'wepos' );
fetchingToast.type = 'success';
}
} ).fail( ( response, status, xhr ) => {
fetchingToast.title = this.__( 'Failed to refresh products!', 'wepos' );
fetchingToast.type = 'error';
} ).then( ( response, status, xhr ) => {
this.productLogsLoading = false;

this.toast( fetchingToast );
} );
Comment on lines +1066 to +1093
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve error handling in the fetchProductLogs method.

Currently, the method does not handle potential API failures gracefully. Consider adding error handling to improve the robustness of the product log fetching process.

+ .fail( (error) => {
+     console.error('Failed to fetch product logs:', error);
+     this.productLogsLoading = false;
+ });

Committable suggestion was skipped due low confidence.

},
appendProducts( products ) {
products.forEach( product => {
Expand Down Expand Up @@ -1137,6 +1195,15 @@ export default {
},
},

async beforeCreate() {
const dbName = 'ProductsDB';
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName );

if ( ! isExistsProductsDB ) {
wepos.productIndexedDb.createProductsDB();
}
Comment on lines +1306 to +1312
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure browser compatibility when using indexedDB.databases()

The indexedDB.databases() method is experimental and not supported in all browsers, such as Safari and Internet Explorer. Relying on it may cause runtime errors in unsupported browsers. Consider checking for its availability before using it or providing a fallback mechanism. Alternatively, attempt to open the database and handle exceptions if it doesn't exist.

Apply this diff to handle browser compatibility:

async beforeCreate() {
+   if ('databases' in indexedDB) {
        const dbName             = 'ProductsDB';
        const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName );
        if ( ! isExistsProductsDB ) {
            wepos.productIndexedDb.createProductsDB();
        }
+   } else {
+       // Fallback for browsers without indexedDB.databases()
+       const request = indexedDB.open('ProductsDB');
+       request.onupgradeneeded = (event) => {
+           wepos.productIndexedDb.createProductsDB();
+       };
+       request.onsuccess = (event) => {
+           // Database exists
+       };
+       request.onerror = (event) => {
+           // Handle errors
+       };
+   }
}
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async beforeCreate() {
const dbName = 'ProductsDB';
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName );
if ( ! isExistsProductsDB ) {
wepos.productIndexedDb.createProductsDB();
}
async beforeCreate() {
if ('databases' in indexedDB) {
const dbName = 'ProductsDB';
const isExistsProductsDB = ( await window.indexedDB.databases() ).map( db => db.name ).includes( dbName );
if ( ! isExistsProductsDB ) {
wepos.productIndexedDb.createProductsDB();
}
} else {
// Fallback for browsers without indexedDB.databases()
const request = indexedDB.open('ProductsDB');
request.onupgradeneeded = (event) => {
wepos.productIndexedDb.createProductsDB();
};
request.onsuccess = (event) => {
// Database exists
};
request.onerror = (event) => {
// Handle errors
};
}
}

},

async created() {
this.fetchSettings();
this.fetchTaxes();
Expand Down Expand Up @@ -1171,6 +1238,15 @@ export default {

<style lang="less">

@keyframes rotation {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

#wepos-main {
padding: 20px;
display: flex;
Expand Down Expand Up @@ -1364,7 +1440,7 @@ export default {

.category {
width: 26%;
margin-right: 2%;
margin-right: 1%;
float:left;
position: relative;

Expand Down Expand Up @@ -1401,8 +1477,50 @@ export default {
}

}
.refresh-products {
float: left;
margin-right: -5px;
width: 4.5%;

.refresh-icon {
box-sizing: border-box;
padding: 9px 10px 7px;
background: #fff;
display: inline-block;
border: 1px solid #E9EDF0;
box-shadow: 0 3px 15px 0 rgba(0,0,0,.02);
cursor: pointer;

&:hover {
svg {
fill: #3B80F4;
}
}

&.active {
svg {
fill: #3B80F4;
animation: rotation 1s infinite linear;
}
}

&:before {
margin-left: 0px;
font-size: 13px;
}

&.list-view {
margin-right: -4px;
border-right: none;
}

svg {
fill: #bdc0c9;
}
}
}
.toggle-view {
width: 14%;
width: 10%;
float: left;
text-align: right;

Expand Down
Loading