Interface K-Psul

- Création style kpsul.css
- Affichage trigramme et données du compte. Couleurs en fonction de
  l'état du compte (négatif, pas beaucoup, gelé, ok)
- Affichage de la sélection et des données de la caisse
- Ajout des boutons pour les charges et retraits (juste les boutons)
- Ajout du champ d'autocomplétion pour les articles (et ça autocomplète bien)
- Correction css général
- K-Psul JS: utilisation de $.extend pour les données récupérées en
  ajax/websocket pour utiliser les valeurs par défaut (plus joli)
This commit is contained in:
Aurélien Delobelle 2016-08-16 03:36:14 +02:00
parent 39af9afd5b
commit 82f0dd9638
6 changed files with 414 additions and 60 deletions

View file

@ -136,7 +136,11 @@ class KPsulAccountForm(forms.ModelForm):
model = Account model = Account
fields = ['trigramme'] fields = ['trigramme']
widgets = { widgets = {
'trigramme': forms.TextInput(attrs={'autocomplete': 'off'}), 'trigramme': forms.TextInput(
attrs={
'autocomplete': 'off',
'spellcheck': 'false',
}),
} }
class KPsulCheckoutForm(forms.Form): class KPsulCheckoutForm(forms.Form):

View file

@ -1,4 +1,5 @@
@import url("nav.css"); @import url("nav.css");
@import url("kpsul.css");
body { body {
margin-top:50px; margin-top:50px;
@ -13,8 +14,8 @@ a:focus, a:hover {
color:#C8102E; color:#C8102E;
} }
.btn-group .btn+.btn, .btn-group .btn+.btn-group, .btn-group .btn-group+.btn, .btn-group .btn-group+.btn-group { :focus {
margin-left:5px; outline:none;
} }
.btn, .btn-lg, .btn-group-lg>.btn { .btn, .btn-lg, .btn-group-lg>.btn {
@ -33,12 +34,9 @@ a:focus, a:hover {
color:#FFF; color:#FFF;
} }
.container-fluid .row:first-child { .row-page-header {
background-color:rgba(200,16,46,1); background-color:rgba(200,16,46,1);
color:#FFF; color:#FFF;
}
.row.row-page-header {
border-bottom:3px solid #000; border-bottom:3px solid #000;
} }

View file

@ -0,0 +1,236 @@
/*
* Top row
*/
.row.kpsul_top {
padding:0 15px;
color:#000;
}
.row.kpsul_top > div {
margin-top:15px;
}
/* Account */
#account {
border:1px solid #ddd;
color:black;
height:160px;
}
#account[data-balance="ok"] { border-color:#009011; }
#account[data-balance="ok"] #account_form input { background:#009011; color:#FFF;}
#account[data-balance="low"] { border-color:#EC6400; }
#account[data-balance="low"] #account_form input { background:#EC6400; color:#FFF; }
#account[data-balance="neg"] { border-color:#C8102E; }
#account[data-balance="neg"] #account_form input { background:#C8102E; color:#FFF; }
#account[data-balance="frozen"] { border-color:#000FBA; }
#account[data-balance="frozen"] #account_form input { background:#000FBA; color:#FFF; }
#account_form {
padding:0;
height:100%;
}
#account_form input {
width:100%;
height:100%;
padding:0;
padding-bottom:10px;
border:0;
border-radius:0;
background:#ddd;
font-family:'Roboto Mono';
font-size:70px;
font-weight:bold;
text-align:center;
text-transform:uppercase;
}
@media (min-width: 768px) {
#account {
margin-right:0;
}
#account_form input {
font-size:85px;
}
@media (min-width: 992px) {
#account_form input {
font-size:100px;
}
}
}
#account_data {
height:100%;
}
#account_data .data_line {
line-height:24px;
font-family:'Roboto Mono';
font-size:14px;
}
#account_data #account-balance{
height:60px;
line-height:60px;
font-size:60px;
font-weight:bold;
}
#account_data #account-name {
font-weight:bold;
}
/* Checkout */
#checkout {
border:1px solid #ddd;
padding:0;
height:160px;
font-family:'Roboto Mono';
}
#checkout_form select {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
cursor:pointer;
outline:none;
border:0;
width:100%;
height:50px;
padding:0 15px;
background:#ddd;
font-weight:bold;
font-size:18px;
}
#checkout_form select option {
height:25px;
padding:0 15px;
line-height:25px;
font-weight:normal;
font-size:14px;
}
#checkout_data {
padding:0 15px;
}
/*
* Second part
*/
.row.kpsul_middle {
padding:0 15px;
font-family:'Roboto Mono';
color:#000;
}
.row.kpsul_middle > div {
margin-top:15px;
padding:0;
}
.row.kpsul_middle > div:first-child > div {
margin-right:0;
}
@media (min-width:768px) {
.row.kpsul_middle > div:first-child > div {
margin-right:15px
}
}
/* Special operations */
#special_operations {
height:40px;
}
#special_operations button {
height:100%;
width:50%;
float:left;
background:#ddd;
color:#000;
font-size:18px;
font-weight:bold;
}
#special_operations button:focus {
outline:none;
}
#operation_deposit:focus, #operation_deposit:hover {
background:#009011;
color:#FFF;
}
#operation_withdraw:focus, #operation_withdraw:hover {
background:#C8102E;
color:#FFF;
}
/* Article autocomplete */
#article_selection {
height:40px;
width:100%;
}
#article_selection input {
height:100%;
float:left;
border:1px solid #ddd;
border-radius:0;
border-top:0;
font-size:16px;
font-weight:bold;
}
#article_selection input+input {
border-left:0;
}
#article_selection input[type=number] {
-moz-appearance:textfield;
}
#article_selection input[type=number]::-webkit-inner-spin-button,
#article_selection input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
#article_autocomplete {
width:95%;
padding-left:10px;
}
#article_number {
width:5%;
text-align:center;
}
/* Article data */

