Skip to content

Commit

Permalink
Use tomselect instead of jquery autoselect for counter clicks
Browse files Browse the repository at this point in the history
  • Loading branch information
klmp200 committed Dec 19, 2024
1 parent ddeb12f commit ddcb2d8
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base";
import { registerComponent } from "#core:utils/web-components";
import type { RecursivePartial, TomSettings } from "tom-select/dist/types/types";

const productParsingRegex = /^(\d+x)?(.*)/i;

function parseProduct(query: string): [number, string] {
const parsed = productParsingRegex.exec(query);
return [Number.parseInt(parsed[1] || "1"), parsed[2]];
}

@registerComponent("counter-product-select")
export class CounterProductSelect extends AutoCompleteSelectBase {
public getOperationCodes(): string[] {
return ["FIN", "ANN"];
}

protected attachBehaviors(): void {
this.allowMultipleProducts();
}

private allowMultipleProducts(): void {
const search = this.widget.search;
const onOptionSelect = this.widget.onOptionSelect;
this.widget.hook("instead", "search", (query: string) => {
return search.call(this.widget, parseProduct(query)[1]);
});
this.widget.hook(
"instead",
"onOptionSelect",
(evt: MouseEvent | KeyboardEvent, option: HTMLElement) => {
const [quantity, _] = parseProduct(this.widget.inputValue());
const originalValue = option.getAttribute("data-value") ?? option.innerText;

if (quantity === 1 || this.getOperationCodes().includes(originalValue)) {
return onOptionSelect.call(this.widget, evt, option);
}

const value = `${quantity}x${originalValue}`;
const label = `${quantity}x${option.innerText}`;
this.widget.addOption({ value: value, text: label }, true);
return onOptionSelect.call(
this.widget,
evt,
this.widget.getOption(value, true),
);
},
);

this.widget.hook("after", "onOptionSelect", () => {
/* Focus the next element if it's an input */
if (this.nextElementSibling.nodeName === "INPUT") {
(this.nextElementSibling as HTMLInputElement).focus();
}
});
}
protected tomSelectSettings(): RecursivePartial<TomSettings> {
/* We disable the dropdown on focus because we're going to always autofocus the widget */
return { ...super.tomSelectSettings(), openOnFocus: false };
}
}
61 changes: 13 additions & 48 deletions counter/static/bundled/counter/counter-click-index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { exportToHtml } from "#core:utils/globals";
import type TomSelect from "tom-select";

