Website: Product Configurator and Bootstrap 4 (#15965)
- Refactored Homepage with customisable Hero Section
- New Homepage Section to add content on Homepage as cards or using Custom HTML
- Products page at "/all-products" with customisable filters
- Item Configure dialog to find an Item Variant filtered by attribute values
- Contact Us dialog on Item page
- Customisable Item page content using the Website Content field
diff --git a/erpnext/templates/generators/item.html b/erpnext/templates/generators/item.html
deleted file mode 100644
index b258bde..0000000
--- a/erpnext/templates/generators/item.html
+++ /dev/null
@@ -1,143 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} {{ title }} {% endblock %}
-
-{% block breadcrumbs %}
- {% include "templates/includes/breadcrumbs.html" %}
-{% endblock %}
-
-{% block page_content %}
-{% from "erpnext/templates/includes/macros.html" import product_image %}
-<div class="item-content">
- <div class="product-page-content" itemscope itemtype="http://schema.org/Product">
- <div class="row">
- <div class="row">
- {% if slideshow %}
- {% set slideshow_items = frappe.get_list(doctype="Website Slideshow Item", fields=["image"], filters={ "parent": doc.slideshow }) %}
- <div class="col-md-1">
- {%- for slideshow_item in slideshow_items -%}
- {% set image_src = slideshow_item['image'] %}
- {% if image_src %}
- <div class="item-alternative-image border">
- <img src="{{ image_src }}" height="50" weight="50" />
- </div>
- {% endif %}
- {% endfor %}
- </div>
- <div class="col-md-5">
- <div class="item-image">
- {% set first_image = slideshow_items[0]['image'] %}
- {{ product_image(first_image, "product-full-image") }}
- </div>
- </div>
- {% else %}
- <div class="col-md-6">
- {{ product_image(website_image, "product-full-image") }}
- </div>
- {% endif %}
- <div class="col-sm-6">
- <h2 itemprop="name">{{ item_name }}</h2>
- <p class="text-muted">
- {{ _("Item Code") }}: <span itemprop="productID">{{ variant and variant.name or name }}</span>
- </p>
- <br>
- <div class="item-attribute-selectors">
- {% if has_variants and attributes %}
-
- {% for d in attributes %}
- {% if attribute_values[d.attribute] -%}
- <div class="item-view-attribute {% if (attribute_values[d.attribute] | len)==1 -%} hidden {%- endif %}"
- style="margin-bottom: 10px;">
- <h6 class="text-muted">{{ _(d.attribute) }}</h6>
- <select class="form-control"
- style="max-width: 140px"
- data-attribute="{{ d.attribute }}">
- {% for value in attribute_values[d.attribute] %}
- <option value="{{ value }}"
- {% if selected_attributes and selected_attributes[d.attribute]==value -%}
- selected
- {%- elif disabled_attributes and value in disabled_attributes.get(d.attribute, []) -%}
- disabled
- {%- endif %}>
- {{ _(value) }}
- </option>
- {% endfor %}
- </select>
- </div>
- {%- endif %}
- {% endfor %}
-
- {% endif %}
- </div>
- <br>
- <div>
- <div itemprop="offers" itemscope itemtype="http://schema.org/Offer">
- <h4 class="item-price hide" itemprop="price"></h4>
- <div class="item-stock hide" itemprop="availability"></div>
- </div>
- <div class="item-cart hide">
- <div id="item-spinner">
- <span style="display: inline-block">
- <div class="input-group number-spinner">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="dwn">
- –</button>
- </span>
- <input class="form-control text-right cart-qty" value="1">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
- +</button>
- </span>
- </div>
- </span>
- </div>
- <div id="item-add-to-cart">
- <button class="btn btn-primary btn-sm">
- {{ _("Add to Cart") }}</button>
- </div>
- <div id="item-update-cart" style="display: none;">
- <a href="/cart" class='btn btn-sm btn-default'>
- <i class='octicon octicon-check'></i>
- {{ _("View in Cart") }}</a>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="row item-website-description margin-top">
- <div class="col-md-12">
- <div class="h6 text-uppercase">{{ _("Description") }}</div>
- <div itemprop="description" class="item-desc">
- {{ web_long_description or description or _("No description given") }}
- </div>
- </div>
- </div>
- {% if website_specifications -%}
- <div class="row item-website-specification margin-top">
- <div class="col-md-12">
- <div class="h6 text-uppercase">{{ _("Specifications") }}</div>
-
- <table class="table">
- {% for d in website_specifications -%}
- <tr>
- <td class="text-muted" style="width: 30%;">{{ d.label }}</td>
- <td>{{ d.description }}</td>
- </tr>
- {%- endfor %}
- </table>
- </div>
- </div>
- {%- endif %}
- </div>
- </div>
-</div>
-<script>
- {% include "templates/includes/product_page.js" %}
-
- {% if variant_info %}
- window.variant_info = {{ variant_info }};
- {% else %}
- window.variant_info = null;
- {% endif %}
-</script>
-{% endblock %}
diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html
new file mode 100644
index 0000000..d3691a6
--- /dev/null
+++ b/erpnext/templates/generators/item/item.html
@@ -0,0 +1,32 @@
+{% extends "templates/web.html" %}
+
+{% block title %} {{ title }} {% endblock %}
+
+{% block breadcrumbs %}
+ {% include "templates/includes/breadcrumbs.html" %}
+{% endblock %}
+
+{% block page_content %}
+{% from "erpnext/templates/includes/macros.html" import product_image %}
+<div class="item-content">
+ <div class="product-page-content" itemscope itemtype="http://schema.org/Product">
+ <div class="row mb-5">
+ {% include "templates/generators/item/item_image.html" %}
+ {% include "templates/generators/item/item_details.html" %}
+ </div>
+
+ {% include "templates/generators/item/item_specifications.html" %}
+
+ {{ doc.website_content or '' }}
+ </div>
+</div>
+{% endblock %}
+
+{% block base_scripts %}
+<!-- js should be loaded in body! -->
+<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
+<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
+<script type="text/javascript" src="/assets/js/control.min.js"></script>
+<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
+<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
new file mode 100644
index 0000000..f4a31a7
--- /dev/null
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -0,0 +1,67 @@
+{% if shopping_cart and shopping_cart.cart_settings.enabled %}
+
+{% set cart_settings = shopping_cart.cart_settings %}
+{% set product_info = shopping_cart.product_info %}
+
+<div class="item-cart row mt-2" data-variant-item-code="{{ item_code }}">
+ <div class="col-md-12">
+ {% if cart_settings.show_price and product_info.price %}
+ <h4>
+ {{ product_info.price.formatted_price_sales_uom }}
+ <small class="text-muted">({{ product_info.price.formatted_price }} / {{ product_info.uom }})</small>
+ </h4>
+ {% endif %}
+ {% if cart_settings.show_stock_availability %}
+ <div>
+ {% if product_info.in_stock == 0 %}
+ <span class="text-danger">
+ {{ _('Not in stock') }}
+ </span>
+ {% elif product_info.in_stock == 1 %}
+ <span class="text-success">
+ {{ _('In stock') }}
+ {% if product_info.show_stock_qty and product_info.stock_qty %}
+ ({{ product_info.stock_qty[0][0] }})
+ {% endif %}
+ </span>
+ {% endif %}
+ </div>
+ {% endif %}
+ <div class="mt-3">
+ <a href="/cart"
+ class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
+ role="button"
+ >
+ {{ _("View in Cart") }}
+ </a>
+ <button
+ data-item-code="{{item_code}}"
+ class="btn btn-outline-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %}"
+ >
+ {{ _("Add to Cart") }}
+ </button>
+ </div>
+ </div>
+</div>
+
+<script>
+ frappe.ready(() => {
+ $('.page_content').on('click', '.btn-add-to-cart', (e) => {
+ const $btn = $(e.currentTarget);
+ $btn.prop('disabled', true);
+ const item_code = $btn.data('item-code');
+ erpnext.shopping_cart.update_cart({
+ item_code,
+ qty: 1,
+ callback(r) {
+ $btn.prop('disabled', false);
+ if (r.message) {
+ $('.btn-add-to-cart, .btn-view-in-cart').toggleClass('hidden');
+ }
+ }
+ });
+ });
+ });
+</script>
+
+{% endif %}
\ No newline at end of file
diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html
new file mode 100644
index 0000000..04f89ec
--- /dev/null
+++ b/erpnext/templates/generators/item/item_configure.html
@@ -0,0 +1,23 @@
+{% if shopping_cart and shopping_cart.cart_settings.enabled %}
+{% set cart_settings = shopping_cart.cart_settings %}
+
+<div class="mt-3">
+ {% if cart_settings.show_configure_button | int %}
+ <button class="btn btn-primary btn-configure"
+ data-item-code="{{ doc.name }}"
+ data-item-name="{{ doc.item_name }}"
+ >
+ {{ _('Configure') }}
+ </button>
+ {% endif %}
+ {% if cart_settings.show_contact_us_button | int %}
+ <button class="btn btn-link btn-inquiry" data-item-code="{{ doc.name }}">
+ {{ _('Contact Us') }}
+ </button>
+ {% endif %}
+</div>
+<script>
+{% include "templates/generators/item/item_configure.js" %}
+{% include "templates/generators/item/item_inquiry.js" %}
+</script>
+{% endif %}
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
new file mode 100644
index 0000000..5fd9011
--- /dev/null
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -0,0 +1,318 @@
+class ItemConfigure {
+ constructor(item_code, item_name) {
+ this.item_code = item_code;
+ this.item_name = item_name;
+
+ this.get_attributes_and_values()
+ .then(attribute_data => {
+ this.attribute_data = attribute_data;
+ this.show_configure_dialog();
+ });
+ }
+
+ show_configure_dialog() {
+ const fields = this.attribute_data.map(a => {
+ return {
+ fieldtype: 'Select',
+ label: a.attribute,
+ fieldname: a.attribute,
+ options: a.values.map(v => {
+ return {
+ label: v,
+ value: v
+ };
+ }),
+ change: (e) => {
+ this.on_attribute_selection(e);
+ }
+ };
+ });
+
+ this.dialog = new frappe.ui.Dialog({
+ title: __('Configure {0}', [this.item_name]),
+ fields,
+ on_hide: () => {
+ set_continue_configuration();
+ }
+ });
+
+ this.attribute_data.forEach(a => {
+ const field = this.dialog.get_field(a.attribute);
+ const $a = $(`<a href>${__("Clear")}</a>`);
+ $a.on('click', (e) => {
+ e.preventDefault();
+ this.dialog.set_value(a.attribute, '');
+ });
+ field.$wrapper.find('.help-box').append($a);
+ });
+
+ this.append_status_area();
+ this.dialog.show();
+
+ this.dialog.set_values(JSON.parse(localStorage.getItem(this.get_cache_key())));
+
+ $('.btn-configure').prop('disabled', false);
+ }
+
+ on_attribute_selection(e) {
+ if (e) {
+ const changed_fieldname = $(e.target).data('fieldname');
+ this.show_range_input_if_applicable(changed_fieldname);
+ } else {
+ this.show_range_input_for_all_fields();
+ }
+
+ const values = this.dialog.get_values();
+ if (Object.keys(values).length === 0) {
+ this.clear_status();
+ localStorage.removeItem(this.get_cache_key());
+ return;
+ }
+
+ // save state
+ localStorage.setItem(this.get_cache_key(), JSON.stringify(values));
+
+ // show
+ this.set_loading_status();
+
+ this.get_next_attribute_and_values(values)
+ .then(data => {
+ const {
+ valid_options_for_attributes,
+ } = data;
+
+ this.set_item_found_status(data);
+
+ for (let attribute in valid_options_for_attributes) {
+ const valid_options = valid_options_for_attributes[attribute];
+ const options = this.dialog.get_field(attribute).df.options;
+ const new_options = options.map(o => {
+ o.disabled = !valid_options.includes(o.value);
+ return o;
+ });
+
+ this.dialog.set_df_property(attribute, 'options', new_options);
+ this.dialog.get_field(attribute).set_options();
+ }
+ });
+ }
+
+ show_range_input_for_all_fields() {
+ this.dialog.fields.forEach(f => {
+ this.show_range_input_if_applicable(f.fieldname);
+ });
+ }
+
+ show_range_input_if_applicable(fieldname) {
+ const changed_field = this.dialog.get_field(fieldname);
+ const changed_value = changed_field.get_value();
+ if (changed_value && changed_value.includes(' to ')) {
+ // possible range input
+ let numbers = changed_value.split(' to ');
+ numbers = numbers.map(number => parseFloat(number));
+
+ if (!numbers.some(n => isNaN(n))) {
+ numbers.sort((a, b) => a - b);
+ if (changed_field.$input_wrapper.find('.range-selector').length) {
+ return;
+ }
+ const parent = $('<div class="range-selector">')
+ .insertBefore(changed_field.$input_wrapper.find('.help-box'));
+ const control = frappe.ui.form.make_control({
+ df: {
+ fieldtype: 'Int',
+ label: __('Enter value betweeen {0} and {1}', [numbers[0], numbers[1]]),
+ change: () => {
+ const value = control.get_value();
+ if (value < numbers[0] || value > numbers[1]) {
+ control.$wrapper.addClass('was-validated');
+ control.set_description(
+ __('Value must be between {0} and {1}', [numbers[0], numbers[1]]));
+ control.$input[0].setCustomValidity('error');
+ } else {
+ control.$wrapper.removeClass('was-validated');
+ control.set_description('');
+ control.$input[0].setCustomValidity('');
+ this.update_range_values(fieldname, value);
+ }
+ }
+ },
+ render_input: true,
+ parent
+ });
+ control.$wrapper.addClass('mt-3');
+ }
+ }
+ }
+
+ update_range_values(attribute, range_value) {
+ this.range_values = this.range_values || {};
+ this.range_values[attribute] = range_value;
+ }
+
+ show_remaining_optional_attributes() {
+ // show all attributes if remaining
+ // unselected attributes are all optional
+ const unselected_attributes = this.dialog.fields.filter(df => {
+ const value_selected = this.dialog.get_value(df.fieldname);
+ return !value_selected;
+ });
+ const is_optional_attribute = df => {
+ const optional_attributes = this.attribute_data
+ .filter(a => a.optional).map(a => a.attribute);
+ return optional_attributes.includes(df.fieldname);
+ };
+ if (unselected_attributes.every(is_optional_attribute)) {
+ unselected_attributes.forEach(df => {
+ this.dialog.fields_dict[df.fieldname].$wrapper.show();
+ });
+ }
+ }
+
+ set_loading_status() {
+ this.dialog.$status_area.html(`
+ <div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
+ ${__('Loading...')}
+ </div>
+ `);
+ }
+
+ set_item_found_status(data) {
+ const html = this.get_html_for_item_found(data);
+ this.dialog.$status_area.html(html);
+ }
+
+ clear_status() {
+ this.dialog.$status_area.empty();
+ }
+
+ get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info }) {
+ const exact_match_message = __('1 exact match.');
+ const one_item = exact_match.length === 1 ?
+ exact_match[0] :
+ filtered_items_count === 1 ?
+ filtered_items[0] : '';
+
+ const item_add_to_cart = one_item ? `
+ <div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
+ <div>
+ <div>${one_item} ${product_info && product_info.price ? '(' + product_info.price.formatted_price_sales_uom + ')' : ''}</div>
+ </div>
+ <a href data-action="btn_add_to_cart" data-item-code="${one_item}">
+ ${__('Add to cart')}
+ </a>
+ </div>
+ `: '';
+
+ const items_found = filtered_items_count === 1 ?
+ __('{0} item found.', [filtered_items_count]) :
+ __('{0} items found.', [filtered_items_count]);
+
+ const item_found_status = `
+ <div class="alert alert-warning d-flex justify-content-between align-items-center" role="alert">
+ <span>
+ ${exact_match.length === 1 ? '' : items_found}
+ ${exact_match.length === 1 ? `<span>${exact_match_message}</span>` : ''}
+ </span>
+ <a href data-action="btn_clear_values">
+ ${__('Clear values')}
+ </a>
+ </div>
+ `;
+
+ return `
+ ${item_add_to_cart}
+ ${item_found_status}
+ `;
+ }
+
+ btn_add_to_cart(e) {
+ if (frappe.session.user !== 'Guest') {
+ localStorage.removeItem(this.get_cache_key());
+ }
+ const item_code = $(e.currentTarget).data('item-code');
+ const additional_notes = Object.keys(this.range_values || {}).map(attribute => {
+ return `${attribute}: ${this.range_values[attribute]}`;
+ }).join('\n');
+ erpnext.shopping_cart.update_cart({
+ item_code,
+ additional_notes,
+ qty: 1
+ });
+ this.dialog.hide();
+ }
+
+ btn_clear_values() {
+ this.dialog.fields_list.forEach(f => {
+ f.df.options = f.df.options.map(option => {
+ option.disabled = false;
+ return option;
+ });
+ });
+ this.dialog.clear();
+ this.on_attribute_selection();
+ }
+
+ append_status_area() {
+ this.dialog.$status_area = $('<div class="status-area">');
+ this.dialog.$wrapper.find('.modal-body').prepend(this.dialog.$status_area);
+ this.dialog.$wrapper.on('click', '[data-action]', (e) => {
+ e.preventDefault();
+ const $target = $(e.currentTarget);
+ const action = $target.data('action');
+ const method = this[action];
+ method.call(this, e);
+ });
+ this.dialog.$body.css({ maxHeight: '75vh', overflow: 'auto', overflowX: 'hidden' });
+ }
+
+ get_next_attribute_and_values(selected_attributes) {
+ return this.call('erpnext.portal.product_configurator.utils.get_next_attribute_and_values', {
+ item_code: this.item_code,
+ selected_attributes
+ });
+ }
+
+ get_attributes_and_values() {
+ return this.call('erpnext.portal.product_configurator.utils.get_attributes_and_values', {
+ item_code: this.item_code
+ });
+ }
+
+ get_cache_key() {
+ return `configure:${this.item_code}`;
+ }
+
+ call(method, args) {
+ // promisified frappe.call
+ return new Promise((resolve, reject) => {
+ frappe.call(method, args)
+ .then(r => resolve(r.message))
+ .fail(reject);
+ });
+ }
+}
+
+function set_continue_configuration() {
+ const $btn_configure = $('.btn-configure');
+ const { itemCode } = $btn_configure.data();
+
+ if (localStorage.getItem(`configure:${itemCode}`)) {
+ $btn_configure.text(__('Continue Configuration'));
+ } else {
+ $btn_configure.text(__('Configure'));
+ }
+}
+
+frappe.ready(() => {
+ const $btn_configure = $('.btn-configure');
+ if (!$btn_configure.length) return;
+ const { itemCode, itemName } = $btn_configure.data();
+
+ set_continue_configuration();
+
+ $btn_configure.on('click', () => {
+ $btn_configure.prop('disabled', true);
+ new ItemConfigure(itemCode, itemName);
+ });
+});
diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html
new file mode 100644
index 0000000..4f8f8c2
--- /dev/null
+++ b/erpnext/templates/generators/item/item_details.html
@@ -0,0 +1,22 @@
+<div class="col-md-8">
+<!-- title -->
+<h1 itemprop="name">
+ {{ item_name }}
+</h1>
+<p class="text-muted">
+ <span>{{ _("Item Code") }}:</span>
+ <span itemprop="productID">{{ doc.name }}</span>
+</p>
+<!-- description -->
+<div itemprop="description">
+ {{ doc.web_long_description or doc.description or _("No description given") | safe }}
+</div>
+
+{% if has_variants %}
+ <!-- configure template -->
+ {% include "templates/generators/item/item_configure.html" %}
+{% else %}
+ <!-- add variant to cart -->
+ {% include "templates/generators/item/item_add_to_cart.html" %}
+{% endif %}
+</div>
diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html
new file mode 100644
index 0000000..0dd4c35
--- /dev/null
+++ b/erpnext/templates/generators/item/item_image.html
@@ -0,0 +1,107 @@
+<div class="col-md-4 h-100">
+{% if slides %}
+{{ product_image(slides[0].image, 'product-image') }}
+<div class="item-slideshow">
+ {% for item in slides %}
+ <img class="item-slideshow-image mt-2 {% if loop.first %}active{% endif %}"
+ src="{{ item.image }}" alt="{{ item.heading }}">
+ {% endfor %}
+</div>
+<!-- Simple image slideshow -->
+<script>
+ frappe.ready(() => {
+ $('.page_content').on('click', '.item-slideshow-image', (e) => {
+ const $img = $(e.currentTarget);
+ const link = $img.prop('src');
+ const $product_image = $('.product-image');
+ $product_image.find('a').prop('href', link);
+ $product_image.find('img').prop('src', link);
+
+ $('.item-slideshow-image').removeClass('active');
+ $img.addClass('active');
+ });
+ })
+</script>
+{% else %}
+{{ product_image(website_image or image or 'no-image.jpg') }}
+{% endif %}
+
+<!-- Simple image preview -->
+
+<div class="image-zoom-view" style="display: none;">
+ <button type="button" class="close" aria-label="Close">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+ stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x">
+ <line x1="18" y1="6" x2="6" y2="18"></line>
+ <line x1="6" y1="6" x2="18" y2="18"></line>
+ </svg>
+ </button>
+</div>
+</div>
+<style>
+ .website-image {
+ cursor: pointer;
+ }
+
+ .image-zoom-view {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 100vh;
+ width: 100vw;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background: rgba(0, 0, 0, 0.8);
+ z-index: 1080;
+ }
+
+ .image-zoom-view img {
+ max-height: 100%;
+ max-width: 100%;
+ }
+
+ .image-zoom-view button {
+ position: absolute;
+ right: 3rem;
+ top: 2rem;
+ }
+
+ .image-zoom-view svg {
+ color: var(--white);
+ }
+</style>
+<script>
+ frappe.ready(() => {
+ const $zoom_wrapper = $('.image-zoom-view');
+
+ $('.website-image').on('click', (e) => {
+ e.preventDefault();
+ const $img = $(e.target);
+ const src = $img.prop('src');
+ if (!src) return;
+ show_preview(src);
+ });
+
+ $zoom_wrapper.on('click', 'button', hide_preview);
+
+ $(document).on('keydown', (e) => {
+ if (e.key === 'Escape') {
+ hide_preview();
+ }
+ });
+
+ function show_preview(src) {
+ $zoom_wrapper.show();
+ const $img = $(`<img src="${src}">`)
+ $zoom_wrapper.append($img);
+ }
+
+ function hide_preview() {
+ $zoom_wrapper.find('img').remove();
+ $zoom_wrapper.hide();
+ }
+ })
+</script>
diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js
new file mode 100644
index 0000000..52ddae2
--- /dev/null
+++ b/erpnext/templates/generators/item/item_inquiry.js
@@ -0,0 +1,70 @@
+frappe.ready(() => {
+ const d = new frappe.ui.Dialog({
+ title: __('Contact Us'),
+ fields: [
+ {
+ fieldtype: 'Data',
+ label: __('Full Name'),
+ fieldname: 'lead_name',
+ reqd: 1
+ },
+ {
+ fieldtype: 'Data',
+ label: __('Organization Name'),
+ fieldname: 'company_name',
+ },
+ {
+ fieldtype: 'Data',
+ label: __('Email'),
+ fieldname: 'email_id',
+ options: 'Email',
+ reqd: 1
+ },
+ {
+ fieldtype: 'Data',
+ label: __('Subject'),
+ fieldname: 'subject',
+ reqd: 1
+ },
+ {
+ fieldtype: 'Text',
+ label: __('Message'),
+ fieldname: 'message',
+ reqd: 1
+ }
+ ],
+ primary_action: send_inquiry,
+ primary_action_label: __('Send')
+ });
+
+ function send_inquiry() {
+ const values = d.get_values();
+ const doc = Object.assign({}, values);
+ delete doc.subject;
+ delete doc.message;
+
+ d.hide();
+
+ frappe.call('erpnext.shopping_cart.cart.create_lead_for_item_inquiry', {
+ lead: doc,
+ subject: values.subject,
+ message: values.message
+ }).then(r => {
+ if (r.message) {
+ d.clear();
+ }
+ });
+ }
+
+ $('.btn-inquiry').click((e) => {
+ const $btn = $(e.target);
+ const item_code = $btn.data('item-code');
+ d.set_value('subject', 'Inquiry about ' + item_code);
+ if (!['Administrator', 'Guest'].includes(frappe.session.user)) {
+ d.set_value('email_id', frappe.session.user);
+ d.set_value('lead_name', frappe.get_cookie('full_name'));
+ }
+
+ d.show();
+ });
+});
\ No newline at end of file
diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html
new file mode 100644
index 0000000..a12a074
--- /dev/null
+++ b/erpnext/templates/generators/item/item_specifications.html
@@ -0,0 +1,16 @@
+{% if doc.website_specifications -%}
+<div class="row item-website-specification mt-5">
+ <div class="col-md-12">
+ <h6 class="text-uppercase text-muted">{{ _("Specifications") }}</h6>
+
+ <table class="table table-bordered">
+ {% for d in doc.website_specifications -%}
+ <tr>
+ <td class="text-muted" style="width: 30%;">{{ d.label }}</td>
+ <td>{{ d.description }}</td>
+ </tr>
+ {%- endfor %}
+ </table>
+ </div>
+</div>
+{%- endif %}
\ No newline at end of file
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index cf8aa15..3f98453 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -9,29 +9,32 @@
{% include "templates/includes/slideshow.html" %}
{% endif %}
{% if description %}<!-- description -->
- <div itemprop="description">{{ description or ""}}</div>
+ <div class="mb-3" itemprop="description">{{ description or ""}}</div>
{% endif %}
</div>
- <div>
- {% if items %}
- <div id="search-list" {% if not products_as_list -%} class="row" {%- endif %}>
- {% for i in range(0, page_length) %}
- {% if items[i] %}
- {{ items[i] }}
+ <div class="row">
+ <div class="col-md-8">
+ {% if items %}
+ <div id="search-list">
+ {% for i in range(0, page_length) %}
+ {% if items[i] %}
+ {%- set item = items[i] %}
+ {% include "erpnext/www/all-products/item_row.html" %}
+ {% endif %}
+ {% endfor %}
+ </div>
+ <div class="item-group-nav-buttons">
+ {% if frappe.form_dict.start|int > 0 %}
+ <a class="btn btn-outline-secondary" href="/{{ pathname }}?start={{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</a>
{% endif %}
- {% endfor %}
- </div>
- <div class="text-center item-group-nav-buttons">
- {% if frappe.form_dict.start|int > 0 %}
- <a class="btn btn-default" href="/{{ pathname }}?start={{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</a>
- {% endif %}
- {% if items|length > page_length %}
- <a class="btn btn-default" href="/{{ pathname }}?start={{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</a>
- {% endif %}
- </div>
- {% else %}
+ {% if items|length > page_length %}
+ <a class="btn btn-outline-secondary" href="/{{ pathname }}?start={{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</a>
+ {% endif %}
+ </div>
+ {% else %}
<div class="text-muted">{{ _("No items listed") }}.</div>
- {% endif %}
+ {% endif %}
+ </div>
</div>
</div>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/address_row.html b/erpnext/templates/includes/address_row.html
index bfc035a..dadd2df 100644
--- a/erpnext/templates/includes/address_row.html
+++ b/erpnext/templates/includes/address_row.html
@@ -1,12 +1,12 @@
-<div class="web-list-item">
- <a href="/addresses?name={{ doc.name | urlencode }}" class="no-decoration">
+<div class="web-list-item mb-3">
+ <a href="/addresses?name={{ doc.name | urlencode }}" class="no-underline text-reset">
<div class="row">
- <div class="col-xs-3">
+ <div class="col-3">
<span class="indicator {{ "red" if doc.address_type=="Office" else "green" if doc.address_type=="Billing" else "blue" if doc.address_type=="Shipping" else "darkgrey" }}">{{ doc.address_title }}</span>
</div>
- <div class="col-xs-2"> {{ _(doc.address_type) }} </div>
- <div class="col-xs-2"> {{ doc.city }} </div>
- <div class="col-xs-5 text-right small text-muted">
+ <div class="col-2"> {{ _(doc.address_type) }} </div>
+ <div class="col-2"> {{ doc.city }} </div>
+ <div class="col-5 text-right small text-muted">
{{ frappe.get_doc(doc).get_display() }}
</div>
</div>
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/includes/cart.js
index 51be954..983898b 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/includes/cart.js
@@ -16,41 +16,33 @@
bind_events: function() {
shopping_cart.bind_address_select();
shopping_cart.bind_place_order();
+ shopping_cart.bind_request_quotation();
shopping_cart.bind_change_qty();
+ shopping_cart.bind_change_notes();
shopping_cart.bind_dropdown_cart_buttons();
},
bind_address_select: function() {
- $(".cart-addresses").find('input[data-address-name]').on("click", function() {
- if($(this).prop("checked")) {
- var me = this;
+ $(".cart-addresses").on('click', '.address-card', function(e) {
+ const $card = $(e.currentTarget);
+ const address_fieldname = $card.closest('[data-fieldname]').attr('data-fieldname');
+ const address_name = $card.closest('[data-address-name]').attr('data-address-name');
- // uncheck other shipping or billing addresses:
- if ( $(this).is('input[data-fieldname=customer_address]') ) {
- $('input[data-fieldname=customer_address]').not(this).prop('checked', false);
- } else {
- $('input[data-fieldname=shipping_address_name]').not(this).prop('checked', false);
- }
-
- return frappe.call({
- type: "POST",
- method: "erpnext.shopping_cart.cart.update_cart_address",
- freeze: true,
- args: {
- address_fieldname: $(this).attr("data-fieldname"),
- address_name: $(this).attr("data-address-name")
- },
- callback: function(r) {
- if(!r.exc) {
- $(".cart-tax-items").html(r.message.taxes);
- }
+ return frappe.call({
+ type: "POST",
+ method: "erpnext.shopping_cart.cart.update_cart_address",
+ freeze: true,
+ args: {
+ address_fieldname,
+ address_name
+ },
+ callback: function(r) {
+ if(!r.exc) {
+ $(".cart-tax-items").html(r.message.taxes);
}
- });
- } else {
- return false;
- }
+ }
+ });
});
-
},
bind_place_order: function() {
@@ -59,12 +51,18 @@
});
},
+ bind_request_quotation: function() {
+ $('.btn-request-for-quotation').on('click', function() {
+ shopping_cart.request_quotation(this);
+ });
+ },
+
bind_change_qty: function() {
// bind update button
$(".cart-items").on("change", ".cart-qty", function() {
var item_code = $(this).attr("data-item-code");
var newVal = $(this).val();
- shopping_cart.shopping_cart_update(item_code, newVal);
+ shopping_cart.shopping_cart_update({item_code, qty: newVal});
});
$(".cart-items").on('click', '.number-spinner button', function () {
@@ -82,7 +80,21 @@
}
input.val(newVal);
var item_code = input.attr("data-item-code");
- shopping_cart.shopping_cart_update(item_code, newVal);
+ shopping_cart.shopping_cart_update({item_code, qty: newVal});
+ });
+ },
+
+ bind_change_notes: function() {
+ $('.cart-items').on('change', 'textarea', function() {
+ const $textarea = $(this);
+ const item_code = $textarea.attr('data-item-code');
+ const qty = $textarea.closest('tr').find('.cart-qty').val();
+ const notes = $textarea.val();
+ shopping_cart.shopping_cart_update({
+ item_code,
+ qty,
+ additional_notes: notes
+ });
});
},
@@ -150,7 +162,32 @@
.html(msg || frappe._("Something went wrong!"))
.toggle(true);
} else {
- window.location.href = "/orders/" + encodeURIComponent(r.message);
+ window.open('/orders/' + encodeURIComponent(r.message), '_blank');
+ window.location.reload();
+ }
+ }
+ });
+ },
+
+ request_quotation: function(btn) {
+ return frappe.call({
+ type: "POST",
+ method: "erpnext.shopping_cart.cart.request_for_quotation",
+ btn: btn,
+ callback: function(r) {
+ if(r.exc) {
+ var msg = "";
+ if(r._server_messages) {
+ msg = JSON.parse(r._server_messages || []).join("<br>");
+ }
+
+ $("#cart-error")
+ .empty()
+ .html(msg || frappe._("Something went wrong!"))
+ .toggle(true);
+ } else {
+ window.open('/printview?doctype=Quotation&name=' + r.message, '_blank');
+ window.location.reload();
}
}
});
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
new file mode 100644
index 0000000..c91723e
--- /dev/null
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -0,0 +1,12 @@
+<div class="card address-card h-100">
+ <div class="check" style="position: absolute; right: 15px; top: 15px;">
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check"><polyline points="20 6 9 17 4 12"></polyline></svg>
+ </div>
+ <div class="card-body">
+ <h5 class="card-title">{{ address.name }}</h5>
+ <p class="card-text text-muted">
+ {{ address.display }}
+ </p>
+ <a href="/addresses?name={{address.name}}" class="card-link">{{ _('Edit') }}</a>
+ </div>
+</div>
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 7bd9256..2c90f8c 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -1,26 +1,141 @@
{% from "erpnext/templates/includes/cart/cart_macros.html" import show_address %}
-<div class="row">
- {% if addresses|length == 1%}
- {% set select_address = True %}
- {% endif %}
- <div class="col-sm-6">
- <div class="h6 text-uppercase">{{ _("Shipping Address") }}</div>
- <div id="cart-shipping-address" class="panel-group"
- data-fieldname="shipping_address_name">
- {% for address in shipping_addresses %}
- {{ show_address(address, doc, "shipping_address_name", select_address) }}
- {% endfor %}
- </div>
- <a class="btn btn-default btn-sm" href="/addresses">
- {{ _("Manage Addresses") }}</a>
- </div>
- <div class="col-sm-6">
- <div class="h6 text-uppercase">{{ _("Billing Address") }}</div>
- <div id="cart-billing-address" class="panel-group"
- data-fieldname="customer_address">
- {% for address in billing_addresses %}
- {{ show_address(address, doc, "customer_address", select_address) }}
- {% endfor %}
- </div>
+
+{% if addresses | length == 1%}
+ {% set select_address = True %}
+{% endif %}
+
+<div class="mb-3" data-section="shipping-address">
+ <h6 class="text-uppercase">{{ _("Shipping Address") }}</h6>
+ <div class="row no-gutters" data-fieldname="shipping_address_name">
+ {% for address in shipping_addresses %}
+ <div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.shipping_address_name == address.name %} data-active {% endif %}>
+ {% include "templates/includes/cart/address_card.html" %}
+ </div>
+ {% endfor %}
</div>
</div>
+<div class="mb-3" data-section="billing-address">
+ <h6 class="text-uppercase">{{ _("Billing Address") }}</h6>
+ <div class="row no-gutters" data-fieldname="customer_address">
+ {% for address in billing_addresses %}
+ <div class="mr-3 mb-3 w-25" data-address-name="{{address.name}}" {% if doc.customer_address == address.name %} data-active {% endif %}>
+ {% include "templates/includes/cart/address_card.html" %}
+ </div>
+ {% endfor %}
+ </div>
+</div>
+<div class="custom-control custom-checkbox">
+ <input type="checkbox" class="custom-control-input" id="input_same_billing" checked>
+ <label class="custom-control-label" for="input_same_billing">{{ _('Billing Address is same as Shipping Address') }}</label>
+</div>
+<button class="btn btn-outline-primary btn-sm mt-3 btn-new-address">{{ _("Add a new address") }}</button>
+
+<script>
+frappe.ready(() => {
+ $(document).on('click', '.address-card', (e) => {
+ const $target = $(e.currentTarget);
+ const $section = $target.closest('[data-section]');
+ $section.find('.address-card').removeClass('active');
+ $target.addClass('active');
+ });
+
+ $('#input_same_billing').change((e) => {
+ const $check = $(e.target);
+ toggle_billing_address_section(!$check.is(':checked'));
+ });
+
+ $('.btn-new-address').click(() => {
+ const d = new frappe.ui.Dialog({
+ title: __('New Address'),
+ fields: [
+ {
+ label: __('Address Title'),
+ fieldname: 'address_title',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ {
+ label: __('Address Type'),
+ fieldname: 'address_type',
+ fieldtype: 'Select',
+ options: [
+ 'Billing',
+ 'Shipping'
+ ],
+ reqd: 1
+ },
+ {
+ label: __('Address Line 1'),
+ fieldname: 'address_line1',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ {
+ label: __('Address Line 2'),
+ fieldname: 'address_line2',
+ fieldtype: 'Data'
+ },
+ {
+ label: __('City/Town'),
+ fieldname: 'city',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ {
+ label: __('State'),
+ fieldname: 'state',
+ fieldtype: 'Data'
+ },
+ {
+ label: __('Pin Code'),
+ fieldname: 'pincode',
+ fieldtype: 'Data'
+ },
+ {
+ label: __('Country'),
+ fieldname: 'country',
+ fieldtype: 'Data',
+ reqd: 1
+ },
+ ],
+ primary_action_label: __('Save'),
+ primary_action: (values) => {
+ frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
+ .then(r => {
+ d.hide();
+ window.location.reload();
+ });
+ }
+ })
+
+ d.show();
+ });
+
+ function setup_state() {
+ const shipping_address = $('[data-section="shipping-address"]')
+ .find('[data-address-name][data-active]').attr('data-address-name');
+
+ const billing_address = $('[data-section="billing-address"]')
+ .find('[data-address-name][data-active]').attr('data-address-name');
+
+ $('#input_same_billing').prop('checked', shipping_address === billing_address).trigger('change');
+
+ if (!shipping_address && !billing_address) {
+ $('#input_same_billing').prop('checked', true).trigger('change');
+ }
+
+ if (shipping_address) {
+ $(`[data-section="shipping-address"] [data-address-name="${shipping_address}"] .address-card`).addClass('active');
+ }
+ if (billing_address) {
+ $(`[data-section="billing-address"] [data-address-name="${billing_address}"] .address-card`).addClass('active');
+ }
+ }
+
+ setup_state();
+
+ function toggle_billing_address_section(flag) {
+ $('[data-section="billing-address"]').toggle(flag);
+ }
+});
+</script>
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html
index 65b81d9..ca5744b 100644
--- a/erpnext/templates/includes/cart/cart_items.html
+++ b/erpnext/templates/includes/cart/cart_items.html
@@ -1,31 +1,42 @@
-{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description %}
-{% from "erpnext/templates/includes/order/order_macros.html" import item_name_and_description_cart %}
-
{% for d in doc.items %}
-<div class="row checkout">
- <div class="col-sm-8 col-xs-6 col-name-description">
- {{ item_name_and_description(d) }}
- </div>
- <div class="col-sm-2 col-xs-3 text-right col-qty">
- <span style="display: inline-block">
- <div class="input-group number-spinner">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="dwn">
- –</button>
- </span>
- <input class="form-control text-right cart-qty"
- value = "{{ d.get_formatted('qty') }}"
- data-item-code="{{ d.item_code }}">
- <span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="up" style="margin-left:-2px;">
- +</button>
- </span>
- </div>
+<tr data-name="{{ d.name }}">
+ <td>
+ <div class="font-weight-bold">
+ {{ d.item_name }}
+ </div>
+ <div>
+ {{ d.item_code }}
+ </div>
+ {%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
+ {% if variant_of %}
+ <span class="text-muted">
+ {{ _('Variant of') }} <a href="{{frappe.db.get_value('Item', variant_of, 'route')}}">{{ variant_of }}</a>
</span>
- </div>
- <div class="col-sm-2 col-xs-3 text-right col-amount">
- {{ d.get_formatted("amount") }}
- <p class="text-muted small item-rate">{{ _("Rate") }} {{ d.get_formatted("rate") }}</p>
- </div>
-</div>
-{% endfor %}
\ No newline at end of file
+ {% endif %}
+ <div class="mt-2">
+ <textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">{{d.additional_notes or ''}}</textarea>
+ </div>
+ </td>
+ <td class="text-right">
+ <div class="input-group number-spinner">
+ <span class="input-group-prepend d-none d-sm-inline-block">
+ <button class="btn btn-outline-secondary cart-btn" data-dir="dwn">–</button>
+ </span>
+ <input class="form-control text-right cart-qty border-secondary" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
+ <span class="input-group-append d-none d-sm-inline-block">
+ <button class="btn btn-outline-secondary cart-btn" data-dir="up">+</button>
+ </span>
+ </div>
+ </td>
+ {% if cart_settings.enable_checkout %}
+ <td class="text-right">
+ <div>
+ {{ d.get_formatted('amount') }}
+ </div>
+ <span class="text-muted">
+ {{ _('Rate:') }} {{ d.get_formatted('rate') }}
+ </span>
+ </td>
+ {% endif %}
+</tr>
+{% endfor %}
diff --git a/erpnext/templates/includes/footer/footer_extension.html b/erpnext/templates/includes/footer/footer_extension.html
index 23a6a34..8cf3081 100644
--- a/erpnext/templates/includes/footer/footer_extension.html
+++ b/erpnext/templates/includes/footer/footer_extension.html
@@ -1,11 +1,14 @@
{% if not hide_footer_signup %}
-<div class='input-group input-group-sm pull-right footer-subscribe'>
- <input class="form-control" type="text" id="footer-subscribe-email"
- placeholder="{{ _('Your email address') }}...">
- <span class='input-group-btn'>
- <button class="btn btn-default" type="button"
- id="footer-subscribe-button">{{ _("Get Updates") }}</button>
- </span>
+<div class="input-group">
+ <input type="text" class="form-control border-secondary"
+ id="footer-subscribe-email"
+ placeholder="{{ _('Your email address...') }}"
+ aria-label="{{ _('Your email address...') }}"
+ aria-describedby="footer-subscribe-button">
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary"
+ type="button" id="footer-subscribe-button">{{ _("Get Updates") }}</button>
+ </div>
</div>
<script>
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index e9d5f56..faf5e92 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -1,2 +1 @@
-<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">
- Powered by ERPNext</a>
+<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index 863d48e..2d27915 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -1,7 +1,4 @@
{% macro product_image_square(website_image, css_class="") %}
-{% if website_image -%}
- <meta itemprop="image" content="{{ frappe.utils.quoted(website_image) | abs_url }}"></meta>
-{%- endif %}
<div class="product-image product-image-square
{% if not website_image -%} missing-image {%- endif %} {{ css_class }}"
{% if website_image -%}
@@ -11,12 +8,8 @@
{% endmacro %}
{% macro product_image(website_image, css_class="") %}
- <div class="product-image {% if not website_image -%} missing-image {%- endif %} {{ css_class }}">
- {% if website_image -%}
- <a href="{{ frappe.utils.quoted(website_image) }}">
- <img itemprop="image" src="{{ frappe.utils.quoted(website_image) | abs_url }}" class="img-responsive">
- </a>
- {%- endif %}
+ <div class="border text-center rounded h-100 {{ css_class }}" style="overflow: hidden;">
+ <img itemprop="image" class="website-image h-100 w-100" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
</div>
{% endmacro %}
@@ -33,3 +26,35 @@
{%- endif %}
</div>
{% endmacro %}
+
+{% macro render_homepage_section(section) %}
+
+{% if section.section_based_on == 'Custom HTML' and section.section_html %}
+ {{ section.section_html }}
+{% elif section.section_based_on == 'Cards' %}
+<section class="container my-5">
+ <h3>{{ section.name }}</h3>
+
+ <div class="row">
+ {% for card in section.section_cards %}
+ <div class="col-md-{{ section.column_value }} mb-4">
+ <div class="card h-100 justify-content-between">
+ {% if card.image %}
+ <div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ card.image }}" data-alt="{{ card.title }}"></div>
+ {% endif %}
+ <div class="card-body">
+ <h5 class="card-title">{{ card.title }}</h5>
+ <p class="card-subtitle mb-2 text-muted">{{ card.subtitle or '' }}</p>
+ <p class="card-text">{{ card.content | truncate(140, True) }}</p>
+ </div>
+ <div class="card-body flex-grow-0">
+ <a href="{{ card.route }}" class="card-link">{{ _('More details') }}</a>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+</section>
+{% endif %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index faf8adf..4daf0e7 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -1,12 +1,10 @@
{% extends 'frappe/templates/includes/navbar/navbar_items.html' %}
{% block navbar_right_extension %}
- <li class="shopping-cart hidden">
- <div class="cart-icon">
- <a class="dropdown-toggle" href="#" data-toggle="dropdown" id="navLogin">
- {{ _("Cart") }} <span class="badge-wrapper" id="cart-count"></span>
- </a>
- <div id="cart-overlay" class="dropdown-menu shopping-cart-menu"></div>
- </div>
+ <li class="shopping-cart cart-icon hidden">
+ <a href="/cart" class="nav-link">
+ {{ _("Cart") }}
+ <span class="badge badge-primary" id="cart-count"></span>
+ </a>
</li>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html
index c2dff8c..da4fb8c 100644
--- a/erpnext/templates/includes/order/order_macros.html
+++ b/erpnext/templates/includes/order/order_macros.html
@@ -9,7 +9,9 @@
</div>
<div class="col-xs-8 col-sm-10">
{{ d.item_code }}
- <div class="text-muted small item-description">{{ d.description }}</div>
+ <div class="text-muted small item-description">
+ {{ html2text(d.description) | truncate(140) }}
+ </div>
</div>
</div>
{% endmacro %}
@@ -25,14 +27,14 @@
{{ d.item_name|truncate(25) }}
<div class="input-group number-spinner">
<span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="dwn">
+ <button class="btn btn-light cart-btn" data-dir="dwn">
–</button>
</span>
<input class="form-control text-right cart-qty"
value = "{{ d.get_formatted('qty') }}"
data-item-code="{{ d.item_code }}">
<span class="input-group-btn">
- <button class="btn btn-default cart-btn" data-dir="up">
+ <button class="btn btn-light cart-btn" data-dir="up">
+</button>
</span>
</div>
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index 462d77d..1d26700 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -1,24 +1,32 @@
{% if doc.taxes %}
-<div class="row tax-net-total-row">
- <div class="col-xs-6 text-right">{{ _("Net Total") }}</div>
- <div class="col-xs-6 text-right">
- {{ doc.get_formatted("net_total") }}</div>
-</div>
+<tr>
+ <td class="text-right" colspan="2">
+ {{ _("Net Total") }}
+ </td>
+ <td class="text-right">
+ {{ doc.get_formatted("net_total") }}
+ </td>
+</tr>
{% endif %}
+
{% for d in doc.taxes %}
{% if d.base_tax_amount > 0 %}
-<div class="row tax-row">
- <div class="col-xs-6 text-right">{{ d.description }}</div>
- <div class="col-xs-6 text-right">
- {{ d.get_formatted("base_tax_amount") }}</div>
-</div>
+<tr>
+ <td class="text-right" colspan="2">
+ {{ d.description }}
+ </td>
+ <td class="text-right">
+ {{ d.get_formatted("base_tax_amount") }}
+ </td>
+</tr>
{% endif %}
{% endfor %}
-<div class="row tax-grand-total-row">
- <div class="col-xs-6 text-right text-uppercase h6 text-muted">{{ _("Grand Total") }}</div>
- <div class="col-xs-6 text-right">
- <span class="tax-grand-total bold">
- {{ doc.get_formatted("grand_total") }}
- </span>
- </div>
-</div>
+
+<tr>
+ <th class="text-right" colspan="2">
+ {{ _("Grand Total") }}
+ </th>
+ <th class="text-right">
+ {{ doc.get_formatted("grand_total") }}
+ </th>
+</tr>
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
deleted file mode 100644
index ef69e20..0000000
--- a/erpnext/templates/includes/product_page.js
+++ /dev/null
@@ -1,215 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ready(function() {
- window.item_code = $('[itemscope] [itemprop="productID"]').text().trim();
- var qty = 0;
-
- frappe.call({
- type: "POST",
- method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
- args: {
- item_code: get_item_code()
- },
- callback: function(r) {
- if(r.message) {
- if(r.message.cart_settings.enabled) {
- $(".item-cart, .item-price, .item-stock").toggleClass("hide", (!!!r.message.product_info.price || !!!r.message.product_info.in_stock));
- }
- if(r.message.cart_settings.show_price) {
- $(".item-price").toggleClass("hide", false);
- }
- if(r.message.cart_settings.show_stock_availability) {
- $(".item-stock").toggleClass("hide", false);
- }
- if(r.message.product_info.price) {
- $(".item-price")
- .html(r.message.product_info.price.formatted_price_sales_uom + "<div style='font-size: small'>\
- (" + r.message.product_info.price.formatted_price + " / " + r.message.product_info.uom + ")</div>");
-
- if(r.message.product_info.in_stock==0) {
- $(".item-stock").html("<div style='color: red'> <i class='fa fa-close'></i> {{ _("Not in stock") }}</div>");
- }
- else if(r.message.product_info.in_stock==1) {
- var qty_display = "{{ _("In stock") }}";
- if (r.message.product_info.show_stock_qty) {
- qty_display += " ("+r.message.product_info.stock_qty+")";
- }
- $(".item-stock").html("<div style='color: green'>\
- <i class='fa fa-check'></i> "+qty_display+"</div>");
- }
-
- if(r.message.product_info.qty) {
- qty = r.message.product_info.qty;
- toggle_update_cart(r.message.product_info.qty);
- } else {
- toggle_update_cart(0);
- }
- }
- }
- }
- })
-
- $("#item-add-to-cart button").on("click", function() {
- frappe.provide('erpnext.shopping_cart');
-
- erpnext.shopping_cart.update_cart({
- item_code: get_item_code(),
- qty: $("#item-spinner .cart-qty").val(),
- callback: function(r) {
- if(!r.exc) {
- toggle_update_cart(1);
- qty = 1;
- }
- },
- btn: this,
- });
- });
-
- $("#item-spinner").on('click', '.number-spinner button', function () {
- var btn = $(this),
- input = btn.closest('.number-spinner').find('input'),
- oldValue = input.val().trim(),
- newVal = 0;
-
- if (btn.attr('data-dir') == 'up') {
- newVal = parseInt(oldValue) + 1;
- } else if (btn.attr('data-dir') == 'dwn') {
- if (parseInt(oldValue) > 1) {
- newVal = parseInt(oldValue) - 1;
- }
- else {
- newVal = parseInt(oldValue);
- }
- }
- input.val(newVal);
- });
-
- $("[itemscope] .item-view-attribute .form-control").on("change", function() {
- try {
- var item_code = encodeURIComponent(get_item_code());
-
- } catch(e) {
- // unable to find variant
- // then chose the closest available one
-
- var attribute = $(this).attr("data-attribute");
- var attribute_value = $(this).val();
- var item_code = find_closest_match(attribute, attribute_value);
-
- if (!item_code) {
- frappe.msgprint(__("Cannot find a matching Item. Please select some other value for {0}.", [attribute]))
- throw e;
- }
- }
-
- if (window.location.search == ("?variant=" + item_code) || window.location.search.includes(item_code)) {
- return;
- }
-
- window.location.href = window.location.pathname + "?variant=" + item_code;
- });
-
- // change the item image src when alternate images are hovered
- $(document.body).on('mouseover', '.item-alternative-image', (e) => {
- const $alternative_image = $(e.currentTarget);
- const src = $alternative_image.find('img').prop('src');
- $('.item-image img').prop('src', src);
- });
-});
-
-var toggle_update_cart = function(qty) {
- $("#item-add-to-cart").toggle(qty ? false : true);
- $("#item-update-cart")
- .toggle(qty ? true : false)
- .find("input").val(qty);
- $("#item-spinner").toggle(qty ? false : true);
-}
-
-function get_item_code() {
- var variant_info = window.variant_info;
- if(variant_info) {
- var attributes = get_selected_attributes();
- var no_of_attributes = Object.keys(attributes).length;
-
- for(var i in variant_info) {
- var variant = variant_info[i];
-
- if (variant.attributes.length < no_of_attributes) {
- // the case when variant has less attributes than template
- continue;
- }
-
- var match = true;
- for(var j in variant.attributes) {
- if(attributes[variant.attributes[j].attribute]
- != variant.attributes[j].attribute_value
- ) {
- match = false;
- break;
- }
- }
- if(match) {
- return variant.name;
- }
- }
- throw "Unable to match variant";
- } else {
- return window.item_code;
- }
-}
-
-function find_closest_match(selected_attribute, selected_attribute_value) {
- // find the closest match keeping the selected attribute in focus and get the item code
-
- var attributes = get_selected_attributes();
-
- var previous_match_score = 0;
- var previous_no_of_attributes = 0;
- var matched;
-
- var variant_info = window.variant_info;
- for(var i in variant_info) {
- var variant = variant_info[i];
- var match_score = 0;
- var has_selected_attribute = false;
-
- for(var j in variant.attributes) {
- if(attributes[variant.attributes[j].attribute]===variant.attributes[j].attribute_value) {
- match_score = match_score + 1;
-
- if (variant.attributes[j].attribute==selected_attribute && variant.attributes[j].attribute_value==selected_attribute_value) {
- has_selected_attribute = true;
- }
- }
- }
-
- if (has_selected_attribute
- && ((match_score > previous_match_score) || (match_score==previous_match_score && previous_no_of_attributes < variant.attributes.length))) {
- previous_match_score = match_score;
- matched = variant;
- previous_no_of_attributes = variant.attributes.length;
-
-
- }
- }
-
- if (matched) {
- for (var j in matched.attributes) {
- var attr = matched.attributes[j];
- $('[itemscope]')
- .find(repl('.item-view-attribute .form-control[data-attribute="%(attribute)s"]', attr))
- .val(attr.attribute_value);
- }
-
- return matched.name;
- }
-}
-
-function get_selected_attributes() {
- var attributes = {};
- $('[itemscope]').find(".item-view-attribute .form-control").each(function() {
- attributes[$(this).attr('data-attribute')] = $(this).val();
- });
- return attributes;
-}
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index fb0c05f..b301fc0 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -2,18 +2,25 @@
{% block title %} {{ _("Shopping Cart") }} {% endblock %}
-{% block header %}<h2>{{ _("My Cart") }}</h2>{% endblock %}
+{% block header %}<h1>{{ _("Shopping Cart") }}</h1>{% endblock %}
+<!--
{% block script %}
<script>{% include "templates/includes/cart.js" %}</script>
{% endblock %}
+-->
{% block header_actions %}
-{% if doc.items %}
-<button class="btn btn-primary btn-place-order btn-sm"
- type="button">
- {{ _("Place Order") }}</button>
+{% if doc.items and cart_settings.enable_checkout %}
+<button class="btn btn-primary btn-place-order" type="button">
+ {{ _("Place Order") }}
+</button>
+{% endif %}
+{% if doc.items and not cart_settings.enable_checkout %}
+<button class="btn btn-primary btn-request-for-quotation" type="button">
+ {{ _("Request for Quotation") }}
+</button>
{% endif %}
{% endblock %}
@@ -22,58 +29,89 @@
{% from "templates/includes/macros.html" import item_name_and_description %}
<div class="cart-container">
- <div id="cart-container">
- <div id="cart-error" class="alert alert-danger"
- style="display: none;"></div>
- <div id="cart-items">
- <div class="row cart-item-header text-muted">
- <div class="col-sm-8 col-xs-6 h6 text-uppercase">
- {{ _("Item") }}
- </div>
- <div class="col-sm-2 col-xs-3 text-center h6 text-uppercase">
- {{ _("Qty") }}
- </div>
- <div class="col-sm-2 col-xs-3 text-right h6 text-uppercase">
- {{ _("Subtotal") }}
- </div>
- </div>
- {% if doc.items %}
- <div class="cart-items">
- {% include "templates/includes/cart/cart_items.html" %}
- </div>
- {% else %}
- <p class="empty-cart">{{ _("Cart is Empty") }}</p>
- {% endif %}
- </div>
- {% if doc.items %}
- <!-- taxes -->
- <div class="row cart-taxes">
- <div class="col-sm-6"><!-- empty --></div>
- <div class="col-sm-6 text-right cart-tax-items">
+ <div id="cart-error" class="alert alert-danger" style="display: none;"></div>
+
+ {% if doc.items %}
+ <table class="table table-bordered mt-3">
+ <thead>
+ <tr>
+ <th width="60%">{{ _('Item') }}</th>
+ <th width="20%" class="text-right">{{ _('Quantity') }}</th>
+ {% if cart_settings.enable_checkout %}
+ <th width="20%" class="text-right">{{ _('Subtotal') }}</th>
+ {% endif %}
+ </tr>
+ </thead>
+ <tbody class="cart-items">
+ {% include "templates/includes/cart/cart_items.html" %}
+ </tbody>
+ {% if cart_settings.enable_checkout %}
+ <tfoot class="cart-tax-items">
{% include "templates/includes/order/order_taxes.html" %}
- </div>
- </div>
-
- {% if doc.tc_name %}
- <div class="cart-terms" style="display: none;" title={{doc.tc_name}}>
- {{doc.tc_name}}
- {{doc.terms}}
- </div>
- <div class="cart-link">
- <a href="#" onclick="show_terms();return false;">*{{ __("Terms and Conditions") }}</a>
- </div>
+ </tfoot>
{% endif %}
+ </table>
+ {% else %}
+ <p class="text-muted">{{ _('Your cart is Empty') }}</p>
+ {% endif %}
- <div class="cart-addresses">
- {% include "templates/includes/cart/cart_address.html" %}
+ {% if doc.items %}
+ {% if doc.tc_name %}
+ <div class="terms-and-conditions-link">
+ <a href class="link-terms-and-conditions" data-terms-name="{{ doc.tc_name }}">
+ {{ _("Terms and Conditions") }}
+ </a>
+ <script>
+ frappe.ready(() => {
+ $('.link-terms-and-conditions').click((e) => {
+ e.preventDefault();
+ const $link = $(e.target);
+ const terms_name = $link.attr('data-terms-name');
+ show_terms_and_conditions(terms_name);
+ })
+ });
+ function show_terms_and_conditions(terms_name) {
+ frappe.call('erpnext.shopping_cart.cart.get_terms_and_conditions', { terms_name })
+ .then(r => {
+ frappe.msgprint({
+ title: terms_name,
+ message: r.message
+ });
+ });
+ }
+ </script>
</div>
+ {% endif %}
- <p class="cart-footer text-right">
- <button class="btn btn-primary btn-place-order btn-sm" type="button">
- {{ _("Place Order") }}</button></p>
+ {% if cart_settings.enable_checkout %}
+ <div class="cart-addresses mt-5">
+ {% include "templates/includes/cart/cart_address.html" %}
+ </div>
+ {% endif %}
+ {% endif %}
+</div>
+
+<div class="row mt-5">
+ <div class="col-12">
+ {% if cart_settings.enable_checkout %}
+ <a href="/orders">
+ {{ _('See past orders') }}
+ </a>
+ {% else %}
+ <a href="/quotations">
+ {{ _('See past quotations') }}
+ </a>
{% endif %}
</div>
</div>
+{% endblock %}
+{% block base_scripts %}
+<!-- js should be loaded in body! -->
+<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
+<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
+<script type="text/javascript" src="/assets/js/control.min.js"></script>
+<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
+<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
{% endblock %}
diff --git a/erpnext/templates/pages/help.html b/erpnext/templates/pages/help.html
index 8c26852..1cfe358 100644
--- a/erpnext/templates/pages/help.html
+++ b/erpnext/templates/pages/help.html
@@ -11,7 +11,7 @@
value='{{ frappe.form_dict.q or ''}}'
{% if not frappe.form_dict.q%}placeholder="{{ _("What do you need help with?") }}"{% endif %}>
<input type='submit'
- class='btn btn-sm btn-default btn-search' value="{{ _("Search") }}">
+ class='btn btn-sm btn-light btn-search' value="{{ _("Search") }}">
</form>
</div>
diff --git a/erpnext/templates/pages/home.css b/erpnext/templates/pages/home.css
new file mode 100644
index 0000000..cf54766
--- /dev/null
+++ b/erpnext/templates/pages/home.css
@@ -0,0 +1,9 @@
+/* csslint ignore:start */
+{% if homepage.hero_image %}
+.hero-image {
+ background-image: url("{{ homepage.hero_image }}");
+ background-size: cover;
+ padding: 10rem 0;
+}
+{% endif %}
+/* csslint ignore:end */
\ No newline at end of file
diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html
index f36b4e0..b67a465 100644
--- a/erpnext/templates/pages/home.html
+++ b/erpnext/templates/pages/home.html
@@ -1,75 +1,75 @@
{% extends "templates/web.html" %}
-{% from "erpnext/templates/includes/macros.html" import product_image_square %}
-{% block page_content %}
+{% from "erpnext/templates/includes/macros.html" import render_homepage_section %}
-<div class="row">
- <div class="col-sm-12">
- <div class="hero">
- <h1 class="text-center">{{ homepage.tag_line or '' }}</h1>
- <p class="text-center">{{ homepage.description or '' }}</p>
+{% block content %}
+<main>
+ {% if homepage.hero_section_based_on == 'Default' %}
+ <section class="hero-section border-bottom {%if homepage.hero_image%}hero-image{%endif%}">
+ <div class="container py-5">
+ <h1 class="d-none d-sm-block display-4">{{ homepage.tag_line }}</h1>
+ <h1 class="d-block d-sm-none">{{ homepage.tag_line }}</h1>
+ <h2 class="d-none d-sm-block">{{ homepage.description }}</h2>
+ <h3 class="d-block d-sm-none">{{ homepage.description }}</h3>
</div>
- {% if homepage.products %}
- <div class='featured-products-section' itemscope itemtype="http://schema.org/Product">
- <h5 class='featured-product-heading'>{{ _("Featured Products") }}</h5>
- <div class="featured-products">
- <div id="search-list" class="row" style="margin-top:40px;">
- {% for item in homepage.products %}
- <a class="product-link" href="{{ item.route|abs_url }}">
- <div class="col-sm-4 col-xs-4 product-image-wrapper">
- <div class="product-image-img">
- <!-- thumbnail not updated, and used as background image in item card -->
- {{ product_image_square(item.image) }}
- <div class="product-text" itemprop="name">{{ item.item_name }}</div>
- </div>
- </div>
- </a>
- {% endfor %}
+
+ <div class="container">
+ <a href="{{ explore_link }}" class="mb-5 btn btn-primary">{{ _('Explore') }}</a>
+ </div>
+ </section>
+ {% elif homepage.hero_section_based_on == 'Slideshow' and slideshow %}
+ <section class="hero-section">
+ {% include "templates/includes/slideshow.html" %}
+ </section>
+ {% elif homepage.hero_section_based_on == 'Homepage Section' %}
+ {{ render_homepage_section(homepage.hero_section_doc) }}
+ {% endif %}
+
+ {% if homepage.products %}
+ <section class="container section-products my-5">
+ <h3>{{ _('Products') }}</h3>
+
+ <div class="row">
+ {% for item in homepage.products %}
+ <div class="col-md-4 mb-4">
+ <div class="card h-100 justify-content-between">
+ <div class="website-image-lazy" data-class="card-img-top h-100" data-src="{{ item.image }}" data-alt="{{ item.item_name }}"></div>
+ <div class="card-body flex-grow-0">
+ <h5 class="card-title">{{ item.item_name }}</h5>
+ <a href="{{ item.route }}" class="card-link">{{ _('More details') }}</a>
+ </div>
</div>
</div>
- <div class="text-center padding">
- <a href="{{ homepage.products_url or "/products" }}" class="btn btn-primary all-products">
- {{ _("View All Products") }}</a></div>
+ {% endfor %}
</div>
- {% endif %}
- </div>
-</div>
-{% endblock %}
+ </section>
+ {% endif %}
-{% block style %}
-<style>
- .hero {
- padding-top: 50px;
- padding-bottom: 100px;
- }
+ {% if blogs %}
+ <section class="container my-5">
+ <h3>{{ _('Publications') }}</h3>
- .hero h1 {
- font-size: 40px;
- font-weight: 200;
- }
+ <div class="row">
+ {% for blog in blogs %}
+ <div class="col-md-4 mb-4">
+ <div class="card h-100">
+ <div class="card-body">
+ <h5 class="card-title">{{ blog.title }}</h5>
+ <p class="card-subtitle mb-2 text-muted">{{ _('By {0}').format(blog.blogger) }}</p>
+ <p class="card-text">{{ blog.blog_intro }}</p>
+ </div>
+ <div class="card-body flex-grow-0">
+ <a href="{{ blog.route }}" class="card-link">{{ _('Read blog') }}</a>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+ </section>
+ {% endif %}
- .home-login {
- margin-top: 30px;
- }
- .btn-login {
- width: 80px;
- }
-
- .featured-product-heading, .all-products {
- text-transform: uppercase;
- letter-spacing: 0.5px;
- font-size: 12px;
- font-weight: 500;
- }
-
- .all-products {
- font-weight: 300;
- padding-left: 25px;
- padding-right: 25px;
- padding-top: 10px;
- padding-bottom: 10px;
- }
-
-
-</style>
-{% endblock %}
+ {% for section in homepage_sections %}
+ {{ render_homepage_section(section) }}
+ {% endfor %}
+</main>
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py
index 82d525a..4b688b1 100644
--- a/erpnext/templates/pages/home.py
+++ b/erpnext/templates/pages/home.py
@@ -15,15 +15,38 @@
if route:
item.route = '/' + route
- context.title = homepage.title or homepage.company
-
- # show atleast 3 products
- if len(homepage.products) < 3:
- for i in range(3 - len(homepage.products)):
- homepage.append('products', {
- 'item_code': 'product-{0}'.format(i),
- 'item_name': frappe._('Product {0}').format(i),
- 'route': '#'
- })
-
+ homepage.title = homepage.title or homepage.company
+ context.title = homepage.title
context.homepage = homepage
+
+ if homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section:
+ homepage.hero_section_doc = frappe.get_doc('Homepage Section', homepage.hero_section)
+
+ if homepage.slideshow:
+ doc = frappe.get_doc('Website Slideshow', homepage.slideshow)
+ context.slideshow = homepage.slideshow
+ context.slideshow_header = doc.header
+ context.slides = doc.slideshow_items
+
+ context.blogs = frappe.get_all('Blog Post',
+ fields=['title', 'blogger', 'blog_intro', 'route'],
+ filters={
+ 'published': 1
+ },
+ order_by='modified desc',
+ limit=3
+ )
+
+ # filter out homepage section which is used as hero section
+ homepage_hero_section = homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section
+ homepage_sections = frappe.get_all('Homepage Section',
+ filters=[['name', '!=', homepage_hero_section]] if homepage_hero_section else None,
+ order_by='section_order asc'
+ )
+ context.homepage_sections = [frappe.get_doc('Homepage Section', name) for name in homepage_sections]
+
+ context.metatags = context.metatags or frappe._dict({})
+ context.metatags.image = homepage.hero_image or None
+ context.metatags.description = homepage.description or None
+
+ context.explore_link = '/all-products'
diff --git a/erpnext/templates/pages/material_request_info.html b/erpnext/templates/pages/material_request_info.html
index ff3bd65..9d18989 100644
--- a/erpnext/templates/pages/material_request_info.html
+++ b/erpnext/templates/pages/material_request_info.html
@@ -12,7 +12,7 @@
{% endblock %}
{% block header_actions %}
-<a class='btn btn-xs btn-default' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
+<a class='btn btn-xs btn-light' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
{% endblock %}
{% block page_content %}
@@ -70,5 +70,5 @@
{% endif %}
{% endfor %}
</div>
-</div>
+</div>
{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/non_profit/leave-chapter.html b/erpnext/templates/pages/non_profit/leave-chapter.html
index 009c7af..bc4242f 100644
--- a/erpnext/templates/pages/non_profit/leave-chapter.html
+++ b/erpnext/templates/pages/non_profit/leave-chapter.html
@@ -9,7 +9,7 @@
<label for="leave">Why do you want to leave this chapter</label>
<input type="text" name="leave" class="form-control" id="leave">
</div>
- <button type="button" class="btn btn-default btn-leave" data-title= "{{ chapter.name }}" id="btn-leave">Submit
+ <button type="button" class="btn btn-light btn-leave" data-title= "{{ chapter.name }}" id="btn-leave">Submit
</button>
</form>
</div>
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 64fd32a..67a8fed 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -8,23 +8,22 @@
{% block title %}{{ doc.name }}{% endblock %}
{% block header %}
- <h1>{{ doc.name }}</h1>
+ <h1 class="m-0">{{ doc.name }}</h1>
{% endblock %}
{% block header_actions %}
-<a class='btn btn-xs btn-default' href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
+<a href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}' target="_blank" rel="noopener noreferrer">{{ _("Print") }}</a>
{% endblock %}
{% block page_content %}
<div class="row transaction-subheading">
- <div class="col-xs-6">
-
+ <div class="col-6">
<span class="indicator {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
</span>
</div>
- <div class="col-xs-6 text-muted text-right small">
+ <div class="col-6 text-muted text-right small">
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }}
{% if doc.valid_till %}
<p>
@@ -34,16 +33,14 @@
</div>
</div>
-<p class='small' style='padding-top: 15px;'>
-{% if doc.doctype == 'Supplier Quotation' %}
- <b>{{ doc.supplier_name}}</b>
-{% else %}
- <b>{{ doc.customer_name}}</b>
-{% endif %}
-{% if doc.contact_display %}
- <br>
- {{ doc.contact_display }}
-{% endif %}
+<p class="small my-3">
+ {%- set party_name = doc.supplier_name if doc.doctype == 'Supplier Quotation' else doc.customer_name %}
+ <b>{{ party_name }}</b>
+
+ {% if doc.contact_display and doc.contact_display != party_name %}
+ <br>
+ {{ doc.contact_display }}
+ {% endif %}
</p>
{% if doc._header %}
@@ -55,7 +52,7 @@
<!-- items -->
<div class="order-item-table">
<div class="row order-items order-item-header text-muted">
- <div class="col-sm-6 col-xs-6 h6 text-uppercase">
+ <div class="col-sm-6 col-6 h6 text-uppercase">
{{ _("Item") }}
</div>
<div class="col-sm-3 col-xs-3 text-right h6 text-uppercase">
@@ -67,7 +64,7 @@
</div>
{% for d in doc.items %}
<div class="row order-items">
- <div class="col-sm-6 col-xs-6">
+ <div class="col-sm-6 col-6">
{{ item_name_and_description(d) }}
</div>
<div class="col-sm-3 col-xs-3 text-right">
@@ -85,11 +82,10 @@
</div>
<!-- taxes -->
- <div class="order-taxes row">
- <div class="col-sm-6"><!-- empty --></div>
- <div class="col-sm-6 text-right">
+ <div class="order-taxes d-flex justify-content-end">
+ <table>
{% include "erpnext/templates/includes/order/order_taxes.html" %}
- </div>
+ </table>
</div>
</div>
@@ -115,7 +111,7 @@
<div class="control-input">
<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
</div>
- <p class="help-box small text-muted hidden-xs"> Available Points: {{ available_loyalty_points }} </p>
+ <p class="help-box small text-muted d-none d-sm-block"> Available Points: {{ available_loyalty_points }} </p>
</div>
</div>
{% endif %}
diff --git a/erpnext/templates/pages/product_search.html b/erpnext/templates/pages/product_search.html
index f9efd48..6a5425b 100644
--- a/erpnext/templates/pages/product_search.html
+++ b/erpnext/templates/pages/product_search.html
@@ -25,7 +25,7 @@
<div style="text-align: center;">
<div class="more-btn"
style="display: none; text-align: center;">
- <button class="btn btn-default">{{ _("More...") }}</button>
+ <button class="btn btn-light">{{ _("More...") }}</button>
</div>
</div>
</div>
diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html
index baa2ae6..7e294e0 100644
--- a/erpnext/templates/pages/projects.html
+++ b/erpnext/templates/pages/projects.html
@@ -20,11 +20,11 @@
aria-valuemin="0" aria-valuemax="100" style="width:{{ doc.percent_complete|round|int }}%;">
</div>
</div>
-{% endif %}
+{% endif %}
<div class="clearfix">
<h4 style="float: left;">{{ _("Tasks") }}</h4>
- <a class="btn btn-secondary btn-default btn-sm" style="float: right; position: relative; top: 10px;" href='/tasks?new=1&project={{ doc.project_name }}'>{{ _("New task") }}</a>
+ <a class="btn btn-secondary btn-light btn-sm" style="float: right; position: relative; top: 10px;" href='/tasks?new=1&project={{ doc.project_name }}'>{{ _("New task") }}</a>
</div>
<p>
diff --git a/erpnext/templates/pages/task_info.html b/erpnext/templates/pages/task_info.html
index 6cfac28..6cd6a7e 100644
--- a/erpnext/templates/pages/task_info.html
+++ b/erpnext/templates/pages/task_info.html
@@ -20,7 +20,7 @@
<div class="page-header-actions-block" data-html-block="header-actions">
<button type="submit" class="btn btn-primary btn-sm btn-form-submit">
{{ __("Update") }}</button>
- <a href="tasks" class="btn btn-default btn-sm">
+ <a href="tasks" class="btn btn-light btn-sm">
{{ __("Cancel") }}</a>
</div>
</div>
@@ -91,7 +91,7 @@
{% endfor %}
</div>
<div class="comment-form-wrapper">
- <a class="add-comment btn btn-default btn-sm">{{ __("Add Comment") }}</a>
+ <a class="add-comment btn btn-light btn-sm">{{ __("Add Comment") }}</a>
<div style="display: none;" id="comment-form">
<p>{{ __("Add Comment") }}</p>
<form>