View file

@ -9,7 +9,7 @@
{# CSS #} {# CSS #}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href='https://fonts.googleapis.com/css?family=Roboto|Oswald:400,700' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Roboto|Oswald:400,700|Roboto+Mono:400,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="{% static 'kfet/css/index.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'kfet/css/index.css' %}">
{# JS #} {# JS #}

View file

@ -16,48 +16,79 @@
<form id="operationgroup_form">{{ operationgroup_form }}</form> <form id="operationgroup_form">{{ operationgroup_form }}</form>
{{ trigramme_form.as_p }} <div class="row kpsul_top">
<div id="account_data"> <div class="col-sm-8">
<p id="account-balance"></p> <div class="row" id="account">
<p id="account-name"></p> <div class="col-lg-3 col-xs-4" id="account_form">
<p id="account-email"></p> {{ trigramme_form.trigramme }}
<p id="account-is_cof"></p> </div>
<p id="account-promo"></p> <div class="col-lg-9 col-xs-8" id="account_data">
<p id="account-is_frozen"></p> <div id="account-balance" class="data_line"></div>
<p id="account-departement"></p> <div id="account-name" class="data_line"></div>
<p id="account-nickname"></p> <div id="account-nickname" class="data_line"></div>
<div class="data_line">
<span id="account-is_cof"></span>
<span id="account-departement"></span>
<span id="account-promo"></span>
</div>
<div id="account-email" class="data_line"></div>
</div>
</div>
</div>
<div class="col-sm-4" id="checkout">
<div id="checkout_form">
{{ checkout_form.checkout }}
</div>
<div id="checkout_data">
<div>
<b>En caisse:</b> <span id="checkout-balance"></span>
</div>
<div>
<b>Dernier relevé: </b><br>
<span id="checkout-last_statement_balance"></span>
à <span id="checkout-last_statement_at"></span>
par <span id="checkout-last_statement_by_trigramme"></span>
</div>
</div>
</div>
</div> </div>
{{ checkout_form.as_p }} <div class="row kpsul_middle">
<div id="checkout_data"> <div class="col-sm-8">
<p id="checkout-name"></p> <div>
<p id="checkout-balance"></p> <div id="special_operations">
<p id="checkout-valid_from"></p> <button role="button" class="btn" id="operation_deposit">Charge</button>
<p id="checkout-valid_to"></p> <button role="button" class="btn" id="operation_withdraw">Retrait</button>
<p id="checkout-last_statement_balance"></p> </div>
<p id="checkout-last_statement_at"></p> <div id="article_selection">
<p id="checkout-last_statement_by_trigramme"></p> <input type="text" id="article_autocomplete">
<p id="checkout-last_statement_by_first_name"></p> <input type="number" id="article_number" step="1" min="1">
<p id="checkout-last_statement_by_last_name"></p> <input type="hidden" id="article_id" value="">
</div>
<div id="articles_data">
<table>
<thead>
<tr>
<td>Nom</td>
<td>Catégorie</td>
<td>Prix</td>
<td>Stock</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-sm-4">
</div>
</div> </div>
<form id="operation_formset"> <form id="operation_formset">
{{ operation_formset.as_p }} {{ operation_formset.as_p }}
</form> </form>
<table id="articles_data">
<thead>
<tr>
<td>Nom</td>
<td>Catégorie</td>
<td>Prix</td>
<td>Stock</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<button type="button" id="perform_operations">Valider</button> <button type="button" id="perform_operations">Valider</button>
<form id="cancel_form"> <form id="cancel_form">
@ -106,6 +137,7 @@ $(document).ready(function() {
'is_cof' : false, 'is_cof' : false,
'promo' : '', 'promo' : '',
'balance': '', 'balance': '',
'trigramme' : '',
'is_frozen' : false, 'is_frozen' : false,
'departement': '', 'departement': '',
'nickname' : '', 'nickname' : '',
@ -113,10 +145,19 @@ $(document).ready(function() {
// Display data // Display data
function displayAccountData() { function displayAccountData() {
for (var elem in account_data_default) { for (var elem in account_data) {
$('#account-'+elem).text( $('#account-'+elem).text(account_data[elem]);
account_data[elem] ? account_data[elem] : account_data_default[elem] }
); if (account_data['is_frozen']) {
$('#account').attr('data-balance', 'frozen');
} else if (account_data['balance'] == '') {
$('#account').attr('data-balance', '');
} else if (account_data['balance'] >= 5 || account_data['trigramme'] == 'LIQ') {
$('#account').attr('data-balance', 'ok');
} else if (account_data['balance'] >= 0) {
$('#account').attr('data-balance', 'low');
} else {
$('#account').attr('data-balance', 'neg');
} }
} }
@ -129,7 +170,7 @@ $(document).ready(function() {
// Store data // Store data
function storeAccountData(data) { function storeAccountData(data) {
account_data = data account_data = $.extend({}, account_data_default, data);
$('#id_on_acc').val(account_data['pk']); $('#id_on_acc').val(account_data['pk']);
displayAccountData(); displayAccountData();
} }
@ -148,7 +189,7 @@ $(document).ready(function() {
// Event listener // Event listener
triInput.on('input', function() { triInput.on('input', function() {
var tri = triInput.val() var tri = triInput.val().toUpperCase();
// Checking if tri is valid to avoid sending requests // Checking if tri is valid to avoid sending requests
if (tri.match(triPattern)) { if (tri.match(triPattern)) {
retrieveAccountData(tri); retrieveAccountData(tri);
@ -179,10 +220,8 @@ $(document).ready(function() {
// Display data // Display data
function displayCheckoutData() { function displayCheckoutData() {
for (var elem in checkout_data_default) { for (var elem in checkout_data) {
$('#checkout-'+elem).text( $('#checkout-'+elem).text(checkout_data[elem]);
checkout_data[elem] ? checkout_data[elem] : checkout_data_default[elem]
);
} }
} }
@ -195,7 +234,7 @@ $(document).ready(function() {
// Store data // Store data
function storeCheckoutData(data) { function storeCheckoutData(data) {
checkout_data = data; checkout_data = $.extend({}, checkout_data_default, data);
$('#id_checkout').val(checkout_data['id']); $('#id_checkout').val(checkout_data['id']);
displayCheckoutData(); displayCheckoutData();
} }
@ -290,6 +329,8 @@ $(document).ready(function() {
article_html.find('.'+elem).text(article[elem]) article_html.find('.'+elem).text(article[elem])
} }
articles_container.append(article_html); articles_container.append(article_html);
// Pour l'autocomplétion
articlesList.push([article['name'],article['id']]);
} }
function getArticlesData() { function getArticlesData() {
@ -305,6 +346,81 @@ $(document).ready(function() {
}); });
} }
// -----
// Article selection
// -----
var articleSelect = $('#article_autocomplete');
var articleId = $('#article_id');
var articleNb = $('#article_number');
// 8:Backspace|9:Tab|13:Enter|116:F5|117:F6|122:F11|123:F12
var normalKeys = /^(8|9|13|116|117|122|123)$/;
var articlesList = [];
var articlesMatch = [];
function deleteNonMatching(array, str) {
var dup = [];
var lower_str = str.toLowerCase();
for (var i=0; i<array.length; i++) {
if (((array[i][0]).toLowerCase()).indexOf(lower_str) === 0)
dup.push(array[i])
}
return dup;
}
function callbackForPrefix(elt) {
return elt[0].toLowerCase();
}
function sharedPrefix(array) {
var dup = array.map(callbackForPrefix);
dup.sort(); // On trie l'array
// On récupère le préfixe du premier et du dernier élément
var first = dup[0], last = dup[array.length-1],
length = first.length, i = 0;
while (i < length && first.charAt(i) === last.charAt(i)) i++;
return first.substring(0, i);
}
articleSelect.on('keypress', function(e) {
// Comportement normal pour ces touches
if (normalKeys.test(e.keyCode) || e.ctrlKey) {
if (e.keyCode == 8)
articleId.val(0);
if (e.charCode == 97 && e.ctrlKey) {
articleId.val(0);
articleSelect.val('');
}
return true;
} else if (e.charCode !== 0) {
var text = articleSelect.val();
if (!text)
// On part de rien donc on charge tout
articlesMatch = articlesList.slice();
var articlesMatch_old = articlesMatch;
// Filtrage des articles correspondant avec le caractère en plus
articlesMatch = deleteNonMatching(articlesMatch_old, text + e.key.toLowerCase());
if (articlesMatch.length == 0) {
// Pas de résultat, cette lettre n'est pas utilisable
// On refuse et on restaure articlesMatch
articlesMatch = articlesMatch_old;
return false;
} else if (articlesMatch.length == 1) {
// 1 seul résultat, victoire
articleId.val(articlesMatch[0][1]);
articleSelect.val(articlesMatch[0][0]);
return false;
} else {
articleId.val(0);
// Plusieurs résultats, on calcule le préfixe commun
articleSelect.val(sharedPrefix(articlesMatch));
return false;
}
}
return false;
});
// ----- // -----
// History // History
// ----- // -----
@ -377,13 +493,11 @@ $(document).ready(function() {
// Synchronization // Synchronization
// ----- // -----
websocket_msg_default = {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}
socket = new ReconnectingWebSocket("ws://" + window.location.host + "/k-fet/k-psul/"); socket = new ReconnectingWebSocket("ws://" + window.location.host + "/k-fet/k-psul/");
socket.onmessage = function(e) { socket.onmessage = function(e) {
data = JSON.parse(e.data); data = $.extend({}, websocket_msg_default, JSON.parse(e.data));
data['opegroups'] = data['opegroups'] || [];
data['opes'] = data['opes'] || [];
data['checkouts'] = data['checkouts'] || [];
data['articles'] = data['articles'] || [];
for (var i=0; i<data['opegroups'].length; i++) { for (var i=0; i<data['opegroups'].length; i++) {
if (data['opegroups'][i]['add']) { if (data['opegroups'][i]['add']) {
@ -414,9 +528,10 @@ $(document).ready(function() {
// Initiliazing all // Initiliazing all
// ----- // -----
resetAccountData(); resetAccountData();
resetCheckoutData(); checkoutInput.change();
getHistory(); getHistory();
getArticlesData(); getArticlesData();
articleId.val(0);
}); });
</script> </script>

View file

@ -417,7 +417,8 @@ def kpsul_account_data(request):
data = { 'pk': account.pk, 'name': account.name, 'email': account.email, data = { 'pk': account.pk, 'name': account.name, 'email': account.email,
'is_cof': account.is_cof, 'promo': account.promo, 'is_cof': account.is_cof, 'promo': account.promo,
'balance': account.balance, 'is_frozen': account.is_frozen, 'balance': account.balance, 'is_frozen': account.is_frozen,
'departement': account.departement, 'nickname': account.nickname } 'departement': account.departement, 'nickname': account.nickname,
'trigramme': account.trigramme }
return JsonResponse(data) return JsonResponse(data)
@permission_required('kfet.is_team') @permission_required('kfet.is_team')