feat(Product): Ajoute la publication des produits et les périodes bloquées

Ajoute la possibilité de publier ou masquer un produit.
Permet de bloquer des périodes pour un produit.
Corrige des bugs liés à la suppression des produits du panier.
Mise à jour de l'affichage du calendrier pour les blocages.
```
This commit is contained in:
Serreau Jovann
2026-02-03 14:53:11 +01:00
parent 6c6324addc
commit d993a545d9
14 changed files with 850 additions and 25 deletions

View File

@@ -194,10 +194,19 @@ export default class PlaningLogistics extends HTMLElement {
fetch(`/crm/reservation/data?${params.toString()}`)
.then(response => response.json())
.then(data => {
const formattedData = data.map(item => ({
...item,
backgroundColor: item.extendedProps.isSigned ? '#059669' : '#2563eb'
}));
const formattedData = data.map(item => {
let bgColor = '#2563eb';
if (item.extendedProps.type === 'blocked') {
bgColor = '#ef4444';
} else if (item.extendedProps.isSigned) {
bgColor = '#059669';
}
return {
...item,
backgroundColor: bgColor,
borderColor: bgColor
};
});
successCallback(formattedData);
this.updateStats(formattedData);
loader.classList.add('hidden');
@@ -208,7 +217,22 @@ export default class PlaningLogistics extends HTMLElement {
});
},
eventContent: (arg) => {
const { caution, acompte, solde, isSigned } = arg.event.extendedProps;
const props = arg.event.extendedProps;
if (props.type === 'blocked') {
return {
html: `
<div class="fc-event-main-frame">
<div class="text-[10px] font-black uppercase tracking-tight leading-tight line-clamp-2 flex items-center gap-1 text-white">
${arg.event.title.replace('BLOCAGE: ', '')}
</div>
<div class="text-[9px] text-white/80 italic truncate mt-0.5">${props.reason || 'Pas de raison'}</div>
</div>
`
};
}
const { caution, acompte, solde, isSigned } = props;
return {
html: `
<div class="fc-event-main-frame">
@@ -243,6 +267,32 @@ export default class PlaningLogistics extends HTMLElement {
const modal = this.querySelector('#calendar-modal');
const props = event.extendedProps;
const modalStatus = this.querySelector('#modal-status-container');
// Reset visibility
this.querySelector('#link-phone').style.display = 'flex';
this.querySelector('#link-email').style.display = 'flex';
this.querySelector('#modal-link-contrat').parentElement.style.display = 'flex';
this.querySelector('.bg-slate-900\\/50.rounded-2xl.border').style.display = 'block'; // Address container
if (props.type === 'blocked') {
this.querySelector('#modal-contract-number').innerText = 'INDISPONIBILITÉ';
this.querySelector('#modal-title').innerText = props.productName;
this.querySelector('#modal-client').innerText = props.reason || 'Aucune raison spécifiée';
this.querySelector('#modal-email').innerText = '-';
this.querySelector('#modal-phone').innerText = '-';
this.querySelector('#modal-start').innerText = props.start;
this.querySelector('#modal-end').innerText = props.end;
// Hide irrelevant sections
this.querySelector('#link-phone').style.display = 'none';
this.querySelector('#link-email').style.display = 'none';
this.querySelector('#modal-link-contrat').parentElement.style.display = 'none';
this.querySelector('.bg-slate-900\\/50.rounded-2xl.border').style.display = 'none';
modalStatus.innerHTML = '<span class="px-2.5 py-1 rounded-lg text-[9px] font-black uppercase tracking-widest bg-rose-500/10 text-rose-500 border border-rose-500/20">Blocage Matériel</span>';
modal.classList.replace('hidden', 'flex');
return;
}
this.querySelector('#modal-contract-number').innerText = `Contrat n°${props.contractNumber || '?'}`;
this.querySelector('#modal-title').innerText = event.title;

View File

@@ -128,6 +128,21 @@ export class FlowReserve extends HTMLAnchorElement {
const data = await response.json();
this.productsAvailable = data.available;
if (data.unavailable_products_ids && data.unavailable_products_ids.length > 0) {
let currentList = this.getList();
const initialLength = currentList.length;
// Filter out unavailable items
currentList = currentList.filter(id => !data.unavailable_products_ids.includes(parseInt(id)) && !data.unavailable_products_ids.includes(String(id)));
if (currentList.length !== initialLength) {
localStorage.setItem(this.storageKey, JSON.stringify(currentList));
window.dispatchEvent(new CustomEvent('cart:updated'));
console.warn('Produits indisponibles retirés du panier:', data.unavailable_products_ids);
// Force refresh to update UI immediately
this.refreshContent();
}
}
} catch (error) {
console.error('Erreur lors de la vérification de disponibilité:', error);
this.productsAvailable = false;
@@ -242,6 +257,20 @@ export class FlowReserve extends HTMLAnchorElement {
const data = await response.json();
// Handle removed products (deleted from DB)
if (data.unavailable_products_ids && data.unavailable_products_ids.length > 0) {
let currentList = this.getList();
const initialLength = currentList.length;
currentList = currentList.filter(id => !data.unavailable_products_ids.includes(parseInt(id)) && !data.unavailable_products_ids.includes(String(id))); // Handle string/int types
if (currentList.length !== initialLength) {
localStorage.setItem(this.storageKey, JSON.stringify(currentList));
window.dispatchEvent(new CustomEvent('cart:updated'));
// We don't recurse here to avoid infinite loops, but the UI will update next time or we could just use the returned 'products' which already excludes them.
console.warn('Certains produits ont été retirés car ils n\'existent plus:', data.unavailable_products_ids);
}
}
// Merge client-side dates if server didn't return them (or to prioritize client choice)
if (!data.start_date && dates.start) data.start_date = this.formatDate(dates.start);
if (!data.end_date && dates.end) data.end_date = this.formatDate(dates.end);