From 210baafad4e0fa12fc70999d5830998ba3e7c26c Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Wed, 11 Nov 2020 12:04:00 +0530 Subject: [PATCH] feat: pos customer selector new ui --- erpnext/public/scss/point-of-sale.scss | 169 ++++++++++++++ .../page/point_of_sale/pos_item_cart.js | 217 ++++++++++-------- 2 files changed, 285 insertions(+), 101 deletions(-) diff --git a/erpnext/public/scss/point-of-sale.scss b/erpnext/public/scss/point-of-sale.scss index 1d01496277..4d5bc21de5 100644 --- a/erpnext/public/scss/point-of-sale.scss +++ b/erpnext/public/scss/point-of-sale.scss @@ -51,6 +51,10 @@ border-radius: var(--border-radius-md); } + .seperator { + border-bottom: 1px solid var(--gray-300); + } + > .items-selector { grid-column: span 6 / span 6; display: flex; @@ -142,4 +146,169 @@ } } } + + > .customer-cart-container { + grid-column: span 4 / span 4; + display: flex; + flex-direction: column; + + > .customer-section { + @extend .pos-card; + display: flex; + flex-direction: column; + padding: var(--padding-md); + + > .customer-field { + display: flex; + align-items: center; + } + + > .customer-details { + display: flex; + flex-direction: column; + position: sticky; + top: -1px; + z-index: 1; + background-color: var(--fg-color); + + > .header { + display: flex; + margin-bottom: var(--margin-md); + justify-content: space-between; + padding-top: var(--padding-md); + + > .label { + @extend .label; + } + + > .close-details-btn { + display: flex; + align-items: center; + cursor: pointer; + } + } + + > .customer-display { + display: flex; + align-items: center; + cursor: pointer; + + > .customer-image { + display: flex; + align-items: center; + justify-content: center; + width: 3rem; + height: 3rem; + border-radius: 50%; + color: var(--gray-500); + margin-right: var(--margin-md); + + > img { + @extend .image; + border-radius: 50%; + } + } + + > .customer-abbr { + @extend .abbr; + font-size: var(--text-2xl); + } + + > .customer-name-desc { + @extend .nowrap; + display: flex; + flex-direction: column; + margin-right: auto; + + >.customer-name { + font-weight: 700; + font-size: var(--text-lg); + } + + >.customer-desc { + color: var(--gray-600); + font-weight: 500; + font-size: var(--text-sm); + } + } + + > .reset-customer-btn { + display: flex; + align-items: center; + cursor: pointer; + } + + } + + > .customer-fields-container { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: var(--margin-sm); + } + + > .transactions-label { + @extend .label; + margin-top: var(--margin-sm); + margin-bottom: var(--margin-sm); + } + } + } + + > .cart-container { + + } + } + + .invoice-wrapper { + @extend .pointer-no-select; + display: flex; + justify-content: space-between; + border-radius: var(--border-radius-md); + padding: var(--padding-sm); + + &:hover { + background-color: var(--gray-50); + } + + > .invoice-name-date { + display: flex; + flex-direction: column; + justify-content: end; + + > .invoice-name { + @extend .nowrap; + font-size: var(--text-md); + font-weight: 700; + margin-bottom: var(--margin-xs); + } + + > .invoice-date { + @extend .nowrap; + font-size: var(--text-sm); + display: flex; + align-items: center; + } + } + + > .invoice-total-status { + display: flex; + flex-direction: column; + font-weight: 500; + font-size: var(--text-sm); + margin-left: var(--margin-md); + + > .invoice-total { + margin-bottom: var(--margin-xs); + font-size: var(--text-base); + font-weight: 700; + text-align: right; + } + + > .invoice-status { + display: flex; + align-items: center; + justify-content: right; + } + } + } } \ No newline at end of file diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js index 7799dacacb..11453f7cf0 100644 --- a/erpnext/selling/page/point_of_sale/pos_item_cart.js +++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js @@ -16,10 +16,10 @@ erpnext.PointOfSale.ItemCart = class { prepare_dom() { this.wrapper.append( - `
` + `
` ) - this.$component = this.wrapper.find('.item-cart'); + this.$component = this.wrapper.find('.customer-cart-container'); } init_child_components() { @@ -29,7 +29,7 @@ erpnext.PointOfSale.ItemCart = class { init_customer_selector() { this.$component.append( - `
` + `
` ) this.$customer_section = this.$component.find('.customer-section'); } @@ -37,21 +37,20 @@ erpnext.PointOfSale.ItemCart = class { reset_customer_selector() { const frm = this.events.get_frm(); frm.set_value('customer', ''); - this.$customer_section.removeClass('border pr-4 pl-4'); this.make_customer_selector(); this.customer_field.set_focus(); } init_cart_components() { this.$component.append( - `
+ `
Item
Qty
Amount
-
+
@@ -88,7 +87,7 @@ erpnext.PointOfSale.ItemCart = class { `
+ Add Discount
-
+
Net Total
@@ -106,7 +105,7 @@ erpnext.PointOfSale.ItemCart = class {
0.00
-
+
Checkout
@@ -151,7 +150,7 @@ erpnext.PointOfSale.ItemCart = class { this.$numpad_section.append( `
+ text-center text-white no-select pointer rounded-md text-md text-bold mt-4" data-button-value="checkout"> Checkout
` ) @@ -159,15 +158,17 @@ erpnext.PointOfSale.ItemCart = class { bind_events() { const me = this; - this.$customer_section.on('click', '.add-remove-customer', function (e) { - const customer_info_is_visible = me.$cart_container.hasClass('d-none'); - customer_info_is_visible ? - me.toggle_customer_info(false) : me.reset_customer_selector(); + this.$customer_section.on('click', '.reset-customer-btn', function (e) { + me.reset_customer_selector(); }); - this.$customer_section.on('click', '.customer-header', function(e) { - // don't triggger the event if .add-remove-customer btn is clicked which is under .customer-header - if ($(e.target).closest('.add-remove-customer').length) return; + this.$customer_section.on('click', '.close-details-btn', function (e) { + me.toggle_customer_info(false); + }); + + this.$customer_section.on('click', '.customer-display', function(e) { + // don't triggger the event if .reset-customer-btn btn is clicked which is under .customer-header + if ($(e.target).closest('.reset-customer-btn').length) return; const show = !me.$cart_container.hasClass('d-none'); me.toggle_customer_info(show); @@ -282,24 +283,26 @@ erpnext.PointOfSale.ItemCart = class { toggle_item_highlight(item) { const $cart_item = $(item); - const item_is_highlighted = $cart_item.hasClass("shadow"); + const item_is_highlighted = $cart_item.hasClass("shadow-base"); if (!item || item_is_highlighted) { this.item_is_selected = false; - this.$cart_container.find('.cart-item-wrapper').removeClass("shadow").css("opacity", "1"); + this.$cart_container.find('.cart-item-wrapper').removeClass("shadow-base").css("opacity", "1"); } else { - $cart_item.addClass("shadow"); + $cart_item.addClass("shadow-base"); this.item_is_selected = true; this.$cart_container.find('.cart-item-wrapper').css("opacity", "1"); - this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow").css("opacity", "0.65"); + this.$cart_container.find('.cart-item-wrapper').not(item).removeClass("shadow-base").css("opacity", "0.65"); } - // highlight with inner shadow - // $cart_item.addClass("shadow-inner bg-selected"); - // me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-inner bg-selected"); + // highlight with inner shadow-base + // $cart_item.addClass("shadow-base-inner bg-selected"); + // me.$cart_container.find('.cart-item-wrapper').not(this).removeClass("shadow-base-inner bg-selected"); } make_customer_selector() { - this.$customer_section.html(`
`); + this.$customer_section.html(` +
+ `); const me = this; const query = { query: 'erpnext.controllers.queries.customer_query' }; const allowed_customer_group = this.events.get_allowed_customer_group() || []; @@ -313,6 +316,7 @@ erpnext.PointOfSale.ItemCart = class { label: __('Customer'), fieldtype: 'Link', options: 'Customer', + input_class: 'input-xs', placeholder: __('Search by customer name, phone, email.'), get_query: () => query, onchange: function() { @@ -332,7 +336,7 @@ erpnext.PointOfSale.ItemCart = class { } }, }, - parent: this.$customer_section.find('.customer-search-field'), + parent: this.$customer_section.find('.customer-field'), render_input: true, }); this.customer_field.toggle_label(false); @@ -414,7 +418,7 @@ erpnext.PointOfSale.ItemCart = class { stroke-linecap="round" stroke-linejoin="round"> -
+
${String(discount).bold()}% off
` @@ -423,18 +427,18 @@ erpnext.PointOfSale.ItemCart = class { } update_customer_section() { - const { customer, email_id='', mobile_no='', image } = this.customer_info || {}; + const { customer, email_id='', mobile_no='' } = this.customer_info || {}; if (customer) { - this.$customer_section.addClass('border pr-4 pl-4').html( - `
-
- ${get_customer_image()} -
-
${customer}
+ this.$customer_section.html( + `
+
+ ${this.get_customer_image()} +
+
${customer}
${get_customer_description()}
-
+
@@ -449,26 +453,23 @@ erpnext.PointOfSale.ItemCart = class { function get_customer_description() { if (!email_id && !mobile_no) { - return `
Click to add email / phone
` + return `
Click to add email / phone
` } else if (email_id && !mobile_no) { - return `
${email_id}
` + return `
${email_id}
` } else if (mobile_no && !email_id) { - return `
${mobile_no}
` + return `
${mobile_no}
` } else { - return `
${email_id} | ${mobile_no}
` + return `
${email_id} - ${mobile_no}
` } } - function get_customer_image() { - if (image) { - return `
- ${image} -
` - } else { - return `
- ${frappe.get_abbr(customer)} -
` - } + } + get_customer_image() { + const { customer, image } = this.customer_info || {}; + if (image) { + return `
${image}
` + } else { + return `
${frappe.get_abbr(customer)}
` } } @@ -523,7 +524,7 @@ erpnext.PointOfSale.ItemCart = class { let margin_left = ''; if (i !== 0) margin_left = 'ml-2'; const description = /[0-9]+/.test(t.description) ? t.description : `${t.description} @ ${t.rate}%`; - return `${description}` + return `${description}` }).join('') }
@@ -575,7 +576,7 @@ erpnext.PointOfSale.ItemCart = class { if (!$item_to_update.length) { this.$cart_items_wrapper.append( - `
` @@ -618,7 +619,7 @@ erpnext.PointOfSale.ItemCart = class { if (item_data.rate && item_data.amount && item_data.rate !== item_data.amount) { return `
-
+
${item_data.qty || 0}
@@ -629,7 +630,7 @@ erpnext.PointOfSale.ItemCart = class { } else { return `
-
+
${item_data.qty || 0}
@@ -753,25 +754,25 @@ erpnext.PointOfSale.ItemCart = class { } highlight_numpad_btn($btn, curr_action) { - const curr_action_is_highlighted = $btn.hasClass('shadow-inner'); + const curr_action_is_highlighted = $btn.hasClass('shadow-base-inner'); const curr_action_is_action = ['qty', 'discount_percentage', 'rate', 'done'].includes(curr_action); if (!curr_action_is_highlighted) { - $btn.addClass('shadow-inner bg-selected'); + $btn.addClass('shadow-base-inner bg-selected'); } if (this.prev_action === curr_action && curr_action_is_highlighted) { // if Qty is pressed twice - $btn.removeClass('shadow-inner bg-selected'); + $btn.removeClass('shadow-base-inner bg-selected'); } if (this.prev_action && this.prev_action !== curr_action && curr_action_is_action) { // Order: Qty -> Rate then remove Qty highlight const prev_btn = $(`[data-button-value='${this.prev_action}']`); - prev_btn.removeClass('shadow-inner bg-selected'); + prev_btn.removeClass('shadow-base-inner bg-selected'); } if (!curr_action_is_action || curr_action === 'done') { // if numbers are clicked setTimeout(() => { - $btn.removeClass('shadow-inner bg-selected'); + $btn.removeClass('shadow-base-inner bg-selected'); }, 100); } } @@ -790,7 +791,7 @@ erpnext.PointOfSale.ItemCart = class { reset_numpad() { this.numpad_value = ''; this.prev_action = undefined; - this.$numpad_section.find('.shadow-inner').removeClass('shadow-inner bg-selected'); + this.$numpad_section.find('.shadow-base-inner').removeClass('shadow-base-inner bg-selected'); } toggle_numpad_field_edit(fieldname) { @@ -801,48 +802,60 @@ erpnext.PointOfSale.ItemCart = class { toggle_customer_info(show) { if (show) { - this.$cart_container.addClass('d-none') - this.$customer_section.addClass('flex-1 scroll-y').removeClass('mb-0 border pr-4 pl-4') - this.$customer_section.find('.icon').addClass('w-24 h-24 text-2xl').removeClass('w-12 h-12 text-md') - this.$customer_section.find('.customer-header').removeClass('h-18'); - this.$customer_section.find('.customer-details').addClass('sticky z-100 bg-white'); + const { customer } = this.customer_info || {}; - this.$customer_section.find('.customer-name').html( - `
${this.customer_info.customer}
-
` - ) - - this.$customer_section.find('.customer-details').append( - `
-
CONTACT DETAILS
-
- -
-
-
+ this.$cart_container.addClass('d-none'); + this.$customer_section.css({ + 'height': '100%', + 'padding-top': '0px', + 'overflow-x': 'hidden', + 'overflow-y': 'scroll' + }); + this.$customer_section.find('.customer-details').html( + `
+
Contact Details
+
+ + +
-
RECENT TRANSACTIONS
-
` - ) +
+
+ ${this.get_customer_image()} +
+
${customer}
+
+
+
+
+ +
+
+
+
+
Recent Transactions
` + ); // transactions need to be in diff div from sticky elem for scrolling - this.$customer_section.append(`
`) + this.$customer_section.append(`
`) - this.render_customer_info_form(); + this.render_customer_fields(); this.fetch_customer_transactions(); } else { this.$cart_container.removeClass('d-none'); - this.$customer_section.removeClass('flex-1 scroll-y').addClass('mb-0 border pr-4 pl-4'); - this.$customer_section.find('.icon').addClass('w-12 h-12 text-md').removeClass('w-24 h-24 text-2xl'); - this.$customer_section.find('.customer-header').addClass('h-18') - this.$customer_section.find('.customer-details').removeClass('sticky z-100 bg-white'); + this.$customer_section.css({ + 'height': '', + 'padding-top': '', + 'overflow-x': '', + 'overflow-y': '' + }); this.update_customer_section(); } } - render_customer_info_form() { - const $customer_form = this.$customer_section.find('.customer-form'); + render_customer_fields() { + const $customer_form = this.$customer_section.find('.customer-fields-container'); const dfs = [{ fieldname: 'email_id', @@ -864,7 +877,7 @@ erpnext.PointOfSale.ItemCart = class { },{ fieldname: 'loyalty_points', label: __('Loyalty Points'), - fieldtype: 'Int', + fieldtype: 'Data', read_only: 1 }]; @@ -916,14 +929,14 @@ erpnext.PointOfSale.ItemCart = class { const transaction_container = this.$customer_section.find('.customer-transactions'); if (!res.length) { - transaction_container.removeClass('flex-1 border rounded').html( - `
No recent transactions found
` + transaction_container.html( + `
No recent transactions found
` ) return; }; const elapsed_time = moment(res[0].posting_date+" "+res[0].posting_time).fromNow(); - this.$customer_section.find('.last-transacted-on').html(`Last transacted ${elapsed_time}`); + this.$customer_section.find('.customer-desc').html(`Last transacted ${elapsed_time}`); res.forEach(invoice => { const posting_datetime = moment(invoice.posting_date+" "+invoice.posting_time).format("Do MMMM, h:mma"); @@ -934,20 +947,22 @@ erpnext.PointOfSale.ItemCart = class { if (invoice.status === 'Return') (indicator_color = 'grey'); transaction_container.append( - `
-
-
${invoice.name}
-
- ${posting_datetime} -
+ `
+
+
${invoice.name}
+
${posting_datetime}
-
-
+
+
${format_currency(invoice.grand_total, invoice.currency, 0) || 0}
-
${invoice.status}
+
+ + ${invoice.status} +
-
` +
+
` ) }); })