mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-22 15:51:19 +00:00
Make products filterable by product type
This commit is contained in:
parent
6953eaa9d0
commit
accf1befce
@ -107,7 +107,7 @@ form {
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
label, legend {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
|
||||
|
@ -429,6 +429,16 @@ body {
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
$col-gap: 1rem;
|
||||
&.gap {
|
||||
column-gap: var($col-gap);
|
||||
}
|
||||
@for $i from 2 through 5 {
|
||||
&.gap-#{$i}x {
|
||||
column-gap: $i * $col-gap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*---------------------------------NEWS--------------------------------*/
|
||||
|
@ -4,9 +4,11 @@ import type { TomOption } from "tom-select/dist/types/types";
|
||||
import type { escape_html } from "tom-select/dist/types/utils";
|
||||
import {
|
||||
type CounterSchema,
|
||||
type ProductTypeSchema,
|
||||
type SimpleProductSchema,
|
||||
counterSearchCounter,
|
||||
productSearchProducts,
|
||||
producttypeFetchAll,
|
||||
} from "#openapi";
|
||||
|
||||
@registerComponent("product-ajax-select")
|
||||
@ -34,6 +36,37 @@ export class ProductAjaxSelect extends AjaxSelect {
|
||||
}
|
||||
}
|
||||
|
||||
@registerComponent("product-type-ajax-select")
|
||||
export class ProductTypeAjaxSelect extends AjaxSelect {
|
||||
protected valueField = "id";
|
||||
protected labelField = "name";
|
||||
protected searchField = ["name"];
|
||||
private productTypes = null as ProductTypeSchema[];
|
||||
|
||||
protected async search(query: string): Promise<TomOption[]> {
|
||||
// The production database has a grand total of 26 product types
|
||||
// and the filter logic is really simple.
|
||||
// Thus, it's appropriate to fetch all product types during first use,
|
||||
// then to reuse the result again and again.
|
||||
if (this.productTypes === null) {
|
||||
this.productTypes = (await producttypeFetchAll()).data || null;
|
||||
}
|
||||
return this.productTypes.filter((t) =>
|
||||
t.name.toLowerCase().includes(query.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
protected renderOption(item: ProductTypeSchema, sanitize: typeof escape_html) {
|
||||
return `<div class="select-item">
|
||||
<span class="select-item-text">${sanitize(item.name)}</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
protected renderItem(item: ProductTypeSchema, sanitize: typeof escape_html) {
|
||||
return `<span>${sanitize(item.name)}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
@registerComponent("counter-ajax-select")
|
||||
export class CounterAjaxSelect extends AjaxSelect {
|
||||
protected valueField = "id";
|
||||
|
@ -3,6 +3,7 @@ import { csv } from "#core:utils/csv";
|
||||
import { History, getCurrentUrlParams, updateQueryString } from "#core:utils/history";
|
||||
import type { NestedKeyOf } from "#core:utils/types";
|
||||
import { showSaveFilePicker } from "native-file-system-adapter";
|
||||
import type TomSelect from "tom-select";
|
||||
import {
|
||||
type ProductSchema,
|
||||
type ProductSearchProductsDetailedData,
|
||||
@ -58,6 +59,7 @@ document.addEventListener("alpine:init", () => {
|
||||
|
||||
productStatus: "" as "active" | "archived" | "both",
|
||||
search: "",
|
||||
productTypes: [] as string[],
|
||||
pageSize: defaultPageSize,
|
||||
page: defaultPage,
|
||||
|
||||
@ -65,17 +67,22 @@ document.addEventListener("alpine:init", () => {
|
||||
const url = getCurrentUrlParams();
|
||||
this.search = url.get("search") || "";
|
||||
this.productStatus = url.get("productStatus") ?? "active";
|
||||
const widget = this.$refs.productTypesInput.widget as TomSelect;
|
||||
widget.on("change", (items: string[]) => {
|
||||
this.productTypes = [...items];
|
||||
});
|
||||
|
||||
await this.load();
|
||||
for (const param of ["search", "productStatus"]) {
|
||||
const searchParams = ["search", "productStatus", "productTypes"];
|
||||
for (const param of searchParams) {
|
||||
this.$watch(param, () => {
|
||||
this.page = defaultPage;
|
||||
});
|
||||
}
|
||||
for (const param of ["search", "productStatus", "page"]) {
|
||||
for (const param of [...searchParams, "page"]) {
|
||||
this.$watch(param, async (value: string) => {
|
||||
updateQueryString(param, value, History.Replace);
|
||||
this.nbPages = 0;
|
||||
this.products = {};
|
||||
await this.load();
|
||||
});
|
||||
}
|
||||
@ -100,6 +107,8 @@ document.addEventListener("alpine:init", () => {
|
||||
search: search,
|
||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||
is_archived: isArchived,
|
||||
// biome-ignore lint/style/useNamingConvention: api is in snake_case
|
||||
product_type: this.productTypes,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@ -6,45 +6,60 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_js %}
|
||||
<script type="module" src="{{ static("bundled/counter/components/ajax-select-index.ts") }}"></script>
|
||||
<script type="module" src="{{ static("bundled/counter/product-list-index.ts") }}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block additional_css %}
|
||||
<link rel="stylesheet" href="{{ static("core/components/card.scss") }}">
|
||||
<link rel="stylesheet" href="{{ static("counter/css/admin.scss") }}">
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main x-data="productList">
|
||||
<h4 class="margin-bottom">{% trans %}Filter products{% endtrans %}</h4>
|
||||
<form id="search-form" class="margin-bottom">
|
||||
<h4 class="margin-bottom">{% trans %}Filter products{% endtrans %}</h4>
|
||||
<div class="row gap-4x">
|
||||
|
||||
<fieldset>
|
||||
<label for="search-input">{% trans %}Product name{% endtrans %}</label>
|
||||
<input
|
||||
id="search-input"
|
||||
type="text"
|
||||
name="search"
|
||||
x-model.debounce.500ms="search"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans %}Product state{% endtrans %}</legend>
|
||||
<div class="row">
|
||||
<input type="radio" id="filter-active-products" x-model="productStatus" value="active">
|
||||
<label for="filter-active-products">{% trans %}Active products{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="radio" id="filter-inactive-products" x-model="productStatus" value="archived">
|
||||
<label for="filter-inactive-products">{% trans %}Archived products{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="radio" id="filter-all-products" x-model="productStatus" value="both">
|
||||
<label for="filter-all-products">{% trans %}All products{% endtrans %}</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<fieldset>
|
||||
<label for="search-input">{% trans %}Product name{% endtrans %}</label>
|
||||
<input
|
||||
id="search-input"
|
||||
type="text"
|
||||
name="search"
|
||||
x-model.debounce.500ms="search"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<input type="radio" id="filter-active-products" x-model="productStatus" value="active">
|
||||
<label for="filter-active-products">{% trans %}Active products{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="radio" id="filter-inactive-products" x-model="productStatus" value="archived">
|
||||
<label for="filter-inactive-products">{% trans %}Archived products{% endtrans %}</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="radio" id="filter-all-products" x-model="productStatus" value="both">
|
||||
<label for="filter-all-products">{% trans %}All products{% endtrans %}</label>
|
||||
</div>
|
||||
<label for="type-search-input">{% trans %}Product type{% endtrans %}</label>
|
||||
<product-type-ajax-select
|
||||
id="type-search-input"
|
||||
name="product-type"
|
||||
x-ref="productTypesInput"
|
||||
multiple
|
||||
>
|
||||
</product-type-ajax-select>
|
||||
</fieldset>
|
||||
</form>
|
||||
<h3 @click="console.log(totalCount, nbPages())" class="margin-bottom">
|
||||
{% trans %}Product list{% endtrans %}
|
||||
</h3>
|
||||
<h3 class="margin-bottom">{% trans %}Product list{% endtrans %}</h3>
|
||||
|
||||
<div class="row margin-bottom">
|
||||
<a href="{{ url('counter:new_product') }}" class="btn btn-blue">
|
||||
|
@ -1,8 +1,12 @@
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from core.views.widgets.select import AutoCompleteSelect, AutoCompleteSelectMultiple
|
||||
from counter.models import Counter, Product
|
||||
from counter.schemas import SimpleProductSchema, SimplifiedCounterSchema
|
||||
from counter.models import Counter, Product, ProductType
|
||||
from counter.schemas import (
|
||||
ProductTypeSchema,
|
||||
SimpleProductSchema,
|
||||
SimplifiedCounterSchema,
|
||||
)
|
||||
|
||||
_js = ["bundled/counter/components/ajax-select-index.ts"]
|
||||
|
||||
@ -33,3 +37,17 @@ class AutoCompleteSelectMultipleProduct(AutoCompleteSelectMultiple):
|
||||
model = Product
|
||||
adapter = TypeAdapter(list[SimpleProductSchema])
|
||||
js = _js
|
||||
|
||||
|
||||
class AutoCompleteSelectProductType(AutoCompleteSelect):
|
||||
component_name = "product-type-ajax-select"
|
||||
model = ProductType
|
||||
adapter = TypeAdapter(list[ProductTypeSchema])
|
||||
js = _js
|
||||
|
||||
|
||||
class AutoCompleteSelectMultipleProductType(AutoCompleteSelectMultiple):
|
||||
component_name = "product-type-ajax-select"
|
||||
model = ProductType
|
||||
adapter = TypeAdapter(list[ProductTypeSchema])
|
||||
js = _js
|
||||
|
@ -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-21 02:15+0100\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -765,7 +765,7 @@ msgstr "Opération liée : "
|
||||
#: core/templates/core/create.jinja:12 core/templates/core/edit.jinja:7
|
||||
#: core/templates/core/edit.jinja:15 core/templates/core/edit.jinja:20
|
||||
#: core/templates/core/file_edit.jinja:8
|
||||
#: core/templates/core/macros_pages.jinja:25
|
||||
#: core/templates/core/macros_pages.jinja:26
|
||||
#: core/templates/core/page_prop.jinja:11
|
||||
#: core/templates/core/user_godfathers.jinja:61
|
||||
#: core/templates/core/user_godfathers_tree.jinja:85
|
||||
@ -971,7 +971,7 @@ msgstr "Comptoir"
|
||||
msgid "Products"
|
||||
msgstr "Produits"
|
||||
|
||||
#: club/forms.py:168 counter/templates/counter/product_list.jinja:37
|
||||
#: club/forms.py:168 counter/templates/counter/product_list.jinja:43
|
||||
msgid "Archived products"
|
||||
msgstr "Produits archivés"
|
||||
|
||||
@ -1146,7 +1146,7 @@ msgid "There are no members in this club."
|
||||
msgstr "Il n'y a pas de membres dans ce club."
|
||||
|
||||
#: club/templates/club/club_members.jinja:80
|
||||
#: core/templates/core/file_detail.jinja:19 core/views/forms.py:305
|
||||
#: core/templates/core/file_detail.jinja:19 core/views/forms.py:309
|
||||
#: launderette/views.py:208 trombi/templates/trombi/detail.jinja:19
|
||||
msgid "Add"
|
||||
msgstr "Ajouter"
|
||||
@ -1192,7 +1192,7 @@ msgid "Show"
|
||||
msgstr "Montrer"
|
||||
|
||||
#: club/templates/club/club_sellings.jinja:38
|
||||
#: counter/templates/counter/product_list.jinja:59
|
||||
#: counter/templates/counter/product_list.jinja:74
|
||||
msgid "Download as cvs"
|
||||
msgstr "Télécharger en CSV"
|
||||
|
||||
@ -2038,8 +2038,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"
|
||||
@ -2764,11 +2764,11 @@ msgstr "Tout désélectionner"
|
||||
msgid "You're seeing the history of page \"%(page_name)s\""
|
||||
msgstr "Vous consultez l'historique de la page \"%(page_name)s\""
|
||||
|
||||
#: core/templates/core/macros_pages.jinja:8
|
||||
#: core/templates/core/macros_pages.jinja:10
|
||||
msgid "last"
|
||||
msgstr "actuel"
|
||||
|
||||
#: core/templates/core/macros_pages.jinja:21
|
||||
#: core/templates/core/macros_pages.jinja:22
|
||||
msgid "Edit page"
|
||||
msgstr "Éditer la page"
|
||||
|
||||
@ -3026,7 +3026,7 @@ msgstr "Facture eboutic"
|
||||
msgid "Etickets"
|
||||
msgstr "Etickets"
|
||||
|
||||
#: core/templates/core/user_account.jinja:69 core/views/user.py:633
|
||||
#: core/templates/core/user_account.jinja:69 core/views/user.py:631
|
||||
msgid "User has no account"
|
||||
msgstr "L'utilisateur n'a pas de compte"
|
||||
|
||||
@ -3326,7 +3326,8 @@ msgstr "Outils utilisateurs"
|
||||
msgid "Sith management"
|
||||
msgstr "Gestion de Sith"
|
||||
|
||||
#: core/templates/core/user_tools.jinja:21 core/views/user.py:254
|
||||
#: core/templates/core/user_tools.jinja:21 core/views/forms.py:295
|
||||
#: core/views/user.py:254
|
||||
msgid "Groups"
|
||||
msgstr "Groupes"
|
||||
|
||||
@ -3518,32 +3519,32 @@ msgstr "Blouse : montrez aux autres à quoi ressemble votre blouse !"
|
||||
msgid "Bad image format, only jpeg, png, webp and gif are accepted"
|
||||
msgstr "Mauvais format d'image, seuls les jpeg, png, webp et gif sont acceptés"
|
||||
|
||||
#: core/views/forms.py:302
|
||||
#: core/views/forms.py:306
|
||||
msgid "Godfather / Godmother"
|
||||
msgstr "Parrain / Marraine"
|
||||
|
||||
#: core/views/forms.py:303
|
||||
#: core/views/forms.py:307
|
||||
msgid "Godchild"
|
||||
msgstr "Fillot / Fillote"
|
||||
|
||||
#: core/views/forms.py:308 counter/forms.py:78 trombi/views.py:151
|
||||
#: core/views/forms.py:312 counter/forms.py:78 trombi/views.py:151
|
||||
msgid "Select user"
|
||||
msgstr "Choisir un utilisateur"
|
||||
|
||||
#: core/views/forms.py:322
|
||||
#: core/views/forms.py:326
|
||||
msgid "This user does not exist"
|
||||
msgstr "Cet utilisateur n'existe pas"
|
||||
|
||||
#: core/views/forms.py:324
|
||||
#: core/views/forms.py:328
|
||||
msgid "You cannot be related to yourself"
|
||||
msgstr "Vous ne pouvez pas être relié à vous-même"
|
||||
|
||||
#: core/views/forms.py:336
|
||||
#: core/views/forms.py:340
|
||||
#, python-format
|
||||
msgid "%s is already your godfather"
|
||||
msgstr "%s est déjà votre parrain/marraine"
|
||||
|
||||
#: core/views/forms.py:342
|
||||
#: core/views/forms.py:346
|
||||
#, python-format
|
||||
msgid "%s is already your godchild"
|
||||
msgstr "%s est déjà votre fillot/fillote"
|
||||
@ -4152,31 +4153,35 @@ msgstr ""
|
||||
"aucune conséquence autre que le retrait de l'argent de votre compte."
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:5
|
||||
#: counter/templates/counter/product_list.jinja:46
|
||||
#: counter/templates/counter/product_list.jinja:62
|
||||
msgid "Product list"
|
||||
msgstr "Liste des produits"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:20
|
||||
#: counter/templates/counter/product_list.jinja:22
|
||||
msgid "Filter products"
|
||||
msgstr "Filtrer les produits"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:22
|
||||
#: counter/templates/counter/product_list.jinja:27
|
||||
msgid "Product name"
|
||||
msgstr "Nom du produit"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:33
|
||||
#, fuzzy
|
||||
#| msgid "Archived products"
|
||||
#: counter/templates/counter/product_list.jinja:36
|
||||
msgid "Product state"
|
||||
msgstr "Etat du produit"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:39
|
||||
msgid "Active products"
|
||||
msgstr "Produits archivés"
|
||||
msgstr "Produits actifs"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:41
|
||||
#, fuzzy
|
||||
#| msgid "products"
|
||||
#: counter/templates/counter/product_list.jinja:47
|
||||
msgid "All products"
|
||||
msgstr "produits"
|
||||
msgstr "Tous les produits"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:51
|
||||
#: counter/templates/counter/product_list.jinja:52
|
||||
msgid "Product type"
|
||||
msgstr "Type de produit"
|
||||
|
||||
#: counter/templates/counter/product_list.jinja:66
|
||||
msgid "New product"
|
||||
msgstr "Nouveau produit"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user