feat(v2.0.4): Corrections diverses et tri des tableaux devis
- Correction affichage email contact dans SAP (models/msap.php) - Ajout fonctionnalité tri des tableaux devis (jsap.js, jdevis.js) - Améliorations diverses vues devis et SAP - Mise à jour contrôleurs et modèles export 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,100 @@ let oldIdLnEnCours;
|
||||
let oldIdLnArchives;
|
||||
let nbCommentChat = 0;
|
||||
let selectedXmlDevis = new Set();
|
||||
let searchSapTimeout = null;
|
||||
|
||||
const tableSortStates = new Map();
|
||||
|
||||
function initTableSort() {
|
||||
const allTables = document.querySelectorAll('[id^="tblDos"], [id^="tblDosArch"]');
|
||||
|
||||
allTables.forEach(table => {
|
||||
const tableId = table.id;
|
||||
const headers = table.querySelectorAll('th[data-sortable="true"]');
|
||||
const tbody = table.querySelector('tbody');
|
||||
|
||||
if (!tbody) return;
|
||||
|
||||
if (!tableSortStates.has(tableId)) {
|
||||
tableSortStates.set(tableId, {
|
||||
originalOrder: null,
|
||||
currentSort: { column: null, direction: null }
|
||||
});
|
||||
}
|
||||
|
||||
headers.forEach(header => {
|
||||
header.addEventListener('click', function() {
|
||||
const columnIndex = parseInt(this.getAttribute('data-column-index'));
|
||||
const sortType = this.getAttribute('data-sort-type');
|
||||
sortTable(tableId, columnIndex, sortType, this);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function sortTable(tableId, columnIndex, sortType, headerElement) {
|
||||
const table = document.getElementById(tableId);
|
||||
const tbody = table.querySelector('tbody');
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
const state = tableSortStates.get(tableId);
|
||||
|
||||
if (!state.originalOrder) {
|
||||
state.originalOrder = rows.slice();
|
||||
}
|
||||
|
||||
const allHeaders = table.querySelectorAll('th[data-sortable="true"]');
|
||||
allHeaders.forEach(h => h.style.fontWeight = 'normal');
|
||||
|
||||
let sortedRows;
|
||||
|
||||
if (state.currentSort.column === columnIndex && state.currentSort.direction === 'desc') {
|
||||
sortedRows = state.originalOrder.slice();
|
||||
state.currentSort = { column: null, direction: null };
|
||||
} else {
|
||||
const direction = (state.currentSort.column === columnIndex && state.currentSort.direction === 'asc') ? 'desc' : 'asc';
|
||||
|
||||
sortedRows = rows.slice().sort((a, b) => {
|
||||
const aCell = a.cells[columnIndex];
|
||||
const bCell = b.cells[columnIndex];
|
||||
|
||||
if (!aCell || !bCell) return 0;
|
||||
|
||||
let aValue = aCell.textContent.trim();
|
||||
let bValue = bCell.textContent.trim();
|
||||
|
||||
if (sortType === 'number') {
|
||||
aValue = aValue.replace(/[^\d,.-]/g, '').replace(',', '.');
|
||||
bValue = bValue.replace(/[^\d,.-]/g, '').replace(',', '.');
|
||||
aValue = parseFloat(aValue) || 0;
|
||||
bValue = parseFloat(bValue) || 0;
|
||||
return direction === 'asc' ? aValue - bValue : bValue - aValue;
|
||||
} else if (sortType === 'date') {
|
||||
aValue = parseDateFromText(aValue);
|
||||
bValue = parseDateFromText(bValue);
|
||||
if (!aValue && !bValue) return 0;
|
||||
if (!aValue) return direction === 'asc' ? 1 : -1;
|
||||
if (!bValue) return direction === 'asc' ? -1 : 1;
|
||||
return direction === 'asc' ? aValue - bValue : bValue - aValue;
|
||||
} else {
|
||||
const comparison = aValue.localeCompare(bValue, 'fr');
|
||||
return direction === 'asc' ? comparison : -comparison;
|
||||
}
|
||||
});
|
||||
|
||||
state.currentSort = { column: columnIndex, direction };
|
||||
headerElement.style.fontWeight = 'bold';
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
sortedRows.forEach(row => tbody.appendChild(row));
|
||||
}
|
||||
|
||||
function parseDateFromText(dateText) {
|
||||
const match = dateText.match(/(\d{2})\/(\d{2})[\/\s](\d{4})/);
|
||||
if (!match) return null;
|
||||
const [, day, month, year] = match;
|
||||
return new Date(year, month - 1, day);
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
console.log('#');
|
||||
@@ -786,6 +880,201 @@ window.addEventListener('DOMContentLoaded', (event) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Fonctions de recherche SAP
|
||||
let elSearchSAP = document.getElementById('searchSAP');
|
||||
let elBtnResetSearchSAP = document.getElementById('btnResetSearchSAP');
|
||||
|
||||
function restoreSearchSAP() {
|
||||
const storageKey = panel === 'enCours' ? 'sapSearchTermEnCours' : 'sapSearchTermArchives';
|
||||
const savedTerm = sessionStorage.getItem(storageKey);
|
||||
if (savedTerm && savedTerm.length >= 3) {
|
||||
elSearchSAP.value = savedTerm;
|
||||
elBtnResetSearchSAP.style.display = 'inline-block';
|
||||
performSearchSAP(savedTerm);
|
||||
} else {
|
||||
elSearchSAP.value = '';
|
||||
elBtnResetSearchSAP.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function performSearchSAP(term) {
|
||||
if (term.length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = panel === 'archives' ? 'archives' : 'encours';
|
||||
|
||||
fetch('/jxdevis/search_devis_sap', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
term: term,
|
||||
context: context,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
const devisIds = data.devis.map((d) => d.rowid);
|
||||
filterDevisTablesSAP(devisIds, context);
|
||||
updateBadgesSAP(data.nb_devis);
|
||||
} else {
|
||||
console.error('Erreur recherche:', data.message);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Erreur AJAX:', error);
|
||||
});
|
||||
}
|
||||
|
||||
function filterDevisTablesSAP(devisIds, context) {
|
||||
if (context === 'encours') {
|
||||
const statuts = document.querySelectorAll('[id^="tblBodyDos"]');
|
||||
statuts.forEach((tbody) => {
|
||||
const rows = tbody.querySelectorAll('tr.ligEnCours');
|
||||
rows.forEach((row) => {
|
||||
const cells = row.querySelectorAll('.celEnCours');
|
||||
if (cells.length > 0) {
|
||||
const rowId = parseInt(cells[0].getAttribute('data-rid'));
|
||||
if (devisIds.includes(rowId)) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const archives = document.querySelectorAll('[id^="tblBodyDosArch"]');
|
||||
archives.forEach((tbody) => {
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
rows.forEach((row) => {
|
||||
if (row.id && (row.id.startsWith('trArch_') || row.id.startsWith('trArchTous_'))) {
|
||||
const rowId = parseInt(row.id.replace('trArch_', '').replace('trArchTous_', ''));
|
||||
if (devisIds.includes(rowId)) {
|
||||
row.style.display = '';
|
||||
} else {
|
||||
row.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateBadgesSAP(nbDevis) {
|
||||
Object.keys(nbDevis).forEach((statutId) => {
|
||||
const liElements = document.querySelectorAll('[id^="liStat"]');
|
||||
liElements.forEach((li) => {
|
||||
li.setAttribute('data-after-text', nbDevis[statutId] || '0');
|
||||
li.setAttribute('data-after-type', 'orange badge top left');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function resetSearchSAP() {
|
||||
elSearchSAP.value = '';
|
||||
elBtnResetSearchSAP.style.display = 'none';
|
||||
const storageKey = panel === 'enCours' ? 'sapSearchTermEnCours' : 'sapSearchTermArchives';
|
||||
sessionStorage.removeItem(storageKey);
|
||||
|
||||
const context = panel === 'archives' ? 'archives' : 'encours';
|
||||
if (context === 'encours') {
|
||||
const statuts = document.querySelectorAll('[id^="tblBodyDos"]');
|
||||
statuts.forEach((tbody) => {
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
rows.forEach((row) => {
|
||||
row.style.display = '';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const archives = document.querySelectorAll('[id^="tblBodyDosArch"]');
|
||||
archives.forEach((tbody) => {
|
||||
const rows = tbody.querySelectorAll('tr');
|
||||
rows.forEach((row) => {
|
||||
row.style.display = '';
|
||||
});
|
||||
});
|
||||
const tbodyTous = document.getElementById('tblBodyDosArchTous');
|
||||
if (tbodyTous) {
|
||||
const rowsTous = tbodyTous.querySelectorAll('tr');
|
||||
rowsTous.forEach((row) => {
|
||||
row.style.display = '';
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elSearchSAP.addEventListener('input', function () {
|
||||
const term = this.value.trim();
|
||||
|
||||
if (searchSapTimeout) {
|
||||
clearTimeout(searchSapTimeout);
|
||||
}
|
||||
|
||||
if (term.length >= 3) {
|
||||
elBtnResetSearchSAP.style.display = 'inline-block';
|
||||
const storageKey = panel === 'enCours' ? 'sapSearchTermEnCours' : 'sapSearchTermArchives';
|
||||
sessionStorage.setItem(storageKey, term);
|
||||
searchSapTimeout = setTimeout(() => {
|
||||
performSearchSAP(term);
|
||||
}, 300);
|
||||
} else if (term.length === 0) {
|
||||
resetSearchSAP();
|
||||
} else {
|
||||
elBtnResetSearchSAP.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
elBtnResetSearchSAP.addEventListener('click', function () {
|
||||
resetSearchSAP();
|
||||
});
|
||||
|
||||
// Hook sur changement d'onglet pour restaurer la recherche appropriée
|
||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
||||
if ($(this).attr('href') == '#tabEnCours') {
|
||||
panel = 'enCours';
|
||||
restoreSearchSAP();
|
||||
} else if ($(this).attr('href') == '#tabArchives') {
|
||||
panel = 'archives';
|
||||
restoreSearchSAP();
|
||||
}
|
||||
});
|
||||
|
||||
restoreSearchSAP();
|
||||
|
||||
initTableSort();
|
||||
|
||||
// Gestion des états actifs des onglets départements (multiples <ul>)
|
||||
// Utiliser l'événement Bootstrap 'shown.bs.tab' au lieu de 'click'
|
||||
const allDeptTabs = document.querySelectorAll('.dept-tab a');
|
||||
allDeptTabs.forEach((tab) => {
|
||||
$(tab).on('shown.bs.tab', function(e) {
|
||||
// Retirer 'active' de tous les onglets départements sauf celui-ci
|
||||
document.querySelectorAll('.dept-tab').forEach((li) => {
|
||||
if (li !== this.parentElement) {
|
||||
li.classList.remove('active');
|
||||
}
|
||||
});
|
||||
// Retirer 'active' de l'onglet "Tous"
|
||||
const tousLi = document.querySelector('a[href="#dosArchTous"]').parentElement;
|
||||
tousLi.classList.remove('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Gestion du clic sur "Tous"
|
||||
const tousTab = document.querySelector('a[href="#dosArchTous"]');
|
||||
if (tousTab) {
|
||||
$(tousTab).on('shown.bs.tab', function(e) {
|
||||
// Retirer 'active' de tous les onglets départements
|
||||
document.querySelectorAll('.dept-tab').forEach((li) => {
|
||||
li.classList.remove('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add new functions
|
||||
function updateExportButton() {
|
||||
if (selectedXmlDevis.size > 0) {
|
||||
|
||||
Reference in New Issue
Block a user