kfet -- Tables are sortable

Many tables in kfet app templates become sortable:
account list, negative account list, article list, article inventory
list, article supplier list, article category list, checkout list,
checkout statement list, inventory list, inventory details, order list,
order creation, order details.

This is achieved thanks to the jQuery plugin 'tablesorter':
https://mottie.github.io/tablesorter/docs/

- Affected tables also got sticky headers (it stays visible on scroll).
- Dates format are modified in order to ease the date sorting with the
plugin (it avoids writing a custom parser, or an extractor from
additional hidden element in the table cells).
- Tables whose content is classified by category (of articles) now uses
several tbodies. This has minor effects on the tables style.
- Tags of the header help signs become 'i', instead of 'span', in order
to avoid weird spacing.
This commit is contained in:
Aurélien Delobelle 2017-11-27 18:24:22 +01:00
parent 36ce038050
commit 32720c56a6
20 changed files with 6331 additions and 149 deletions

View file

@ -75,6 +75,10 @@ ul {
padding:8px !important;
}
.table thead .sm-padding {
padding:3px !important;
}
.table tr.section {
background: #c63b52 !important;
color:#fff;

View file

@ -3,6 +3,7 @@
/* Libs customizations */
@import url("libs/jconfirm-kfet.css");
@import url("libs/jquery-tablesorter-kfet.css");
@import url("libs/multiple-select-kfet.css");
/* Base */
@ -54,6 +55,11 @@
color: #C81022;
}
.table thead .glyphicon {
font-size: 12px;
opacity: 0.8;
}
/*
* Pages tableaux seuls
@ -82,6 +88,11 @@
border-radius: 0;
}
.table td.small-width {
/* Header still extends the width of the column, but it will be minimal. */
width: 30px;
}
.auth-form {
padding: 15px 0;
background: #d86c7e;

View file

@ -235,3 +235,77 @@ function submit_url(el) {
let url = $(el).data('url');
create_form(url).appendTo($('body')).submit();
}
/**
* jquery-tablesorter
* https://mottie.github.io/tablesorter/docs/
*
* Known bugs (v2.29.0):
* - Sort order icons in sticky headers are not updated.
* Status: Fixed in next release.
*
* TODO:
* - Handle i18n.
*/
function registerBoolParser(id, true_str, false_str) {
$.tablesorter.addParser({
id: id,
format: function(s) {
return s.toLowerCase()
.replace(true_str, 1)
.replace(false_str, 0);
},
type: 'numeric'
});
}
// Parsers for the text representations of boolean.
registerBoolParser('yesno', 'oui', 'non');
registerBoolParser('article__is_sold', 'en vente', 'non vendu');
registerBoolParser('article__hidden', 'caché', 'affiché');
// https://mottie.github.io/tablesorter/docs/index.html#variable-defaults
$.extend(true, $.tablesorter.defaults, {
headerTemplate: '{content} {icon}',
cssIconAsc : 'glyphicon glyphicon-chevron-up',
cssIconDesc : 'glyphicon glyphicon-chevron-down',
cssIconNone : 'glyphicon glyphicon-resize-vertical',
// Only four-digits format year is handled by the builtin parser
// 'shortDate'.
dateFormat: 'ddmmyyyy',
// Accented characters are replaced with their non-accented one.
sortLocaleCompare: true,
// French format: 1 234,56
usNumberFormat: false,
widgets: ['stickyHeaders'],
widgetOptions: {
stickyHeaders_offset: '.navbar',
}
});
// https://mottie.github.io/tablesorter/docs/index.html#variable-language
$.extend($.tablesorter.language, {
sortAsc : 'Trié par ordre croissant, ',
sortDesc : 'Trié par ordre décroissant, ',
sortNone : 'Non trié, ',
sortDisabled : 'tri désactivé et/ou non-modifiable',
nextAsc : 'cliquer pour trier par ordre croissant',
nextDesc : 'cliquer pour trier par ordre décroissant',
nextNone : 'cliquer pour retirer le tri'
});
$( function() {
$('.sortable').tablesorter();
});

File diff suppressed because it is too large Load diff

View file

@ -37,13 +37,16 @@
<section>
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(trigramme,asc)]
data-sortlist="[[0,0]]">
<thead>
<tr>
<td class="text-center">Tri.</td>
<td>Nom</td>
<td class="text-right">Balance</td>
<td class="text-center">COF</td>
<td class="text-center" data-sorter="yesno">COF</td>
<td>Dpt</td>
<td class="text-center">Promo</td>
</tr>