interface CounterConfig {
csrfToken: string;
Expand All @@ -20,6 +21,12 @@ exportToHtml("loadCounter", (config: CounterConfig) => {
basket: config.sessionBasket,
errors: [],
customerBalance: config.customerBalance,
codeField: undefined,

init() {
this.codeField = this.$refs.codeField;
this.codeField.widget.focus();
},

sumBasket() {
if (!this.basket || Object.keys(this.basket).length === 0) {
Expand All @@ -40,17 +47,19 @@ exportToHtml("loadCounter", (config: CounterConfig) => {
(event.detail.target.querySelector("#id_amount") as HTMLInputElement).value,
);
document.getElementById("selling-accordion").click();
this.codeField.widget.focus();
},

async handleCode(event: SubmitEvent) {
const code = (
$(event.target).find("#code_field").val() as string
).toUpperCase();
if (["FIN", "ANN"].includes(code)) {
const widget: TomSelect = this.codeField.widget;
const code = (widget.getValue() as string).toUpperCase();
if (this.codeField.getOperationCodes().includes(code)) {
$(event.target).submit();
} else {
await this.handleAction(event);
}
widget.clear();
widget.focus();
},

async handleAction(event: SubmitEvent) {
Expand All @@ -68,54 +77,12 @@ exportToHtml("loadCounter", (config: CounterConfig) => {
const json = await response.json();
this.basket = json.basket;
this.errors = json.errors;
$("form.code_form #code_field").val("").focus();
},
}));
});
});

interface Product {
value: string;
label: string;
tags: string;
}
declare global {
const productsAutocomplete: Product[];
}

$(() => {
/* Autocompletion in the code field */
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
const codeField: any = $("#code_field");

let quantity = "";
codeField.autocomplete({
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
select: (event: any, ui: any) => {
event.preventDefault();
codeField.val(quantity + ui.item.value);
},
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
focus: (event: any, ui: any) => {
event.preventDefault();
codeField.val(quantity + ui.item.value);
},
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
source: (request: any, response: any) => {
// biome-ignore lint/performance/useTopLevelRegex: performance impact is minimal
const res = /^(\d+x)?(.*)/i.exec(request.term);
quantity = res[1] || "";
const search = res[2];
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
const matcher = new RegExp(($ as any).ui.autocomplete.escapeRegex(search), "i");
response(
$.grep(productsAutocomplete, (value: Product) => {
return matcher.test(value.tags);
}),
);
},
});

/* Accordion UI between basket and refills */
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
($("#click_form") as any).accordion({
Expand All @@ -124,6 +91,4 @@ $(() => {
});
// biome-ignore lint/suspicious/noExplicitAny: dealing with legacy jquery
($("#products") as any).tabs();

codeField.focus();
});
34 changes: 23 additions & 11 deletions counter/templates/counter/counter_click.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
{{ counter }}
{% endblock %}

{% block additional_css %}
<link rel="stylesheet" type="text/css" href="{{ static('bundled/core/components/ajax-select-index.css') }}" defer></link>
<link rel="stylesheet" type="text/css" href="{{ static('core/components/ajax-select.scss') }}" defer></link>
{% endblock %}

{% block additional_js %}
<script type="module" src="{{ static('bundled/counter/counter-click-index.ts') }}"></script>
<script type="module" src="{{ static('bundled/counter/components/counter-product-select-index.ts') }}"></script>
{% endblock %}

{% block info_boxes %}
Expand Down Expand Up @@ -40,9 +46,24 @@
<form method="post" action=""
class="code_form" @submit.prevent="handleCode">
{% csrf_token %}

<input type="hidden" name="action" value="code">
<label for="code_field"></label>
<input type="text" name="code" value="" class="focus" id="code_field"/>

<counter-product-select name="code" x-ref="codeField" autofocus required placeholder="{% trans %}Select a product...{% endtrans %}">
<option value=""></option>
<optgroup label="{% trans %}Operations{% endtrans %}">
<option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option>
<option value="ANN">{% trans %}Cancel (ANN){% endtrans %}</option>
</optgroup>
{% for category in categories.keys() %}
<optgroup label="{{ category }}">
{% for product in categories[category] %}
<option value="{{ product.code }}">{{ product }}</option>
{% endfor %}
</optgroup>
{% endfor %}
</counter-product-select>

<input type="submit" value="{% trans %}Go{% endtrans %}"/>
</form>

Expand Down Expand Up @@ -174,15 +195,6 @@
},
{%- endfor -%}
};
const productsAutocomplete = [
{% for p in products -%}
{
value: "{{ p.code }}",
label: "{{ p.name }}",
tags: "{{ p.code }} {{ p.name }}",
},
{%- endfor %}
];
window.addEventListener("DOMContentLoaded", () => {
loadCounter({
csrfToken: "{{ csrf_token }}",
Expand Down
42 changes: 29 additions & 13 deletions locale/fr/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-12-19 10:43+0100\n"
"POT-Creation-Date: 2024-12-19 14:40+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <[email protected]\n"
"Language-Team: AE info <[email protected]>\n"
Expand Down Expand Up @@ -586,7 +586,7 @@ msgstr "Classeur : "
#: accounting/templates/accounting/journal_statement_accounting.jinja:30
#: core/templates/core/user_account.jinja:39
#: core/templates/core/user_account_detail.jinja:9
#: counter/templates/counter/counter_click.jinja:31
#: counter/templates/counter/counter_click.jinja:37
msgid "Amount: "
msgstr "Montant : "

Expand Down Expand Up @@ -1216,7 +1216,7 @@ msgid "Barman"
msgstr "Barman"

#: club/templates/club/club_sellings.jinja:51
#: counter/templates/counter/counter_click.jinja:28
#: counter/templates/counter/counter_click.jinja:34
#: counter/templates/counter/last_ops.jinja:22
#: counter/templates/counter/last_ops.jinja:47
#: counter/templates/counter/refilling_list.jinja:12
Expand Down Expand Up @@ -2037,8 +2037,8 @@ msgid ""
"The groups this user belongs to. A user will get all permissions granted to "
"each of their groups."
msgstr ""
"Les groupes auxquels cet utilisateur appartient. Un utilisateur aura toutes les "
"permissions de chacun de ses groupes."
"Les groupes auxquels cet utilisateur appartient. Un utilisateur aura toutes "
"les permissions de chacun de ses groupes."

#: core/models.py:277
msgid "profile"
Expand Down Expand Up @@ -2561,7 +2561,7 @@ msgstr "Confirmation"

#: core/templates/core/delete_confirm.jinja:20
#: core/templates/core/file_delete_confirm.jinja:46
#: counter/templates/counter/counter_click.jinja:100
#: counter/templates/counter/counter_click.jinja:121
#: counter/templates/counter/fragments/delete_student_card.jinja:12
#: sas/templates/sas/ask_picture_removal.jinja:20
msgid "Cancel"
Expand Down Expand Up @@ -3275,7 +3275,7 @@ msgid "Go to my Trombi tools"
msgstr "Allez à mes outils de Trombi"

#: core/templates/core/user_preferences.jinja:39
#: counter/templates/counter/counter_click.jinja:123
#: counter/templates/counter/counter_click.jinja:144
msgid "Student card"
msgstr "Carte étudiante"

Expand Down Expand Up @@ -3916,12 +3916,28 @@ msgstr "oui"
msgid "There is no cash register summary in this website."
msgstr "Il n'y a pas de relevé de caisse dans ce site web."

#: counter/templates/counter/counter_click.jinja:35
#: counter/templates/counter/counter_click.jinja:41
#: launderette/templates/launderette/launderette_admin.jinja:8
msgid "Selling"
msgstr "Vente"

#: counter/templates/counter/counter_click.jinja:46
#: counter/templates/counter/counter_click.jinja:52
msgid "Select a product..."
msgstr "Sélectionnez un produit…"

#: counter/templates/counter/counter_click.jinja:54
msgid "Operations"
msgstr "Opérations"

#: counter/templates/counter/counter_click.jinja:55
msgid "Confirm (FIN)"
msgstr "Confirmer (FIN)"

#: counter/templates/counter/counter_click.jinja:56
msgid "Cancel (ANN)"
msgstr "Annuler (ANN)"

#: counter/templates/counter/counter_click.jinja:67
#: counter/templates/counter/fragments/create_refill.jinja:8
#: counter/templates/counter/fragments/create_student_card.jinja:10
#: counter/templates/counter/invoices_call.jinja:16
Expand All @@ -3932,21 +3948,21 @@ msgstr "Vente"
msgid "Go"
msgstr "Valider"

#: counter/templates/counter/counter_click.jinja:53
#: counter/templates/counter/counter_click.jinja:74
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:19
msgid "Basket: "
msgstr "Panier : "

#: counter/templates/counter/counter_click.jinja:94
#: counter/templates/counter/counter_click.jinja:115
msgid "Finish"
msgstr "Terminer"

#: counter/templates/counter/counter_click.jinja:104
#: counter/templates/counter/counter_click.jinja:125
#: counter/templates/counter/refilling_list.jinja:9
msgid "Refilling"
msgstr "Rechargement"

#: counter/templates/counter/counter_click.jinja:114
#: counter/templates/counter/counter_click.jinja:135
msgid ""
"As a barman, you are not able to refill any account on your own. An admin "
"should be connected on this counter for that. The customer can refill by "
Expand Down

0 comments on commit ddcb2d8

Please sign in to comment.