Make product types dynamically orderable.

This commit is contained in:
imperosol
2024-12-17 00:53:47 +01:00
parent c79c251ba7
commit 47876e3971
10 changed files with 269 additions and 113 deletions

View File

@ -0,0 +1,64 @@
import Alpine from "alpinejs";
import { producttypeReorder } from "#openapi";
document.addEventListener("alpine:init", () => {
Alpine.data("productTypesList", () => ({
loading: false,
alertMessage: {
open: false,
success: true,
content: "",
timeout: null,
},
async reorder(itemId: number, newPosition: number) {
// The sort plugin of Alpine doesn't manage dynamic lists with x-sort
// (cf. https://github.com/alpinejs/alpine/discussions/4157).
// There is an open PR that fixes this issue
// (cf. https://github.com/alpinejs/alpine/pull/4361).
// However, it hasn't been merged yet.
// To overcome this, I get the list of DOM elements
// And fetch the `x-sort:item` attribute, which value is
// the id of the object in database.
// Please make this a little bit cleaner when the fix has been merged
// into the main Alpine repo.
this.loading = true;
const productTypes = this.$refs.productTypes
.childNodes as NodeListOf<HTMLLIElement>;
const getId = (elem: HTMLLIElement) =>
Number.parseInt(elem.getAttribute("x-sort:item"));
const query =
newPosition === 0
? { above: getId(productTypes.item(1)) }
: { below: getId(productTypes.item(newPosition - 1)) };
const response = await producttypeReorder({
// biome-ignore lint/style/useNamingConvention: api is snake_case
path: { type_id: itemId },
query: query,
});
this.openAlertMessage(response.response);
this.loading = false;
},
openAlertMessage(response: Response) {
if (response.ok) {
this.alertMessage.success = true;
this.alertMessage.content = gettext("Products types successfully reordered");
} else {
this.alertMessage.success = false;
this.alertMessage.content = interpolate(
gettext("Product type reorganisation failed with status code : %d"),
[response.status],
);
}
this.alertMessage.open = true;
if (this.alertMessage.timeout !== null) {
clearTimeout(this.alertMessage.timeout);
}
this.alertMessage.timeout = setTimeout(() => {
this.alertMessage.open = false;
}, 2000);
this.loading = false;
},
}));
});

View File

@ -0,0 +1,15 @@
.product-type-list {
li {
list-style: none;
margin-bottom: 10px;
i {
cursor: grab;
visibility: hidden;
}
}
}
body:not(.sorting) .product-type-list li:hover i {
visibility: visible;
}

View File

@ -4,21 +4,49 @@
{% trans %}Product type list{% endtrans %}
{% endblock %}
{% block additional_css %}
<link rel="stylesheet" href="{{ static("counter/css/product_type.scss") }}">
{% endblock %}
{% block additional_js %}
<script type="module" src="{{ static("bundled/counter/product-type-index.ts") }}"></script>
{% endblock %}
{% block content %}
<p><a href="{{ url('counter:new_producttype') }}">{% trans %}New product type{% endtrans %}</a></p>
{% if producttype_list %}
<h3>{% trans %}Product type list{% endtrans %}</h3>
<ul>
{% for t in producttype_list %}
<li><a href="{{ url('counter:producttype_edit', type_id=t.id) }}">{{ t }}</a></li>
{% endfor %}
</ul>
<div x-data="productTypesList">
<p
class="alert snackbar"
:class="alertMessage.success ? 'alert-green' : 'alert-red'"
x-show="alertMessage.open"
x-transition.duration.500ms
x-text="alertMessage.content"
></p>
<h3>{% trans %}Product type list{% endtrans %}</h3>
<ul
x-sort="($item, $position) => reorder($item, $position)"
x-ref="productTypes"
class="product-type-list"
:aria-busy="loading"
>
{%- for t in producttype_list -%}
<li x-sort:item="{{ t.id }}">
<i class="fa fa-grip-vertical"></i>
<a href="{{ url('counter:producttype_edit', type_id=t.id) }}">{{ t }}</a>
</li>
{%- endfor -%}
</ul>
</div>
{% else %}
{% trans %}There is no product types in this website.{% endtrans %}
{% endif %}
{% endblock %}
{% block script %}
{% endblock %}