style: riviste parti dell'interfaccia dei ticket. feat: aggiunta possiblità di modificare il tempo inserito nelle note ed aggiunta voce per tempo nel menu azioni rapide
This commit is contained in:
+176
-21
@@ -3,21 +3,50 @@
|
||||
* Manages ticket list filter state and renders filter dropdowns.
|
||||
*/
|
||||
const Filters = {
|
||||
state: {
|
||||
queue_id: '',
|
||||
state_id: '',
|
||||
priority_id: '',
|
||||
user_id: '',
|
||||
date_from: '',
|
||||
date_to: '',
|
||||
currentMode: 'general', // 'general' or 'my'
|
||||
|
||||
allStates: {
|
||||
general: {
|
||||
queue_id: '',
|
||||
state_id: '',
|
||||
priority_id: '',
|
||||
user_id: '',
|
||||
date_from: '',
|
||||
date_to: '',
|
||||
},
|
||||
my: {
|
||||
queue_id: '',
|
||||
state_id: '',
|
||||
priority_id: '',
|
||||
user_id: '',
|
||||
date_from: '',
|
||||
date_to: '',
|
||||
}
|
||||
},
|
||||
|
||||
// Dynamic state getter based on active mode
|
||||
get state() {
|
||||
return this.allStates[this.currentMode];
|
||||
},
|
||||
|
||||
set state(val) {
|
||||
this.allStates[this.currentMode] = val;
|
||||
},
|
||||
|
||||
/** Load saved filters from localStorage */
|
||||
load() {
|
||||
try {
|
||||
const saved = localStorage.getItem('otrs_turbo_filters');
|
||||
if (saved) {
|
||||
Object.assign(this.state, JSON.parse(saved));
|
||||
const savedGeneral = localStorage.getItem('otrs_turbo_filters_general');
|
||||
if (savedGeneral) {
|
||||
Object.assign(this.allStates.general, JSON.parse(savedGeneral));
|
||||
}
|
||||
const savedMy = localStorage.getItem('otrs_turbo_filters_my');
|
||||
if (savedMy) {
|
||||
Object.assign(this.allStates.my, JSON.parse(savedMy));
|
||||
} else {
|
||||
// Default to active agent if not saved yet
|
||||
const activeAgentId = localStorage.getItem('activeAgentId') || '1';
|
||||
this.allStates.my.user_id = activeAgentId;
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
@@ -25,13 +54,17 @@ const Filters = {
|
||||
/** Save filters to localStorage */
|
||||
save() {
|
||||
try {
|
||||
localStorage.setItem('otrs_turbo_filters', JSON.stringify(this.state));
|
||||
localStorage.setItem(`otrs_turbo_filters_${this.currentMode}`, JSON.stringify(this.state));
|
||||
} catch (e) { /* ignore */ }
|
||||
},
|
||||
|
||||
/** Reset all filters */
|
||||
reset() {
|
||||
this.state = { queue_id: '', state_id: '', priority_id: '', user_id: '', date_from: '', date_to: '' };
|
||||
if (this.currentMode === 'my') {
|
||||
const activeAgentId = localStorage.getItem('activeAgentId') || '1';
|
||||
this.state.user_id = activeAgentId;
|
||||
}
|
||||
this.save();
|
||||
},
|
||||
|
||||
@@ -44,6 +77,36 @@ const Filters = {
|
||||
return params;
|
||||
},
|
||||
|
||||
/** Helper to compute trigger button label text */
|
||||
getStateMultiselectLabel(lookups) {
|
||||
const selectedList = Array.isArray(this.state.state_id)
|
||||
? this.state.state_id
|
||||
: (typeof this.state.state_id === 'string' && this.state.state_id ? this.state.state_id.split(',') : []);
|
||||
|
||||
if (selectedList.length === 0) {
|
||||
return 'Tutti';
|
||||
}
|
||||
const selectedNames = (lookups.states || [])
|
||||
.filter(s => selectedList.includes(String(s.id)))
|
||||
.map(s => s.name);
|
||||
|
||||
if (selectedNames.length === (lookups.states || []).length) {
|
||||
return 'Tutti';
|
||||
} else if (selectedNames.length <= 2) {
|
||||
return selectedNames.join(', ');
|
||||
} else {
|
||||
return `${selectedNames.length} selezionati`;
|
||||
}
|
||||
},
|
||||
|
||||
/** Update label DOM element dynamically */
|
||||
updateStateMultiselectLabel(lookups) {
|
||||
const labelEl = document.getElementById('state-multiselect-label');
|
||||
if (labelEl) {
|
||||
labelEl.textContent = this.getStateMultiselectLabel(lookups);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Render filter bar HTML.
|
||||
* @param {Object} lookups - { queues, states, priorities, users }
|
||||
@@ -59,14 +122,36 @@ const Filters = {
|
||||
}).join('');
|
||||
};
|
||||
|
||||
const currentLabel = this.getStateMultiselectLabel(lookups);
|
||||
|
||||
return `
|
||||
<div class="filters-bar" id="filters-bar">
|
||||
<div class="filter-group">
|
||||
<div class="filter-group" style="position:relative;">
|
||||
<span class="filter-label">Stato</span>
|
||||
<select class="filter-select" data-filter="state_id" id="filter-state">
|
||||
<option value="">Tutti</option>
|
||||
${makeOptions(lookups.states || [], 'id', 'name', this.state.state_id)}
|
||||
</select>
|
||||
<div class="multiselect-dropdown" id="state-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="state-multiselect-label" style="text-overflow:ellipsis; overflow:hidden; white-space:nowrap; max-width:130px;">${App.escapeHtml(currentLabel)}</span>
|
||||
<span style="font-size:0.6rem; color:var(--text-muted); margin-left:6px;">▼</span>
|
||||
</div>
|
||||
<div class="multiselect-popover" id="state-multiselect-popover" style="display:none; position:absolute; left:0; top:100%; width: 220px; 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;">
|
||||
<div style="display:flex; flex-direction:column; gap:4px; padding-bottom:6px; border-bottom:1px solid var(--border-subtle);">
|
||||
${(lookups.states || []).map(s => {
|
||||
const selectedList = Array.isArray(this.state.state_id)
|
||||
? this.state.state_id
|
||||
: (typeof this.state.state_id === 'string' && this.state.state_id ? this.state.state_id.split(',') : []);
|
||||
const isSelected = selectedList.includes(String(s.id));
|
||||
const activeStyle = isSelected ? 'background: var(--accent-primary); color: #fff;' : '';
|
||||
return `
|
||||
<div class="state-multiselect-item ${isSelected ? 'active' : ''}" data-value="${s.id}" 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(s.name)}
|
||||
</div>
|
||||
`;
|
||||
}).join('')}
|
||||
</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="state-multiselect-clear" style="height:20px; padding:0 8px; font-size:0.75rem;">Reset</button>
|
||||
<button type="button" class="btn btn-primary btn-xs" id="state-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">Coda</span>
|
||||
@@ -109,11 +194,7 @@ const Filters = {
|
||||
const isMyTickets = window.location.hash.startsWith('#/tickets/my');
|
||||
const selects = document.querySelectorAll('.filter-select[data-filter]');
|
||||
selects.forEach(sel => {
|
||||
if (isMyTickets && sel.dataset.filter === 'user_id') {
|
||||
sel.disabled = true;
|
||||
} else {
|
||||
sel.disabled = false;
|
||||
}
|
||||
sel.disabled = false;
|
||||
|
||||
sel.addEventListener('change', (e) => {
|
||||
this.state[e.target.dataset.filter] = e.target.value;
|
||||
@@ -122,6 +203,68 @@ const Filters = {
|
||||
});
|
||||
});
|
||||
|
||||
// Multiselect dropdown toggle event
|
||||
const dropdown = document.getElementById('state-multiselect-dropdown');
|
||||
const popover = document.getElementById('state-multiselect-popover');
|
||||
const okBtn = document.getElementById('state-multiselect-ok');
|
||||
const clearBtn = document.getElementById('state-multiselect-clear');
|
||||
|
||||
if (dropdown && popover) {
|
||||
dropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isOpen = popover.style.display === 'block';
|
||||
popover.style.display = isOpen ? 'none' : 'block';
|
||||
});
|
||||
|
||||
popover.addEventListener('click', (e) => e.stopPropagation());
|
||||
|
||||
document.addEventListener('click', () => {
|
||||
popover.style.display = 'none';
|
||||
});
|
||||
|
||||
// Bind item clicks
|
||||
popover.querySelectorAll('.state-multiselect-item').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const isActive = item.classList.toggle('active');
|
||||
if (isActive) {
|
||||
item.style.background = 'var(--accent-primary)';
|
||||
item.style.color = '#fff';
|
||||
} else {
|
||||
item.style.background = '';
|
||||
item.style.color = '';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (okBtn) {
|
||||
okBtn.addEventListener('click', () => {
|
||||
const activeItems = popover.querySelectorAll('.state-multiselect-item.active');
|
||||
const ids = Array.from(activeItems).map(item => item.dataset.value);
|
||||
this.state.state_id = ids.join(',');
|
||||
this.save();
|
||||
this.updateStateMultiselectLabel(App.lookups);
|
||||
popover.style.display = 'none';
|
||||
if (onFilterChange) onFilterChange();
|
||||
});
|
||||
}
|
||||
|
||||
if (clearBtn) {
|
||||
clearBtn.addEventListener('click', () => {
|
||||
popover.querySelectorAll('.state-multiselect-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
item.style.background = '';
|
||||
item.style.color = '';
|
||||
});
|
||||
this.state.state_id = '';
|
||||
this.save();
|
||||
this.updateStateMultiselectLabel(App.lookups);
|
||||
popover.style.display = 'none';
|
||||
if (onFilterChange) onFilterChange();
|
||||
});
|
||||
}
|
||||
|
||||
const resetBtn = document.getElementById('filter-reset');
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', () => {
|
||||
@@ -138,6 +281,18 @@ const Filters = {
|
||||
s.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
// Also clear multiselect items and label
|
||||
const statePopover = document.getElementById('state-multiselect-popover');
|
||||
if (statePopover) {
|
||||
statePopover.querySelectorAll('.state-multiselect-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
item.style.background = '';
|
||||
item.style.color = '';
|
||||
});
|
||||
}
|
||||
this.updateStateMultiselectLabel(App.lookups);
|
||||
|
||||
if (onFilterChange) onFilterChange();
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user