feat: possibilità di ingrandire gli elenchi delle pagine dashbaord, ticket e ticket a mio carico
This commit is contained in:
@@ -27,6 +27,20 @@ db.exec(`
|
|||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_settings (
|
||||||
|
agent_id INTEGER PRIMARY KEY,
|
||||||
|
preview_limit INTEGER NOT NULL DEFAULT 10,
|
||||||
|
tickets_per_page INTEGER NOT NULL DEFAULT 50
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.exec(`ALTER TABLE agent_settings ADD COLUMN tickets_per_page INTEGER NOT NULL DEFAULT 50`);
|
||||||
|
} catch (e) {
|
||||||
|
// Column already exists
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a UUID v7 (time-ordered).
|
* Generate a UUID v7 (time-ordered).
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -88,7 +88,19 @@ const DashboardView = {
|
|||||||
|
|
||||||
<!-- Recent Tickets -->
|
<!-- Recent Tickets -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-title">Ticket Recenti</div>
|
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:var(--space-md); flex-wrap:wrap; gap:var(--space-xs);">
|
||||||
|
<div class="card-title" style="margin-bottom:0;">Ticket Recenti</div>
|
||||||
|
<div style="display:flex; align-items:center; gap:var(--space-xs); font-size:0.85rem; color:var(--text-secondary);">
|
||||||
|
<span>Mostra:</span>
|
||||||
|
<select id="dashboard-preview-limit" class="form-select" style="padding: 4px 28px 4px 8px; font-size: 0.8rem; height: 28px; min-width: 70px; margin: 0; background-position: right 8px center; border-color: var(--border-light);">
|
||||||
|
<option value="5" ${stats.preview_limit === 5 ? 'selected' : ''}>5</option>
|
||||||
|
<option value="10" ${stats.preview_limit === 10 ? 'selected' : ''}>10</option>
|
||||||
|
<option value="20" ${stats.preview_limit === 20 ? 'selected' : ''}>20</option>
|
||||||
|
<option value="50" ${stats.preview_limit === 50 ? 'selected' : ''}>50</option>
|
||||||
|
<option value="100" ${stats.preview_limit === 100 ? 'selected' : ''}>100</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
${(stats.recent_tickets || []).length > 0 ? `
|
${(stats.recent_tickets || []).length > 0 ? `
|
||||||
<table class="recent-tickets-table">
|
<table class="recent-tickets-table">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -132,6 +144,24 @@ const DashboardView = {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Bind preview limit change event
|
||||||
|
const limitSelect = document.getElementById('dashboard-preview-limit');
|
||||||
|
if (limitSelect) {
|
||||||
|
limitSelect.addEventListener('change', async () => {
|
||||||
|
const newLimit = parseInt(limitSelect.value, 10);
|
||||||
|
try {
|
||||||
|
await App.api('/api/dashboard/settings', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ preview_limit: newLimit }),
|
||||||
|
});
|
||||||
|
Toast.success(`Limite anteprima aggiornato a ${newLimit} ticket!`);
|
||||||
|
this.render();
|
||||||
|
} catch (err) {
|
||||||
|
Toast.error('Errore durante il salvataggio dell\'impostazione: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Update open ticket count in sidebar badge
|
// Update open ticket count in sidebar badge
|
||||||
const badge = document.getElementById('open-ticket-count');
|
const badge = document.getElementById('open-ticket-count');
|
||||||
if (badge) {
|
if (badge) {
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ const TicketListView = {
|
|||||||
// Fetch lookups for filter dropdowns
|
// Fetch lookups for filter dropdowns
|
||||||
await App.ensureLookups();
|
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;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Failed to load agent settings:', err);
|
||||||
|
}
|
||||||
|
|
||||||
// Build query params
|
// Build query params
|
||||||
const isMyTickets = window.location.hash.startsWith('#/tickets/my');
|
const isMyTickets = window.location.hash.startsWith('#/tickets/my');
|
||||||
Filters.currentMode = isMyTickets ? 'my' : 'general';
|
Filters.currentMode = isMyTickets ? 'my' : 'general';
|
||||||
@@ -168,6 +178,19 @@ const TicketListView = {
|
|||||||
|
|
||||||
renderPagination(page, per_page, total, total_pages, isTop) {
|
renderPagination(page, per_page, total, total_pages, isTop) {
|
||||||
const marginStyle = isTop ? 'margin-bottom: var(--space-md); margin-top: 0;' : 'margin-top: var(--space-md); margin-bottom: 0;';
|
const marginStyle = isTop ? 'margin-bottom: var(--space-md); margin-top: 0;' : 'margin-top: var(--space-md); margin-bottom: 0;';
|
||||||
|
const limitSelectHtml = `
|
||||||
|
<div style="display:inline-flex; align-items:center; gap:var(--space-xs); font-size:0.8rem; color:var(--text-secondary); margin-right:var(--space-md);">
|
||||||
|
<span>Righe:</span>
|
||||||
|
<select class="form-select ticket-per-page-select" style="padding: 2px 24px 2px 6px; font-size: 0.75rem; height: 26px; min-width: 65px; margin: 0; background-position: right 6px center; border-color: var(--border-light);">
|
||||||
|
<option value="10" ${per_page === 10 ? 'selected' : ''}>10</option>
|
||||||
|
<option value="20" ${per_page === 20 ? 'selected' : ''}>20</option>
|
||||||
|
<option value="50" ${per_page === 50 ? 'selected' : ''}>50</option>
|
||||||
|
<option value="100" ${per_page === 100 ? 'selected' : ''}>100</option>
|
||||||
|
<option value="200" ${per_page === 200 ? 'selected' : ''}>200</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
if (total_pages > 1) {
|
if (total_pages > 1) {
|
||||||
return `
|
return `
|
||||||
<div class="pagination" style="${marginStyle}">
|
<div class="pagination" style="${marginStyle}">
|
||||||
@@ -175,6 +198,7 @@ const TicketListView = {
|
|||||||
Mostrando ${((page - 1) * per_page) + 1}–${Math.min(page * per_page, total)} di ${total} ticket
|
Mostrando ${((page - 1) * per_page) + 1}–${Math.min(page * per_page, total)} di ${total} ticket
|
||||||
</div>
|
</div>
|
||||||
<div class="pagination-controls">
|
<div class="pagination-controls">
|
||||||
|
${limitSelectHtml}
|
||||||
<button class="pagination-btn" data-page="1" ${page <= 1 ? 'disabled' : ''}>«</button>
|
<button class="pagination-btn" data-page="1" ${page <= 1 ? 'disabled' : ''}>«</button>
|
||||||
<button class="pagination-btn" data-page="${page - 1}" ${page <= 1 ? 'disabled' : ''}>‹</button>
|
<button class="pagination-btn" data-page="${page - 1}" ${page <= 1 ? 'disabled' : ''}>‹</button>
|
||||||
${this.renderPageButtons(page, total_pages)}
|
${this.renderPageButtons(page, total_pages)}
|
||||||
@@ -187,7 +211,9 @@ const TicketListView = {
|
|||||||
return `
|
return `
|
||||||
<div class="pagination" style="${marginStyle}">
|
<div class="pagination" style="${marginStyle}">
|
||||||
<div class="pagination-info">${total} ticket totali</div>
|
<div class="pagination-info">${total} ticket totali</div>
|
||||||
<div></div>
|
<div class="pagination-controls">
|
||||||
|
${limitSelectHtml}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -400,6 +426,25 @@ const TicketListView = {
|
|||||||
this.render();
|
this.render();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Page size change
|
||||||
|
document.querySelectorAll('.ticket-per-page-select').forEach(select => {
|
||||||
|
select.addEventListener('change', async () => {
|
||||||
|
const newLimit = parseInt(select.value, 10);
|
||||||
|
try {
|
||||||
|
await App.api('/api/dashboard/settings', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ tickets_per_page: newLimit }),
|
||||||
|
});
|
||||||
|
Toast.success(`Righe per pagina aggiornate a ${newLimit}!`);
|
||||||
|
this.perPage = newLimit;
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.render();
|
||||||
|
} catch (err) {
|
||||||
|
Toast.error('Errore durante il salvataggio dell\'impostazione: ' + err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateBatchBar() {
|
updateBatchBar() {
|
||||||
|
|||||||
+58
-2
@@ -74,6 +74,17 @@ seedPhrases();
|
|||||||
// GET /api/dashboard/stats — Dashboard statistics
|
// GET /api/dashboard/stats — Dashboard statistics
|
||||||
router.get('/stats', async (req, res) => {
|
router.get('/stats', async (req, res) => {
|
||||||
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||||
|
|
||||||
|
let previewLimit = 10;
|
||||||
|
try {
|
||||||
|
const row = db.prepare("SELECT preview_limit FROM agent_settings WHERE agent_id = ?").get(activeAgentId);
|
||||||
|
if (row) {
|
||||||
|
previewLimit = row.preview_limit;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading agent_settings:', err.message);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// All queries in parallel for speed
|
// All queries in parallel for speed
|
||||||
const [
|
const [
|
||||||
@@ -138,7 +149,7 @@ router.get('/stats', async (req, res) => {
|
|||||||
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
JOIN ticket_state_type tst ON ts.type_id = tst.id
|
||||||
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')`
|
WHERE tst.name IN ('new', 'open', 'pending reminder', 'pending auto')`
|
||||||
),
|
),
|
||||||
// 10 most recent tickets
|
// Custom most recent tickets based on agent settings
|
||||||
pool.query(
|
pool.query(
|
||||||
`SELECT t.id, t.tn, t.title, ts.name AS state_name,
|
`SELECT t.id, t.tn, t.title, ts.name AS state_name,
|
||||||
tp.name AS priority_name, tp.color AS priority_color,
|
tp.name AS priority_name, tp.color AS priority_color,
|
||||||
@@ -148,7 +159,8 @@ router.get('/stats', async (req, res) => {
|
|||||||
JOIN ticket_priority tp ON t.ticket_priority_id = tp.id
|
JOIN ticket_priority tp ON t.ticket_priority_id = tp.id
|
||||||
JOIN queue q ON t.queue_id = q.id
|
JOIN queue q ON t.queue_id = q.id
|
||||||
ORDER BY t.create_time DESC
|
ORDER BY t.create_time DESC
|
||||||
LIMIT 10`
|
LIMIT $1`,
|
||||||
|
[previewLimit]
|
||||||
),
|
),
|
||||||
// Escalated tickets
|
// Escalated tickets
|
||||||
pool.query(
|
pool.query(
|
||||||
@@ -178,6 +190,7 @@ router.get('/stats', async (req, res) => {
|
|||||||
recent_tickets: recentTickets.rows,
|
recent_tickets: recentTickets.rows,
|
||||||
escalated: parseInt(escalated.rows[0].count),
|
escalated: parseInt(escalated.rows[0].count),
|
||||||
total_my_open: parseInt(myOpenCount.rows[0].count),
|
total_my_open: parseInt(myOpenCount.rows[0].count),
|
||||||
|
preview_limit: previewLimit,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching dashboard stats:', err);
|
console.error('Error fetching dashboard stats:', err);
|
||||||
@@ -206,4 +219,47 @@ router.get('/phrases', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// GET /api/dashboard/settings — Retrieve settings for the agent
|
||||||
|
router.get('/settings', (req, res) => {
|
||||||
|
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||||
|
try {
|
||||||
|
const row = db.prepare("SELECT preview_limit, tickets_per_page FROM agent_settings WHERE agent_id = ?").get(activeAgentId);
|
||||||
|
if (row) {
|
||||||
|
res.json(row);
|
||||||
|
} else {
|
||||||
|
res.json({ preview_limit: 10, tickets_per_page: 50 });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error reading agent settings:', err);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST /api/dashboard/settings — Save settings for the agent
|
||||||
|
router.post('/settings', (req, res) => {
|
||||||
|
const activeAgentId = parseInt(req.headers['x-agent-id'], 10) || 1;
|
||||||
|
const { preview_limit, tickets_per_page } = req.body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let currentSettings = { preview_limit: 10, tickets_per_page: 50 };
|
||||||
|
const row = db.prepare("SELECT preview_limit, tickets_per_page FROM agent_settings WHERE agent_id = ?").get(activeAgentId);
|
||||||
|
if (row) {
|
||||||
|
currentSettings = row;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPreviewLimit = preview_limit !== undefined ? parseInt(preview_limit, 10) : currentSettings.preview_limit;
|
||||||
|
const newTicketsPerPage = tickets_per_page !== undefined ? parseInt(tickets_per_page, 10) : currentSettings.tickets_per_page;
|
||||||
|
|
||||||
|
db.prepare(`
|
||||||
|
INSERT OR REPLACE INTO agent_settings (agent_id, preview_limit, tickets_per_page)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
`).run(activeAgentId, newPreviewLimit, newTicketsPerPage);
|
||||||
|
|
||||||
|
res.json({ success: true, preview_limit: newPreviewLimit, tickets_per_page: newTicketsPerPage });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error saving agent settings:', err);
|
||||||
|
res.status(500).json({ error: err.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user