diff --git a/public/css/style.css b/public/css/style.css
index cb631e1..9e31f8f 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -1793,21 +1793,22 @@ body {
top: 100%;
left: 0;
right: 0;
- background: #1f2937;
- border: 1px solid #4b5563;
+ background: var(--bg-card);
+ border: 1px solid var(--border-light);
border-radius: var(--radius-md);
max-height: 220px;
overflow-y: auto;
z-index: 1000;
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
+ box-shadow: var(--shadow-lg);
+ backdrop-filter: blur(8px);
}
.autocomplete-suggestion-item {
padding: 10px 14px;
cursor: pointer;
- border-bottom: 1px solid rgba(156, 163, 175, 0.15);
+ border-bottom: 1px solid var(--border-subtle);
font-size: 0.85rem;
- color: #f3f4f6;
+ color: var(--text-primary);
transition: all var(--transition-fast);
}
@@ -1817,7 +1818,7 @@ body {
}
.autocomplete-suggestion-item:hover span {
- color: #e5e7eb !important;
+ color: #ffffff !important;
}
.autocomplete-suggestion-item:last-child {
@@ -1943,8 +1944,8 @@ body {
}
.bulk-select option {
- background-color: #111827;
- /* Sfondo scuro per gli stati e i tipi */
+ background-color: var(--bg-secondary);
+ /* Sfondo per gli stati e i tipi */
color: var(--text-primary);
}
@@ -1955,23 +1956,24 @@ body {
width: max-content;
min-width: 100%;
max-width: 400px;
- background: #1f2937;
+ background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: var(--radius-md);
max-height: 380px;
/* Incrementata l'altezza per contenere più elementi */
overflow-y: auto;
z-index: 999;
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.6);
+ box-shadow: var(--shadow-lg);
+ backdrop-filter: blur(8px);
}
.bulk-suggestions-item {
padding: 6px 10px;
/* Compatto */
cursor: pointer;
- border-bottom: 1px solid rgba(156, 163, 175, 0.1);
+ border-bottom: 1px solid var(--border-subtle);
font-size: 0.8rem;
- color: #e5e7eb;
+ color: var(--text-primary);
}
.bulk-suggestions-item:hover {
@@ -1979,6 +1981,10 @@ body {
color: white;
}
+.bulk-suggestions-item:hover span {
+ color: white !important;
+}
+
.bulk-suggestions-item:last-child {
border-bottom: none;
}
diff --git a/public/js/views/ticketDetail.js b/public/js/views/ticketDetail.js
index d9b8302..6b4aca5 100644
--- a/public/js/views/ticketDetail.js
+++ b/public/js/views/ticketDetail.js
@@ -40,6 +40,10 @@ const TicketDetailView = {
queue_id: ticket.queue_id,
user_id: ticket.user_id,
type_id: ticket.type_id,
+ customer_id: ticket.customer_id,
+ customer_user_id: ticket.customer_user_id,
+ customer_first: ticket.customer_first,
+ customer_last: ticket.customer_last,
};
container.innerHTML = `
@@ -113,6 +117,13 @@ const TicketDetailView = {
` : ''}
+
+
+
+
+
+
+
@@ -340,41 +351,117 @@ const TicketDetailView = {
bindEvents() {
// Quick-edit change detection
- const selects = document.querySelectorAll('.quick-edit-select');
+ const fields = document.querySelectorAll('.quick-edit-select, #qe-customer-user-id, #qe-customer-id');
const saveBtn = document.getElementById('qe-save');
const resetBtn = document.getElementById('qe-reset');
const checkChanges = () => {
let hasChanges = false;
- selects.forEach(sel => {
- const field = sel.dataset.field;
+ fields.forEach(el => {
+ const field = el.dataset.field;
const original = String(this.originalValues[field] || '');
- const current = sel.value;
+ const current = el.value;
const changed = current !== original;
- sel.classList.toggle('changed', changed);
+ el.classList.toggle('changed', changed);
if (changed) hasChanges = true;
});
saveBtn.disabled = !hasChanges;
};
- selects.forEach(sel => sel.addEventListener('change', checkChanges));
+ fields.forEach(el => el.addEventListener('change', checkChanges));
+
+ // Customer User Autocomplete inside Quick Edit
+ const customerSearchInput = document.getElementById('qe-customer-search');
+ const customerSuggestionsDiv = document.getElementById('qe-customer-suggestions');
+ const customerUserIdInput = document.getElementById('qe-customer-user-id');
+ const customerIdInput = document.getElementById('qe-customer-id');
+
+ let customerDebounce;
+ if (customerSearchInput) {
+ customerSearchInput.addEventListener('input', () => {
+ clearTimeout(customerDebounce);
+ const q = customerSearchInput.value.trim();
+ if (q.length < 2) {
+ customerSuggestionsDiv.style.display = 'none';
+ customerUserIdInput.value = '';
+ customerIdInput.value = '';
+ customerUserIdInput.dispatchEvent(new Event('change'));
+ customerIdInput.dispatchEvent(new Event('change'));
+ return;
+ }
+
+ customerDebounce = setTimeout(async () => {
+ try {
+ const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
+ if (users.length === 0) {
+ customerSuggestionsDiv.innerHTML = '
Nessun utente trovato
';
+ customerSuggestionsDiv.style.display = 'block';
+ return;
+ }
+
+ customerSuggestionsDiv.innerHTML = users.map(u => `
+
+ ${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}
+ (Login: ${App.escapeHtml(u.login)} | Azienda: ${App.escapeHtml(u.customer_id || '—')})
+
+ `).join('');
+ customerSuggestionsDiv.style.display = 'block';
+
+ // Bind click
+ customerSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
+ if (item.dataset.login) {
+ item.addEventListener('click', () => {
+ customerSearchInput.value = item.dataset.name;
+ customerUserIdInput.value = item.dataset.login;
+ customerIdInput.value = item.dataset.customerId || '';
+ customerSuggestionsDiv.style.display = 'none';
+ customerUserIdInput.dispatchEvent(new Event('change'));
+ customerIdInput.dispatchEvent(new Event('change'));
+ });
+ }
+ });
+ } catch (err) {
+ console.error(err);
+ }
+ }, 300);
+ });
+ }
+
+ // Close suggestions on click outside
+ document.addEventListener('click', (e) => {
+ if (customerSearchInput && e.target !== customerSearchInput && e.target !== customerSuggestionsDiv) {
+ customerSuggestionsDiv.style.display = 'none';
+ }
+ });
// Reset quick-edit
resetBtn.addEventListener('click', () => {
- selects.forEach(sel => {
- sel.value = this.originalValues[sel.dataset.field] || '';
- sel.classList.remove('changed');
+ fields.forEach(el => {
+ el.value = this.originalValues[el.dataset.field] || '';
+ el.classList.remove('changed');
});
+ if (customerSearchInput) {
+ const first = this.originalValues['customer_first'] || '';
+ const last = this.originalValues['customer_last'] || '';
+ customerSearchInput.value = first ? `${first} ${last}` : (this.originalValues['customer_user_id'] || '');
+ }
saveBtn.disabled = true;
});
// Save quick-edit
saveBtn.addEventListener('click', async () => {
const updates = {};
- selects.forEach(sel => {
- const field = sel.dataset.field;
- const val = sel.value ? parseInt(sel.value) : null;
- if (val !== null && val !== this.originalValues[field]) {
+ fields.forEach(el => {
+ const field = el.dataset.field;
+ let val;
+ if (field === 'customer_id' || field === 'customer_user_id') {
+ val = el.value || null;
+ } else {
+ val = el.value ? parseInt(el.value) : null;
+ }
+
+ const origVal = this.originalValues[field] || null;
+ if (val !== origVal) {
updates[field] = val;
}
});
diff --git a/routes/tickets.js b/routes/tickets.js
index 4f5930d..d1198b8 100644
--- a/routes/tickets.js
+++ b/routes/tickets.js
@@ -483,6 +483,8 @@ router.patch('/:id', async (req, res) => {
if (updates.type_id !== undefined) ticketFields.TypeID = updates.type_id;
if (updates.title !== undefined) ticketFields.Title = updates.title;
if (updates.ticket_lock_id !== undefined) ticketFields.LockID = updates.ticket_lock_id;
+ if (updates.customer_id !== undefined) ticketFields.CustomerID = updates.customer_id;
+ if (updates.customer_user_id !== undefined) ticketFields.CustomerUser = updates.customer_user_id;
// Auto sblocco check
if (updates.ticket_state_id) {
@@ -525,7 +527,7 @@ router.patch('/:id', async (req, res) => {
// Fetch current ticket for history comparison
const currentResult = await client.query(
- `SELECT ticket_state_id, ticket_priority_id, queue_id, user_id, type_id, title, ticket_lock_id
+ `SELECT ticket_state_id, ticket_priority_id, queue_id, user_id, type_id, title, ticket_lock_id, customer_id, customer_user_id
FROM ticket WHERE id = $1`,
[id]
);
@@ -557,7 +559,7 @@ router.patch('/:id', async (req, res) => {
const setParams = [];
let pIdx = 1;
- const allowedFields = ['ticket_state_id', 'ticket_priority_id', 'queue_id', 'user_id', 'type_id', 'title', 'ticket_lock_id'];
+ const allowedFields = ['ticket_state_id', 'ticket_priority_id', 'queue_id', 'user_id', 'type_id', 'title', 'ticket_lock_id', 'customer_id', 'customer_user_id'];
for (const field of allowedFields) {
if (updates[field] !== undefined && updates[field] !== current[field]) {
setClauses.push(`${field} = $${pIdx++}`);
@@ -589,6 +591,8 @@ router.patch('/:id', async (req, res) => {
user_id: 'OwnerUpdate',
type_id: 'TypeUpdate',
ticket_lock_id: 'Lock',
+ customer_id: 'CustomerUpdate',
+ customer_user_id: 'CustomerUpdate',
};
for (const field of allowedFields) {