View file

@ -35,16 +35,19 @@
{% block main %}
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(trigramme,asc)]
data-sortlist="[[0,0]]">
<thead>
<tr>
<td class="text-center">Tri.</td>
<td>Nom</td>
<td class="text-right">Balance</td>
<td class="text-right">Réelle</td>
<td>Début</td>
<td data-sorter="shortDate">Début</td>
<td>Découvert autorisé</td>
<td>Jusqu'au</td>
<td data-sorter="shortDate">Jusqu'au</td>
<td>Balance offset</td>
</tr>
</thead>
@ -63,9 +66,13 @@
{{ neg.account.real_balance|floatformat:2 }}€
{% endif %}
</td>
<td>{{ neg.start|date:'d/m/Y H:i:s'}}</td>
<td title="{{ neg.start }}">
{{ neg.start|date:'d/m/Y H:i'}}
</td>
<td>{{ neg.authz_overdraft_amount|default_if_none:'' }}</td>
<td>{{ neg.authz_overdrafy_until|default_if_none:'' }}</td>
<td title="{{ neg.authz_overdraft_until }}">
{{ neg.authz_overdraft_until|date:'d/m/Y H:i' }}
</td>
<td>{{ neg.balance_offset|default_if_none:'' }}</td>
</tr>
{% endfor %}

View file

@ -26,40 +26,50 @@
{% block main %}
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(is_sold,desc), (name,asc)]
data-sortlist="[[3,1], [0,0]]">
<thead>
<tr>
<td>Nom</td>
<td class="text-right">Prix</td>
<td class="text-right">Stock</td>
<td class="text-right">En vente</td>
<td class="text-right">Affiché</td>
<td class="text-right">Dernier inventaire</td>
<td class="text-right" data-sorter="article__is_sold">En vente</td>
<td class="text-right" data-sorter="article__hidden">Affiché</td>
<td class="text-right" data-sorter="shortDate">Dernier inventaire</td>
</tr>
</thead>
<tbody>
{% for article in articles %}
{% ifchanged article.category %}
<tr class="section">
<td colspan="6">{{ article.category.name }}</td>
</tr>
{% endifchanged %}
<tr>
<td>
<a href="{% url 'kfet.article.read' article.pk %}">
{{ article.name }}
</a>
</td>
<td class="text-right">{{ article.price }}€</td>
<td class="text-right">{{ article.stock }}</td>
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
<td class="text-right">{{ article.inventory.0.at }}</td>
</tr>
{% endfor %}
</tbody>
{% regroup articles by category as category_list %}
{% for category in category_list %}
<tbody class="tablesorter-no-sort">
<tr class="section">
<td colspan="6">{{ category.grouper }}</td>
</tr>
</tbody>
<tbody>
{% for article in category.list %}
<tr>
<td>
<a href="{% url 'kfet.article.read' article.pk %}">
{{ article.name }}
</a>
</td>
<td class="text-right">{{ article.price }}€</td>
<td class="text-right">{{ article.stock }}</td>
<td class="text-right">{{ article.is_sold | yesno:"En vente,Non vendu"}}</td>
<td class="text-right">{{ article.hidden | yesno:"Caché,Affiché" }}</td>
{% with last_inventory=article.inventory.0 %}
<td class="text-right" title="{{ last_inventory.at }}">
{{ last_inventory.at|date:'d/m/Y H:i' }}
</td>
{% endwith %}
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

View file

@ -1,7 +1,10 @@
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(inventory.at,desc)]
data-sortlist="[[0,1]]">
<thead>
<tr>
<td>Date</td>
<td data-sorter="shortDate">Date</td>
<td>Stock</td>
<td>Erreur</td>
</tr>
@ -9,9 +12,9 @@
<tbody>
{% for inventoryart in inventoryarts %}
<tr>
<td>
<td title="{{ inventoryart.inventory.at }}">
<a href="{% url "kfet.inventory.read" inventoryart.inventory.pk %}">
{{ inventoryart.inventory.at }}
{{ inventoryart.inventory.at|date:'d/m/Y H:i' }}
</a>
</td>
<td>{{ inventoryart.stock_new }}</td>

View file

