```
✨ feat(admin): Enregistre et utilise SortableReorder.js
Ajoute SortableReorder.js pour permettre le tri des lignes de tableau par drag-and-drop. L'implémente dans la page des formules.
```
This commit is contained in:
@@ -10,6 +10,7 @@ import { SearchProduct, SearchOptions } from "./libs/SearchProduct.js";
|
||||
import { SearchProductDevis, SearchOptionsDevis } from "./libs/SearchProductDevis.js";
|
||||
import { SearchProductFormule, SearchOptionsFormule } from "./libs/SearchProductFormule.js";
|
||||
import PlaningLogestics from "./libs/PlaningLogestics.js";
|
||||
import {SortableReorder} from "./libs/SortableReorder.js";
|
||||
|
||||
// --- CONFIGURATION SENTRY ---
|
||||
Sentry.init({
|
||||
@@ -27,6 +28,7 @@ Sentry.init({
|
||||
const registerCustomElements = () => {
|
||||
const elements = [
|
||||
{ name: 'repeat-line', class: RepeatLine, extends: 'div' },
|
||||
{ name: 'sortable-reorder', class: SortableReorder, extends: 'table' },
|
||||
{ name: 'devis-manager', class: DevisManager, extends: 'div' },
|
||||
{ name: 'search-product', class: SearchProduct, extends: 'button' },
|
||||
{ name: 'search-productformule', class: SearchProductFormule, extends: 'button' },
|
||||
|
||||
85
assets/libs/SortableReorder.js
Normal file
85
assets/libs/SortableReorder.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import Sortable from 'sortablejs';
|
||||
|
||||
/**
|
||||
* Composant <sortable-reorder url="/api/update-order">
|
||||
* Encapsule un tableau pour rendre ses lignes triables.
|
||||
*/
|
||||
export class SortableReorder extends HTMLTableElement {
|
||||
constructor() {
|
||||
super();
|
||||
this.sortable = null;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
// On attend que le DOM enfant soit parsé
|
||||
setTimeout(() => this.init(), 0);
|
||||
}
|
||||
|
||||
init() {
|
||||
const tbody = this.querySelector('tbody');
|
||||
const url = this.getAttribute('url');
|
||||
|
||||
if (!tbody || !url) {
|
||||
console.warn('SortableReorder: <tbody> ou attribut "url" manquant.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.sortable = new Sortable(tbody, {
|
||||
animation: 150,
|
||||
handle: '.cursor-move', // On attrape par l'icône uniquement
|
||||
ghostClass: 'sortable-ghost', // Classe ajoutée à l'élément en mouvement (fond bleu)
|
||||
onEnd: () => this.saveOrder(tbody, url)
|
||||
});
|
||||
}
|
||||
|
||||
async saveOrder(tbody, url) {
|
||||
// Récupération des IDs dans le nouvel ordre
|
||||
const items = Array.from(tbody.querySelectorAll('tr[data-id]'))
|
||||
.map(tr => tr.dataset.id);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({ items })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur réseau lors de la sauvegarde.');
|
||||
}
|
||||
|
||||
// Petit feedback visuel (optionnel)
|
||||
this.showFeedback('success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('SortableReorder:', error);
|
||||
this.showFeedback('error');
|
||||
}
|
||||
}
|
||||
|
||||
showFeedback(type) {
|
||||
// Simple flash visuel sur le conteneur
|
||||
const color = type === 'success' ? 'rgba(16, 185, 129, 0.2)' : 'rgba(239, 68, 68, 0.2)';
|
||||
const originalTransition = this.style.transition;
|
||||
|
||||
this.style.transition = 'background-color 0.3s';
|
||||
this.style.backgroundColor = color;
|
||||
|
||||
setTimeout(() => {
|
||||
this.style.backgroundColor = 'transparent';
|
||||
setTimeout(() => {
|
||||
this.style.transition = originalTransition;
|
||||
}, 300);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this.sortable) {
|
||||
this.sortable.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user