fix: ora le ricerche lavorano nuovamente in cache. feat: aggiunto pulsante per eliminare rapidamente una ricerca testuale.
This commit is contained in:
1 parent
37c5f0c500
commit
c5e17ca57a
2 files changed
+249
-78
No files matched your search
+212
-71
@@ -9,18 +9,22 @@ const TicketListView = {
|
||||
sortDir: 'DESC',
|
||||
selectedIds: new Set(),
|
||||
selectedOrder: [],
|
||||
lastQueryParams: null,
|
||||
cachedData: null,
|
||||
rawTickets: null,
|
||||
otrsBaseUrl: null,
|
||||
lastMode: null,
|
||||
lastSearchTerm: '',
|
||||
needsRefresh: false,
|
||||
|
||||
invalidateCache() {
|
||||
this.lastQueryParams = null;
|
||||
this.cachedData = null;
|
||||
this.rawTickets = null;
|
||||
this.lastMode = null;
|
||||
this.needsRefresh = true;
|
||||
},
|
||||
|
||||
async render(force = false) {
|
||||
const isMyTickets = window.location.hash.startsWith('#/tickets/my');
|
||||
Filters.currentMode = isMyTickets ? 'my' : 'general';
|
||||
const newMode = isMyTickets ? 'my' : 'general';
|
||||
Filters.currentMode = newMode;
|
||||
Filters.load(); // Load state for current mode
|
||||
|
||||
if (isMyTickets) {
|
||||
@@ -29,79 +33,208 @@ const TicketListView = {
|
||||
Filters.save();
|
||||
}
|
||||
|
||||
const params = Filters.toQueryParams();
|
||||
params.set('page', this.currentPage);
|
||||
params.set('per_page', this.perPage);
|
||||
params.set('sort_by', this.sortBy);
|
||||
params.set('sort_dir', this.sortDir);
|
||||
|
||||
const searchInput = document.getElementById('global-search');
|
||||
if (searchInput && searchInput.value.trim()) {
|
||||
params.set('search', searchInput.value.trim());
|
||||
}
|
||||
|
||||
const currentQueryString = `${Filters.currentMode}:${params.toString()}`;
|
||||
const globalSearchVal = searchInput ? searchInput.value.trim() : '';
|
||||
const container = document.getElementById('view-container');
|
||||
if (!container) return;
|
||||
|
||||
// Return cached results instantly if query params haven't changed and no force refresh requested
|
||||
if (!force && !this.needsRefresh && this.lastQueryParams === currentQueryString && this.cachedData) {
|
||||
const currentHash = (window.location.hash || '#/dashboard').split('?')[0];
|
||||
if (currentHash !== '#/tickets' && currentHash !== '#/tickets/my') {
|
||||
return;
|
||||
const shouldFetch = force ||
|
||||
this.needsRefresh ||
|
||||
!this.rawTickets ||
|
||||
this.lastMode !== newMode ||
|
||||
this.lastSearchTerm !== globalSearchVal;
|
||||
|
||||
if (shouldFetch) {
|
||||
if (!container.querySelector('.ticket-table-wrapper') && !container.querySelector('.ticket-table')) {
|
||||
container.innerHTML = '<div class="loading-screen"><div class="spinner"></div><p>Caricamento ticket...</p></div>';
|
||||
}
|
||||
this.renderContent(container, this.cachedData);
|
||||
this.bindEvents(this.cachedData);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!container.querySelector('.ticket-table-wrapper') && !container.querySelector('.ticket-table')) {
|
||||
container.innerHTML = '<div class="loading-screen"><div class="spinner"></div><p>Caricamento ticket...</p></div>';
|
||||
}
|
||||
|
||||
try {
|
||||
// Fetch lookups for filter dropdowns
|
||||
await App.ensureLookups();
|
||||
|
||||
// Fetch agent settings for tickets_per_page
|
||||
try {
|
||||
const settings = await App.api('/api/dashboard/settings');
|
||||
if (settings && settings.tickets_per_page) {
|
||||
this.perPage = settings.tickets_per_page;
|
||||
await App.ensureLookups();
|
||||
|
||||
try {
|
||||
const settings = await App.api('/api/dashboard/settings');
|
||||
if (settings && settings.tickets_per_page) {
|
||||
this.perPage = settings.tickets_per_page;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to load agent settings:', err);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set('per_page', '2000');
|
||||
if (isMyTickets) {
|
||||
const activeAgentId = localStorage.getItem('activeAgentId') || '1';
|
||||
params.set('user_id', activeAgentId);
|
||||
}
|
||||
if (globalSearchVal) {
|
||||
params.set('search', globalSearchVal);
|
||||
}
|
||||
|
||||
const data = await App.api(`/api/tickets?${params.toString()}`);
|
||||
|
||||
const currentHash = (window.location.hash || '#/dashboard').split('?')[0];
|
||||
if (currentHash !== '#/tickets' && currentHash !== '#/tickets/my') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rawTickets = data.tickets || [];
|
||||
this.otrsBaseUrl = data.otrsBaseUrl || '';
|
||||
this.lastMode = newMode;
|
||||
this.lastSearchTerm = globalSearchVal;
|
||||
this.needsRefresh = false;
|
||||
} catch (err) {
|
||||
console.warn('Failed to load agent settings:', err);
|
||||
}
|
||||
const currentHash = (window.location.hash || '#/dashboard').split('?')[0];
|
||||
if (currentHash !== '#/tickets' && currentHash !== '#/tickets/my') {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await App.api(`/api/tickets?${params.toString()}`);
|
||||
|
||||
// Route guard: if the user navigated away while the async search was in flight, do not overwrite the view
|
||||
const currentHash = (window.location.hash || '#/dashboard').split('?')[0];
|
||||
if (currentHash !== '#/tickets' && currentHash !== '#/tickets/my') {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">Errore caricamento ticket</div>
|
||||
<div class="empty-state-sub">${App.escapeHtml(err.message)}</div>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Save to cache
|
||||
this.lastQueryParams = currentQueryString;
|
||||
this.cachedData = data;
|
||||
this.needsRefresh = false;
|
||||
|
||||
this.renderContent(container, data);
|
||||
this.bindEvents(data);
|
||||
|
||||
} catch (err) {
|
||||
const currentHash = (window.location.hash || '#/dashboard').split('?')[0];
|
||||
if (currentHash !== '#/tickets' && currentHash !== '#/tickets/my') {
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-icon">⚠️</div>
|
||||
<div class="empty-state-text">Errore caricamento ticket</div>
|
||||
<div class="empty-state-sub">${App.escapeHtml(err.message)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
this.renderLocalResults();
|
||||
},
|
||||
|
||||
renderLocalResults() {
|
||||
const container = document.getElementById('view-container');
|
||||
if (!container) return;
|
||||
|
||||
const filtered = this.getFilteredTickets();
|
||||
const total = filtered.length;
|
||||
const totalPages = Math.ceil(total / this.perPage) || 1;
|
||||
|
||||
if (this.currentPage > totalPages) {
|
||||
this.currentPage = 1;
|
||||
}
|
||||
|
||||
const sorted = this.sortTickets(filtered);
|
||||
const startIndex = (this.currentPage - 1) * this.perPage;
|
||||
const paginatedTickets = sorted.slice(startIndex, startIndex + this.perPage);
|
||||
|
||||
const dataObj = {
|
||||
tickets: paginatedTickets,
|
||||
total: total,
|
||||
page: this.currentPage,
|
||||
per_page: this.perPage,
|
||||
total_pages: totalPages,
|
||||
otrsBaseUrl: this.otrsBaseUrl
|
||||
};
|
||||
|
||||
this.renderContent(container, dataObj);
|
||||
this.bindEvents(dataObj);
|
||||
},
|
||||
|
||||
getFilteredTickets() {
|
||||
if (!this.rawTickets) return [];
|
||||
let list = [...this.rawTickets];
|
||||
const s = Filters.state;
|
||||
|
||||
// Sub-search text filter
|
||||
if (s.sub_search) {
|
||||
const q = String(s.sub_search).toLowerCase().trim();
|
||||
if (q) {
|
||||
list = list.filter(t => {
|
||||
const fields = [
|
||||
t.tn,
|
||||
t.title,
|
||||
t.queue_name,
|
||||
t.state_name,
|
||||
t.priority_name,
|
||||
t.owner_first,
|
||||
t.owner_last,
|
||||
`${t.owner_first || ''} ${t.owner_last || ''}`,
|
||||
t.customer_first,
|
||||
t.customer_last,
|
||||
`${t.customer_first || ''} ${t.customer_last || ''}`,
|
||||
t.customer_id,
|
||||
t.customer_user_id
|
||||
];
|
||||
return fields.some(f => f && String(f).toLowerCase().includes(q));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// State filter
|
||||
if (s.state_id) {
|
||||
const selectedStates = String(s.state_id).split(',').filter(Boolean);
|
||||
list = list.filter(t => selectedStates.includes(String(t.ticket_state_id)));
|
||||
}
|
||||
|
||||
// Queue filter
|
||||
if (s.queue_id) {
|
||||
const selectedQueues = String(s.queue_id).split(',').filter(Boolean);
|
||||
list = list.filter(t => selectedQueues.includes(String(t.queue_id)));
|
||||
}
|
||||
|
||||
// Priority filter
|
||||
if (s.priority_id) {
|
||||
const selectedPriorities = String(s.priority_id).split(',').filter(Boolean);
|
||||
list = list.filter(t => selectedPriorities.includes(String(t.ticket_priority_id)));
|
||||
}
|
||||
|
||||
// Owner filter (general mode)
|
||||
if (s.user_id && Filters.currentMode !== 'my') {
|
||||
const selectedUsers = String(s.user_id).split(',').filter(Boolean);
|
||||
list = list.filter(t => selectedUsers.includes(String(t.user_id)));
|
||||
}
|
||||
|
||||
// Customer user filter
|
||||
if (s.customer_user_id) {
|
||||
const selectedCustomers = String(s.customer_user_id).split(',').filter(Boolean).map(c => c.toLowerCase());
|
||||
list = list.filter(t => t.customer_user_id && selectedCustomers.includes(String(t.customer_user_id).toLowerCase()));
|
||||
}
|
||||
|
||||
// Date from
|
||||
if (s.date_from) {
|
||||
const fromDate = new Date(s.date_from);
|
||||
list = list.filter(t => new Date(t.create_time) >= fromDate);
|
||||
}
|
||||
|
||||
// Date to
|
||||
if (s.date_to) {
|
||||
const toDate = new Date(s.date_to + 'T23:59:59');
|
||||
list = list.filter(t => new Date(t.create_time) <= toDate);
|
||||
}
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
sortTickets(tickets) {
|
||||
const list = [...tickets];
|
||||
const key = this.sortBy;
|
||||
const isAsc = this.sortDir === 'ASC';
|
||||
|
||||
list.sort((a, b) => {
|
||||
let valA, valB;
|
||||
if (key === 'queue') {
|
||||
valA = a.queue_name || '';
|
||||
valB = b.queue_name || '';
|
||||
} else if (key === 'state') {
|
||||
valA = a.state_name || '';
|
||||
valB = b.state_name || '';
|
||||
} else if (key === 'priority') {
|
||||
valA = a.ticket_priority_id || 0;
|
||||
valB = b.ticket_priority_id || 0;
|
||||
} else {
|
||||
valA = a[key] || '';
|
||||
valB = b[key] || '';
|
||||
}
|
||||
|
||||
if (typeof valA === 'string') {
|
||||
return isAsc ? valA.localeCompare(valB, 'it', { sensitivity: 'base' }) : valB.localeCompare(valA, 'it', { sensitivity: 'base' });
|
||||
}
|
||||
return isAsc ? valA - valB : valB - valA;
|
||||
});
|
||||
|
||||
return list;
|
||||
},
|
||||
|
||||
renderContent(container, data) {
|
||||
@@ -287,9 +420,17 @@ const TicketListView = {
|
||||
this.currentPage = 1;
|
||||
this.selectedIds.clear();
|
||||
this.selectedOrder = [];
|
||||
this.render();
|
||||
this.renderLocalResults();
|
||||
});
|
||||
|
||||
// Refresh button
|
||||
const refreshFilterBtn = document.getElementById('filter-refresh');
|
||||
if (refreshFilterBtn) {
|
||||
refreshFilterBtn.addEventListener('click', () => {
|
||||
this.render(true);
|
||||
});
|
||||
}
|
||||
|
||||
// Sort events
|
||||
document.querySelectorAll('.ticket-table th.sortable').forEach(th => {
|
||||
th.addEventListener('click', () => {
|
||||
@@ -301,7 +442,7 @@ const TicketListView = {
|
||||
this.sortDir = 'DESC';
|
||||
}
|
||||
this.currentPage = 1;
|
||||
this.render();
|
||||
this.renderLocalResults();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -607,10 +748,10 @@ const TicketListView = {
|
||||
// Pagination
|
||||
document.querySelectorAll('.pagination-btn[data-page]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
this.currentPage = parseInt(btn.dataset.page);
|
||||
this.currentPage = parseInt(btn.dataset.page, 10);
|
||||
this.selectedIds.clear();
|
||||
this.selectedOrder = [];
|
||||
this.render();
|
||||
this.renderLocalResults();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -626,7 +767,7 @@ const TicketListView = {
|
||||
Toast.success(`Righe per pagina aggiornate a ${newLimit}!`);
|
||||
this.perPage = newLimit;
|
||||
this.currentPage = 1;
|
||||
this.render();
|
||||
this.renderLocalResults();
|
||||
} catch (err) {
|
||||
Toast.error('Errore durante il salvataggio dell\'impostazione: ' + err.message);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user