mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-10 03:49:24 +00:00
counter: make click page dynamic to avoid repetitive loading
This makes the whole click page load only once for a normal click workflow. The current basket is now rendered client side with Vue.JS, and the backend view is able to answer with JSON if asked to. This should lighten the workflow a lot on the client side, especially with poor connectivity, and the server should also feel lighter during big events, due to far less complex Jinja pages to render.
This commit is contained in:
@ -1,23 +1,6 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
{% from "core/macros.jinja" import user_mini_profile, user_subscription %}
|
||||
|
||||
|
||||
{% macro add_product(id, content, class="") %}
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="{{ class }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_product">
|
||||
<button type="submit" name="product_id" value="{{ id }}"> {{ content|safe }} </button>
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro del_product(id, content, class="") %}
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="{{ class }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="del_product">
|
||||
<button type="submit" name="product_id" value="{{ id }}"> {{ content }} </button>
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
||||
{% block title %}
|
||||
{{ counter }}
|
||||
{% endblock %}
|
||||
@ -58,108 +41,177 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="bar_ui">
|
||||
<h5>{% trans %}Selling{% endtrans %}</h5>
|
||||
<div>
|
||||
<div class="important">
|
||||
{% if request.session['too_young'] %}
|
||||
<p><strong>{% trans %}Too young for that product{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
{% if request.session['not_allowed'] %}
|
||||
<p><strong>{% trans %}Not allowed for that product{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
{% if request.session['no_age'] %}
|
||||
<p><strong>{% trans %}No date of birth provided{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
{% if request.session['not_enough'] %}
|
||||
<p><strong>{% trans %}Not enough money{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
<noscript>
|
||||
<p class="important">Javascript is required for the counter UI.</p>
|
||||
</noscript>
|
||||
<div id="click_form">
|
||||
<h5>{% trans %}Selling{% endtrans %}</h5>
|
||||
<div>
|
||||
|
||||
<div class="important">
|
||||
<p v-for="error in errors"><strong>{{ error }}</strong></p>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="code_form" @submit.prevent="handle_code">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="code">
|
||||
<input type="input" name="code" value="" class="focus" id="code_field"/>
|
||||
<input type="submit" value="{% trans %}Go{% endtrans %}" />
|
||||
</form>
|
||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||
|
||||
{% raw %}
|
||||
<ul>
|
||||
<li v-for="p_info,p_id in basket">
|
||||
|
||||
<form method="post" action="" class="inline del_product_form" @submit.prevent="handle_action">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" v-bind:value="js_csrf_token">
|
||||
<input type="hidden" name="action" value="del_product">
|
||||
<input type="hidden" name="product_id" v-bind:value="p_id">
|
||||
<button type="submit"> - </button>
|
||||
</form>
|
||||
|
||||
{{ p_info["qty"] + p_info["bonus_qty"] }}
|
||||
|
||||
<form method="post" action="" class="inline add_product_form" @submit.prevent="handle_action">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" v-bind:value="js_csrf_token">
|
||||
<input type="hidden" name="action" value="add_product">
|
||||
<input type="hidden" name="product_id" v-bind:value="p_id">
|
||||
<button type="submit"> + </button>
|
||||
</form>
|
||||
|
||||
{{ products[p_id].name }}: {{ (p_info["qty"]*p_info["price"]/100).toLocaleString(undefined, { minimumFractionDigits: 2 }) }} € <span v-if="p_info['bonus_qty'] > 0">P</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>Total: {{ sum_basket().toLocaleString(undefined, { minimumFractionDigits: 2 }) }} €</strong>
|
||||
</p>
|
||||
{% endraw %}
|
||||
|
||||
<div class="important">
|
||||
<p v-for="error in errors"><strong>{{ error }}</strong></p>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="finish">
|
||||
<input type="submit" value="{% trans %}Finish{% endtrans %}" />
|
||||
</form>
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="cancel">
|
||||
<input type="submit" value="{% trans %}Cancel{% endtrans %}" />
|
||||
</form>
|
||||
</div>
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="code">
|
||||
<input type="input" name="code" value="" class="focus" id="code_field"/>
|
||||
<input type="submit" value="{% trans %}Go{% endtrans %}" />
|
||||
</form>
|
||||
<p>{% trans %}Basket: {% endtrans %}</p>
|
||||
{% if counter.type == 'BAR' %}
|
||||
<h5>{% trans %}Refilling{% endtrans %}</h5>
|
||||
<div>
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
{{ refill_form.as_p() }}
|
||||
<input type="hidden" name="action" value="refill">
|
||||
<input type="submit" value="{% trans %}Go{% endtrans %}" />
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="products">
|
||||
<ul>
|
||||
{% for id,infos in request.session['basket']|dictsort %}
|
||||
{% set product = counter.products.filter(id=id).first() %}
|
||||
{% set s = infos['qty'] * infos['price'] / 100 %}
|
||||
<li>{{ del_product(id, '-', "inline") }} {{ infos['qty'] + infos['bonus_qty'] }} {{ add_product(id, '+', "inline") }}
|
||||
{{ product.name }}: {{ "%0.2f"|format(s) }} €
|
||||
{% if infos['bonus_qty'] %}
|
||||
P
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% for category in categories.keys() -%}
|
||||
<li><a href="#cat_{{ category|slugify }}">{{ category }}</a></li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
<p><strong>{% trans %}Total: {% endtrans %}{{ "%0.2f"|format(basket_total) }} €</strong></p>
|
||||
<div class="important">
|
||||
{% if request.session['too_young'] %}
|
||||
<p><strong>{% trans %}Too young for that product{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
{% if request.session['not_allowed'] %}
|
||||
<p><strong>{% trans %}Not allowed for that product{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
{% if request.session['no_age'] %}
|
||||
<p><strong>{% trans %}No date of birth provided{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
{% if request.session['not_enough'] %}
|
||||
<p><strong>{% trans %}Not enough money{% endtrans %}</strong></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="finish">
|
||||
<input type="submit" value="{% trans %}Finish{% endtrans %}" />
|
||||
</form>
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="cancel">
|
||||
<input type="submit" value="{% trans %}Cancel{% endtrans %}" />
|
||||
</form>
|
||||
</div>
|
||||
{% if counter.type == 'BAR' %}
|
||||
<h5>{% trans %}Refilling{% endtrans %}</h5>
|
||||
<div>
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}">
|
||||
{% csrf_token %}
|
||||
{{ refill_form.as_p() }}
|
||||
<input type="hidden" name="action" value="refill">
|
||||
<input type="submit" value="{% trans %}Go{% endtrans %}" />
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="products">
|
||||
<ul>
|
||||
{% for category in categories.keys() -%}
|
||||
<li><a href="#cat_{{ category|slugify }}">{{ category }}</a></li>
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% for category in categories.keys() -%}
|
||||
<div id="cat_{{ category|slugify }}">
|
||||
<h5>{{ category }}</h5>
|
||||
{% for p in categories[category] -%}
|
||||
{% set file = None %}
|
||||
{% if p.icon %}
|
||||
{% set file = p.icon.url %}
|
||||
{% else %}
|
||||
{% set file = static('core/img/na.gif') %}
|
||||
{% endif %}
|
||||
{% set prod = '<strong>%s</strong><hr><img src="%s" /><span>%s €<br>%s</span>' % (p.name, file, p.selling_price, p.code) %}
|
||||
{{ add_product(p.id, prod, "form_button") }}
|
||||
<div id="cat_{{ category|slugify }}">
|
||||
<h5>{{ category }}</h5>
|
||||
{% for p in categories[category] -%}
|
||||
{% set file = None %}
|
||||
{% if p.icon %}
|
||||
{% set file = p.icon.url %}
|
||||
{% else %}
|
||||
{% set file = static('core/img/na.gif') %}
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url('counter:click', counter_id=counter.id, user_id=customer.user.id) }}" class="form_button add_product_form" @submit.prevent="handle_action">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_product">
|
||||
<input type="hidden" name="product_id" value="{{ p.id }}">
|
||||
<button type="submit"><strong>{{ p.name }}</strong><hr><img src="{{ file }}" /><span>{{ p.selling_price }} €<br>{{ p.code }}</span></button>
|
||||
</form>
|
||||
{%- endfor %}
|
||||
</div>
|
||||
{%- endfor %}
|
||||
</div>
|
||||
{%- endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
{{ super() }}
|
||||
<script src="{{ static('core/js/vue.global.prod.js') }}"></script>
|
||||
<script>
|
||||
$( function() {
|
||||
/* Vue.JS dynamic form */
|
||||
const click_form_vue = Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
js_csrf_token: "{{ csrf_token }}",
|
||||
products: {
|
||||
{% for p in products -%}
|
||||
{{ p.id }}: {
|
||||
code: "{{ p.code }}",
|
||||
name: "{{ p.name }}",
|
||||
selling_price: "{{ p.selling_price }}",
|
||||
special_selling_price: "{{ p.special_selling_price }}",
|
||||
},
|
||||
{%- endfor %}
|
||||
},
|
||||
basket: {{ request.session["basket"]|tojson }},
|
||||
errors: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sum_basket() {
|
||||
var vm = this;
|
||||
var total = 0;
|
||||
for(idx in vm.basket) {
|
||||
var item = vm.basket[idx];
|
||||
console.log(item);
|
||||
total += item["qty"] * item["price"];
|
||||
}
|
||||
return total / 100;
|
||||
},
|
||||
handle_code(event) {
|
||||
var vm = this;
|
||||
var code = $(event.target).find("#code_field").val().toUpperCase();
|
||||
console.log("Code:");
|
||||
console.log(code);
|
||||
if(code == "{% trans %}END{% endtrans %}" || code == "{% trans %}CAN{% endtrans %}") {
|
||||
$(event.target).submit();
|
||||
} else {
|
||||
vm.handle_action(event);
|
||||
}
|
||||
},
|
||||
handle_action(event) {
|
||||
var vm = this;
|
||||
var payload = $(event.target).serialize();
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
dataType: 'json',
|
||||
data: payload,
|
||||
success: function(response) {
|
||||
vm.basket = response.basket;
|
||||
vm.errors = [];
|
||||
},
|
||||
error: function(error) {
|
||||
vm.basket = error.responseJSON.basket;
|
||||
vm.errors = error.responseJSON.errors;
|
||||
}
|
||||
});
|
||||
$('form.code_form #code_field').val("").focus();
|
||||
}
|
||||
}
|
||||
}).mount('#bar_ui');
|
||||
|
||||
/* Autocompletion in the code field */
|
||||
var products_autocomplete = [
|
||||
{% for p in products -%}
|
||||
@ -196,7 +248,7 @@ $( function() {
|
||||
});
|
||||
|
||||
/* Accordion UI between basket and refills */
|
||||
$("#bar_ui").accordion({
|
||||
$("#click_form").accordion({
|
||||
heightStyle: "content",
|
||||
activate: function(event, ui){
|
||||
$(".focus").focus();
|
||||
|
Reference in New Issue
Block a user