@ -1,7 +1,10 @@
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(at,desc)]
data-sortlist="[[0,1]]">
<thead>
<tr>
<td>Date</td>
<td data-sorter="shortDate">Date</td>
<td>Fournisseur</td>
<td>HT</td>
<td>TVA</td>
@ -11,7 +14,9 @@
<tbody>
{% for supplierart in supplierarts %}
<tr>
<td>{{ supplierart.at }}</td>
<td title="{{ supplierart.at }}">
{{ supplierart.at|date:'d/m/Y' }}
</td>
<td>{{ supplierart.supplier.name }}</td>
<td>{{ supplierart.price_HT|default_if_none:"" }}</td>
<td>{{ supplierart.TVA|default_if_none:"" }}</td>

View file

@ -24,6 +24,7 @@
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script type="text/javascript" src="{% static 'kfet/js/jquery-ui.min.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/jquery-confirm.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/vendor/jquery-tablesorter/jquery.tablesorter.combined.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/reconnecting-websocket.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment.js' %}"></script>
<script type="text/javascript" src="{% static 'kfet/js/moment-fr.js' %}"></script>

View file

@ -17,12 +17,15 @@
{% block main %}
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(name,asc)]
data-sortlist="[[0,0]]">
<thead>
<tr>
<td>Nom</td>
<td class="text-right">Nombre d'articles</td>
<td class="text-right">Peut être majorée</td>
<td class="text-right" data-sorter="yesno">Peut être majorée</td>
</tr>
</thead>
<tbody>

View file

@ -24,13 +24,16 @@
{% block main %}
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(valid_to,desc)]
data-sortlist="[[3,1]]">
<thead>
<tr>
<td>Nom</td>
<td class="text-right">Balance</td>
<td class="text-right">Déb. valid.</td>
<td class="text-right">Fin valid.</td>
<td class="text-right" data-parser="shortDate">Déb. valid.</td>
<td class="text-right" data-parser="shortDate">Fin valid.</td>
<td class="text-right">Protégée</td>
</tr>
</thead>
@ -43,8 +46,12 @@
</a>
</td>
<td class="text-right">{{ checkout.balance}}€</td>
<td class="text-right">{{ checkout.valid_from }}</td>
<td class="text-right">{{ checkout.valid_to }}</td>
<td class="text-right" title="{{ checkout.valid_from }}">
{{ checkout.valid_from|date:'d/m/Y H:i' }}
</td>
<td class="text-right" title="{{ checkout.valid_to }}">
{{ checkout.valid_to|date:'d/m/Y H:i' }}
</td>
<td class="text-right">{{ checkout.is_protected|yesno }}</td>
</tr>
{% endfor %}

View file

@ -14,10 +14,13 @@
{% if not statements %}
Pas de relevé
{% else %}
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(at,desc)]
data-sortlist="[[0,1]]">
<thead>
<tr>
<td>Date/heure</td>
<td data-sorter="shortDate">Date/heure</td>
<td>Montant pris</td>
<td>Montant laissé</td>
<td>Erreur</td>
@ -25,9 +28,9 @@
<tbody>
{% for statement in statements %}
<tr>
<td>
<td title="{{ statement.at }}">
<a href="{% url 'kfet.checkoutstatement.update' checkout.pk statement.pk %}">
{{ statement.at }}
{{ statement.at|date:'d/m/Y H:i' }}
</a>
</td>
<td>{{ statement.amount_taken }}</td>

View file

@ -17,10 +17,13 @@
{% block main %}
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(at,desc)]
data-sortlist="[[0,1]]">
<thead>
<tr>
<td>Date</td>
<td data-sorter="shortDate">Date</td>
<td>Par</td>
<td>Nb articles</td>
</tr>
@ -28,9 +31,9 @@
<tbody>
{% for inventory in inventories %}
<tr>
<td>
<td title="{{ inventory.at }}">
<a href="{% url 'kfet.inventory.read' inventory.pk %}">
<span>{{ inventory.at }}</span>
{{ inventory.at|date:'d/m/Y H:i' }}
</a>
</td>
<td>{{ inventory.by }}</td>

View file

