fix: correzione doppia consuntivazione note. fix: utenti ldap. feat: ricerca per customer useers. feat: possiblità di rimuovere le note

This commit is contained in:
Gabriele Cimaschi
2026-07-08 12:52:30 +02:00
parent 3b06609fcb
commit 9a91c3d9f5
7 changed files with 661 additions and 72 deletions
+176 -2
View File
@@ -11,6 +11,7 @@ const Filters = {
state_id: '',
priority_id: '',
user_id: '',
customer_user_id: '',
date_from: '',
date_to: '',
},
@@ -19,6 +20,7 @@ const Filters = {
state_id: '',
priority_id: '',
user_id: '',
customer_user_id: '',
date_from: '',
date_to: '',
}
@@ -60,7 +62,7 @@ const Filters = {
/** Reset all filters */
reset() {
this.state = { queue_id: '', state_id: '', priority_id: '', user_id: '', date_from: '', date_to: '' };
this.state = { queue_id: '', state_id: '', priority_id: '', user_id: '', customer_user_id: '', date_from: '', date_to: '' };
if (this.currentMode === 'my') {
const activeAgentId = localStorage.getItem('activeAgentId') || '1';
this.state.user_id = activeAgentId;
@@ -107,6 +109,36 @@ const Filters = {
}
},
/** Helper to compute trigger button label text for customer users */
getCustomerUserMultiselectLabel(lookups) {
const selectedList = Array.isArray(this.state.customer_user_id)
? this.state.customer_user_id
: (typeof this.state.customer_user_id === 'string' && this.state.customer_user_id ? this.state.customer_user_id.split(',') : []);
if (selectedList.length === 0) {
return 'Tutti';
}
const selectedNames = (lookups.customerUsers || [])
.filter(u => selectedList.includes(String(u.login)))
.map(u => `${u.last_name} ${u.first_name}`);
if (selectedNames.length === (lookups.customerUsers || []).length) {
return 'Tutti';
} else if (selectedNames.length <= 2) {
return selectedNames.join(', ');
} else {
return `${selectedNames.length} selezionati`;
}
},
/** Update customer user label DOM element dynamically */
updateCustomerUserMultiselectLabel(lookups) {
const labelEl = document.getElementById('customer-user-multiselect-label');
if (labelEl) {
labelEl.textContent = this.getCustomerUserMultiselectLabel(lookups);
}
},
/**
* Render filter bar HTML.
* @param {Object} lookups - { queues, states, priorities, users }
@@ -123,6 +155,7 @@ const Filters = {
};
const currentLabel = this.getStateMultiselectLabel(lookups);
const customerUserLabel = this.getCustomerUserMultiselectLabel(lookups);
return `
<div class="filters-bar" id="filters-bar">
@@ -174,6 +207,23 @@ const Filters = {
${makeOptions(lookups.users || [], 'id', (u) => `${u.first_name} ${u.last_name}`, this.state.user_id)}
</select>
</div>
<div class="filter-group" style="position:relative;">
<span class="filter-label">Utente Cliente</span>
<div class="multiselect-dropdown" id="customer-user-multiselect-dropdown" style="min-width: 140px; background: var(--bg-card); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); padding: 4px var(--space-sm); font-size: 0.85rem; cursor: pointer; display: flex; justify-content: space-between; align-items: center; user-select: none;">
<span class="multiselect-label" id="customer-user-multiselect-label" style="text-overflow:ellipsis; overflow:hidden; white-space:nowrap; max-width:180px;">${App.escapeHtml(customerUserLabel)}</span>
<span style="font-size:0.6rem; color:var(--text-muted); margin-left:6px;">▼</span>
</div>
<div class="multiselect-popover" id="customer-user-multiselect-popover" style="display:none; position:absolute; left:0; top:100%; width: 280px; background:var(--bg-card); border:1px solid var(--border-subtle); border-radius:var(--radius-sm); box-shadow:var(--shadow-md); z-index:1002; padding:var(--space-xs); margin-top:4px;">
<input type="text" id="customer-user-search-input" placeholder="Cerca utente..." style="width:100%; padding:6px 8px; font-size:0.8rem; background:var(--bg-tertiary); border:1px solid var(--border-subtle); border-radius:var(--radius-sm); margin-bottom:6px; box-sizing:border-box; color:var(--text-primary); font-family:inherit;" autocomplete="off" />
<div id="customer-user-items-container" style="display:flex; flex-direction:column; gap:4px; max-height:220px; overflow-y:auto; padding-bottom:6px; border-bottom:1px solid var(--border-subtle);">
<!-- Populated dynamically -->
</div>
<div style="display:flex; justify-content:flex-end; gap:8px; padding-top:6px; font-size:0.75rem;">
<button type="button" class="btn btn-ghost btn-xs" id="customer-user-multiselect-clear" style="height:20px; padding:0 8px; font-size:0.75rem;">Reset</button>
<button type="button" class="btn btn-primary btn-xs" id="customer-user-multiselect-ok" style="height:20px; padding:0 8px; font-size:0.75rem; line-height:20px;">OK</button>
</div>
</div>
</div>
<div class="filter-group">
<span class="filter-label">Da Data/Ora</span>
<input type="datetime-local" class="filter-select" data-filter="date_from" id="filter-date-from" value="${this.state.date_from || ''}" style="width: 190px; padding: 4px 8px; font-family: inherit; font-size: 0.85rem;" />
@@ -265,6 +315,117 @@ const Filters = {
});
}
// Customer User Multiselect Popover binding
const cuDropdown = document.getElementById('customer-user-multiselect-dropdown');
const cuPopover = document.getElementById('customer-user-multiselect-popover');
const cuSearchInput = document.getElementById('customer-user-search-input');
const cuItemsContainer = document.getElementById('customer-user-items-container');
const cuOkBtn = document.getElementById('customer-user-multiselect-ok');
const cuClearBtn = document.getElementById('customer-user-multiselect-clear');
const renderCustomerUserItems = () => {
if (!cuItemsContainer) return;
const q = (cuSearchInput ? cuSearchInput.value : '').toLowerCase().trim();
const selectedList = Array.isArray(this.state.customer_user_id)
? this.state.customer_user_id
: (typeof this.state.customer_user_id === 'string' && this.state.customer_user_id ? this.state.customer_user_id.split(',') : []);
const filtered = (App.lookups.customerUsers || []).filter(u => {
const fullName = `${u.last_name} ${u.first_name} (${u.login})`.toLowerCase();
return fullName.includes(q);
});
cuItemsContainer.innerHTML = filtered.map(u => {
const isSelected = selectedList.includes(String(u.login));
const activeStyle = isSelected ? 'background: var(--accent-primary); color: #fff;' : '';
return `
<div class="customer-user-multiselect-item ${isSelected ? 'active' : ''}" data-value="${u.login}" style="padding: 6px var(--space-sm); border-radius: var(--radius-sm); font-size: 0.85rem; cursor: pointer; user-select: none; transition: background 0.1s ease; ${activeStyle}">
${App.escapeHtml(u.last_name)} ${App.escapeHtml(u.first_name)} <span style="font-size:0.75rem;opacity:0.8;">(${App.escapeHtml(u.login)})</span>
</div>
`;
}).join('');
// Bind clicks to items
cuItemsContainer.querySelectorAll('.customer-user-multiselect-item').forEach(item => {
item.addEventListener('click', (e) => {
e.stopPropagation();
const login = item.dataset.value;
const currentSelected = Array.isArray(this.state.customer_user_id)
? [...this.state.customer_user_id]
: (typeof this.state.customer_user_id === 'string' && this.state.customer_user_id ? this.state.customer_user_id.split(',') : []);
const idx = currentSelected.indexOf(login);
if (idx > -1) {
currentSelected.splice(idx, 1);
item.classList.remove('active');
item.style.background = '';
item.style.color = '';
} else {
currentSelected.push(login);
item.classList.add('active');
item.style.background = 'var(--accent-primary)';
item.style.color = '#fff';
}
this.state.customer_user_id = currentSelected.join(',');
});
});
};
if (cuDropdown && cuPopover) {
cuDropdown.addEventListener('click', (e) => {
e.stopPropagation();
// Close other popovers
const statePopover = document.getElementById('state-multiselect-popover');
if (statePopover) statePopover.style.display = 'none';
const isOpen = cuPopover.style.display === 'block';
cuPopover.style.display = isOpen ? 'none' : 'block';
if (!isOpen) {
if (cuSearchInput) {
cuSearchInput.value = '';
}
renderCustomerUserItems();
setTimeout(() => {
if (cuSearchInput) cuSearchInput.focus();
}, 50);
}
});
cuPopover.addEventListener('click', (e) => e.stopPropagation());
document.addEventListener('click', () => {
cuPopover.style.display = 'none';
});
if (cuSearchInput) {
cuSearchInput.addEventListener('input', () => {
renderCustomerUserItems();
});
}
}
if (cuOkBtn) {
cuOkBtn.addEventListener('click', () => {
this.save();
this.updateCustomerUserMultiselectLabel(App.lookups);
if (cuPopover) cuPopover.style.display = 'none';
if (onFilterChange) onFilterChange();
});
}
if (cuClearBtn) {
cuClearBtn.addEventListener('click', () => {
this.state.customer_user_id = '';
this.save();
this.updateCustomerUserMultiselectLabel(App.lookups);
if (cuSearchInput) cuSearchInput.value = '';
renderCustomerUserItems();
if (cuPopover) cuPopover.style.display = 'none';
if (onFilterChange) onFilterChange();
});
}
const resetBtn = document.getElementById('filter-reset');
if (resetBtn) {
resetBtn.addEventListener('click', () => {
@@ -282,7 +443,7 @@ const Filters = {
}
});
// Also clear multiselect items and label
// Also clear state multiselect items and label
const statePopover = document.getElementById('state-multiselect-popover');
if (statePopover) {
statePopover.querySelectorAll('.state-multiselect-item').forEach(item => {
@@ -293,6 +454,19 @@ const Filters = {
}
this.updateStateMultiselectLabel(App.lookups);
// Also clear customer user multiselect items and label
const cuPopover = document.getElementById('customer-user-multiselect-popover');
if (cuPopover) {
const searchInput = cuPopover.querySelector('#customer-user-search-input');
if (searchInput) searchInput.value = '';
cuPopover.querySelectorAll('.customer-user-multiselect-item').forEach(item => {
item.classList.remove('active');
item.style.background = '';
item.style.color = '';
});
}
this.updateCustomerUserMultiselectLabel(App.lookups);
if (onFilterChange) onFilterChange();
});
}