@ -27,7 +27,10 @@
{% block main %}
<div class="table-responsive">
<table class="table table-condensed">
<table
class="table table-condensed table-hover table-striped sortable"
// Initial sort: [(article.name,asc)]
data-sortlist="[[0,0]]">
<thead>
<tr>
<td>Article</td>
@ -36,25 +39,28 @@
<td>Erreur</td>
</tr>
</thead>
<tbody>
{% for inventoryart in inventoryarts %}
{% ifchanged inventoryart.article.category %}
<tr class="section">
<td colspan="4">{{ inventoryart.article.category.name }}</td>
</tr>
{% endifchanged %}
<tr>
<td>
<a href="{% url "kfet.article.read" inventoryart.article.id %}">
{{ inventoryart.article.name }}
</a>
</td>
<td>{{ inventoryart.stock_old }}</td>
<td>{{ inventoryart.stock_new }}</td>
<td>{{ inventoryart.stock_error }}</td>
{% regroup inventoryarts by article.category as category_list %}
{% for category in category_list %}
<tbody class="tablesorter-no-sort">
<tr class="section">
<td colspan="4">{{ category.grouper.name }}</td>
</tr>
{% endfor %}
</tbody>
</tbody>
<tbody>
{% for inventoryart in category.list %}
<tr>
<td>
<a href="{% url "kfet.article.read" inventoryart.article.id %}">
{{ inventoryart.article.name }}
</a>
</td>
<td>{{ inventoryart.stock_old }}</td>
<td>{{ inventoryart.stock_new }}</td>
<td>{{ inventoryart.stock_error }}</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
</div>

View file

@ -55,11 +55,14 @@
<section>
<h2>Liste des commandes</h2>
<div class="table-responsive">
<table class="table table-hover table-condensed">
<table
class="table table-hover table-condensed sortable"
// Initial sort: [(at,desc)]
data-sortlist="[[1,1]]">
<thead>
<tr>
<td></td>
<td>Date</td>
<td data-sorter="false"></td>
<td data-parser="shortDate">Date</td>
<td>Fournisseur</td>
<td>Inventaire</td>
</tr>
@ -74,9 +77,9 @@
</a>
{% endif %}
</td>
<td>
<td tile="{{ order.at }}">
<a href="{% url 'kfet.order.read' order.pk %}">
{{ order.at }}
{{ order.at|date:'d/m/Y H:i' }}
</a>
</td>
<td>{{ order.supplier }}</td>

View file

@ -11,60 +11,79 @@
<form action="" method="post">
{% csrf_token %}
<div class="table-responsive">
<table class="table table-hover table-condensed table-condensed-input text-center table-striped">
<table
class="table table-hover table-condensed table-condensed-input text-center table-striped sortable"
// Initial sort: [(name,asc)]
data-sortlist="[[0,0]]">
<thead>
<tr>
<td rowspan="2">Article</td>
<td colspan="{{ scale|length }}">Ventes
<span class='glyphicon glyphicon-question-sign' title="Ventes des 5 dernières semaines" data-placement="bottom"></span>
</td>
<td rowspan="2">V. moy.<br>
<span class='glyphicon glyphicon-question-sign' title="Moyenne des ventes" data-placement="bottom"></span>
</td>
<td rowspan="2">E.T.<br>
<span class='glyphicon glyphicon-question-sign' title="Écart-type des ventes" data-placement="bottom"></span>
</td>
<td rowspan="2">Prév.<br>
<span class='glyphicon glyphicon-question-sign' title="Prévision de ventes" data-placement="bottom"></span>
</td>
<td colspan="{{ scale|length }}">
Ventes
<i class='glyphicon glyphicon-question-sign' title="Ventes des 5 dernières semaines" data-placement="bottom"></i>
</td>
<td rowspan="2">
V. moy.
<br>
<i class='glyphicon glyphicon-question-sign' title="Moyenne des ventes" data-placement="bottom"></i>
</td>
<td rowspan="2" data-sorter="false">
E.T.
<br>
<i class='glyphicon glyphicon-question-sign' title="Écart-type des ventes" data-placement="bottom"></i>
</td>
<td rowspan="2">
Prév.
<br>
<i class='glyphicon glyphicon-question-sign' title="Prévision de ventes" data-placement="bottom"></i>
</td>
<td rowspan="2">Stock</td>
<td rowspan="2">Box<br>
<span class='glyphicon glyphicon-question-sign' title="Capacité d'une boite" data-placement="bottom"></span>
</td>
<td rowspan="2">Rec.<br>
<span class='glyphicon glyphicon-question-sign' title="Quantité conseillée" data-placement="bottom"></span>
</td>
<td rowspan="2">Commande</td>
<td rowspan="2" data-sorter="false">
Box
<br>
<i class='glyphicon glyphicon-question-sign' title="Capacité d'une boite" data-placement="bottom"></i>
</td>
<td rowspan="2">
Rec.
<br>
<i class='glyphicon glyphicon-question-sign' title="Quantité conseillée" data-placement="bottom"></i>
</td>
<td rowspan="2" data-sorter="false" class="small-width">
Commande
</td>
</tr>
<tr>
{% for label in scale.get_labels %}
<td>{{ label }}</td>
<td class="sm-padding">{{ label }}</td>
{% endfor %}
</tr>
</thead>
<tbody>
{% for form in formset %}
{% ifchanged form.category %}
<tr class='section text-left'>
<td colspan="{{ scale|length|add:'8' }}">{{ form.category_name }}</td>
</tr>
{% endifchanged %}
<tr>
{{ form.article }}
<td class="text-left">{{ form.name }}</td>
{% for v_chunk in form.v_all %}
<td>{{ v_chunk }}</td>
{% endfor %}
<td>{{ form.v_moy }}</td>
<td>{{ form.v_et }}</td>
<td>{{ form.v_prev }}</td>
<td>{{ form.stock }}</td>
<td>{{ form.box_capacity|default:"" }}</td>
<td>{{ form.c_rec }}</td>
<td class="nopadding">{{ form.quantity_ordered | add_class:"form-control" }}</td>
{% regroup formset by category_name as category_list %}
{% for category in category_list %}
<tbody class="tablesorter-no-sort">
<tr class='section text-left'>
<td colspan="{{ scale|length|add:'8' }}">{{ category.grouper }}</td>
</tr>
{% endfor %}
</tbody>
</tbody>
<tbody>
{% for form in category.list %}
<tr>
{{ form.article }}
<td class="text-left">{{ form.name }}</td>
{% for v_chunk in form.v_all %}
<td>{{ v_chunk }}</td>
{% endfor %}
<td>{{ form.v_moy }}</td>
<td>{{ form.v_et }}</td>
<td>{{ form.v_prev }}</td>
<td>{{ form.stock }}</td>
<td>{{ form.box_capacity|default:"" }}</td>
<td>{{ form.c_rec }}</td>
<td class="nopadding">{{ form.quantity_ordered|add_class:"form-control" }}</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
</div>
{{ formset.management_form }}

View file

@ -42,7 +42,10 @@
<section>
<h2>Détails</h2>
<div class="table-responsive">
<table class="table table-condensed">
<table
class="table table-condensed table-hover table-striped sortable"
// Initial sort: [(article.name,asc)]
data-sortlist="[[0,0]]">
<thead>
<tr>
<td>Article</td>
@ -51,32 +54,35 @@
<td>Reçu</td>
</tr>
</thead>
<tbody>
{% for orderart in orderarts %}
{% ifchanged orderart.article.category %}
<tr class="section">
<td colspan="4">{{ orderart.article.category.name }}</td>
</tr>
{% endifchanged %}
<tr>
<td>
<a href="{% url "kfet.article.read" orderart.article.id %}">
{{ orderart.article.name }}
</a>
</td>
<td>{{ orderart.quantity_ordered }}</td>
<td>
{% if orderart.article.box_capacity %}
{# c'est une division ! #}
{% widthratio orderart.quantity_ordered orderart.article.box_capacity 1 %}
{% endif %}
</td>
<td>
{{ orderart.quantity_received|default_if_none:'' }}
</td>
{% regroup orderarts by article.category as category_list %}
{% for category in category_list %}
<tbody class="tablesorter-no-sort">
<tr class="section">
<td colspan="4">{{ category.grouper.name }}</td>
</tr>
{% endfor %}
</tbody>
</tbody>
<tbody>
{% for orderart in category.list %}
<tr>
<td>
<a href="{% url "kfet.article.read" orderart.article.id %}">
{{ orderart.article.name }}
</a>
</td>
<td>{{ orderart.quantity_ordered }}</td>
<td>
{% if orderart.article.box_capacity %}
{# c'est une division ! #}
{% widthratio orderart.quantity_ordered orderart.article.box_capacity 1 %}
{% endif %}
</td>
<td>
{{ orderart.quantity_received|default_if_none:'' }}
</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</table>
</div>
</section>

View file

@ -1839,7 +1839,7 @@ def order_create(request, pk):
else:
formset = cls_formset(initial=initial)
scale.label_fmt = "S -{rev_i}"
scale.label_fmt = "S-{rev_i}"
return render(request, 'kfet/order_create.html', {
'supplier': supplier,