/**
* Ticket Create View
* Minimal, fast form for creating new tickets.
*/
const TicketCreateView = {
savedState: null,
attachments: [],
updateAttachmentList() {
const listEl = document.getElementById('create-file-list');
if (!listEl) return;
listEl.innerHTML = this.attachments.map((att, idx) => `
📎 ${App.escapeHtml(att.filename)} (${Math.round(att.content.length * 0.75 / 1024)} KB)
`).join('');
// Bind remove clicks
listEl.querySelectorAll('.upload-file-remove').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = parseInt(btn.dataset.index, 10);
this.attachments.splice(idx, 1);
this.updateAttachmentList();
});
});
},
saveState() {
const stateEl = document.getElementById('create-state');
if (!stateEl) return;
this.savedState = {
state_id: stateEl.value,
title: document.getElementById('create-title').value,
queue_name: document.getElementById('create-queue-search').value,
queue_id: document.getElementById('create-queue').value,
type_id: document.getElementById('create-type').value,
owner_name: document.getElementById('create-owner-search').value,
owner_id: document.getElementById('create-owner').value,
responsible_name: document.getElementById('create-responsible-search').value,
responsible_id: document.getElementById('create-responsible').value,
user_search: document.getElementById('create-user-search').value,
customer_user_id: document.getElementById('create-customer-user-id').value,
customer_id: document.getElementById('create-customer-id').value,
company_search: document.getElementById('create-company-search').value,
priority_id: document.getElementById('create-priority').value,
subject: document.getElementById('create-subject').value,
body: document.getElementById('create-body').value,
isAdvancedVisible: document.getElementById('advanced-options').style.display !== 'none'
};
},
async render() {
const container = document.getElementById('view-container');
container.innerHTML = '';
try {
await App.ensureLookups();
// Check if we have incoming query parameters (e.g. from Teams integration)
const hashParts = window.location.hash.split('?');
const searchStr = hashParts.length > 1 ? hashParts[1] : window.location.search;
const urlParams = new URLSearchParams(searchStr);
const incomingBody = urlParams.get('body');
const incomingCustomer = urlParams.get('customer');
const incomingSubject = urlParams.get('subject');
if (incomingBody || incomingCustomer || incomingSubject) {
let foundCustomer = null;
if (incomingCustomer) {
try {
const results = await App.api(`/api/customer-users/search?q=${encodeURIComponent(incomingCustomer)}`);
if (results && results.length > 0) {
foundCustomer = results[0];
}
} catch (e) {
console.warn('Failed to search customer for Teams integration:', e);
}
}
// Prepopulate default active agent details
const currentAgentId = localStorage.getItem('activeAgentId') || '1';
const activeAgent = (App.lookups.users || []).find(u => String(u.id) === String(currentAgentId));
const agentName = activeAgent ? `${activeAgent.first_name} ${activeAgent.last_name}` : '';
const agentId = activeAgent ? activeAgent.id : '';
this.savedState = {
state_id: '1', // default state
title: 'Segnalazione da Teams',
queue_name: App.lookups.queues && App.lookups.queues.length > 0 ? App.lookups.queues[0].name : '',
queue_id: App.lookups.queues && App.lookups.queues.length > 0 ? App.lookups.queues[0].id : '',
type_id: '',
owner_name: agentName,
owner_id: agentId,
responsible_name: agentName,
responsible_id: agentId,
user_search: foundCustomer ? `${foundCustomer.first_name} ${foundCustomer.last_name} <${foundCustomer.email}>` : (incomingCustomer || ''),
customer_user_id: foundCustomer ? foundCustomer.login : (incomingCustomer || ''),
customer_id: foundCustomer ? foundCustomer.customer_id : '',
company_search: foundCustomer ? foundCustomer.customer_id : '',
priority_id: '3',
subject: incomingSubject || 'Messaggio da Microsoft Teams',
body: incomingBody || '',
isAdvancedVisible: false
};
// Clean query parameters from URL to prevent duplicate inserts on reload
window.history.replaceState({}, document.title, window.location.pathname + window.location.hash.split('?')[0]);
}
container.innerHTML = `
Torna indietro
`;
this.bindEvents();
} catch (err) {
container.innerHTML = `
⚠️
Errore caricamento form
${App.escapeHtml(err.message)}
`;
}
},
bindEvents() {
const submitBtn = document.getElementById('create-submit');
const companySearchInput = document.getElementById('create-company-search');
const customerIdInput = document.getElementById('create-customer-id');
const userSearchInput = document.getElementById('create-user-search');
const userSuggestionsDiv = document.getElementById('user-suggestions');
const customerUserIdInput = document.getElementById('create-customer-user-id');
const ownerSearchInput = document.getElementById('create-owner-search');
const ownerSuggestionsDiv = document.getElementById('owner-suggestions');
const ownerIdInput = document.getElementById('create-owner');
const responsibleSearchInput = document.getElementById('create-responsible-search');
const responsibleSuggestionsDiv = document.getElementById('responsible-suggestions');
const responsibleIdInput = document.getElementById('create-responsible');
const queueSearchInput = document.getElementById('create-queue-search');
const queueSuggestionsDiv = document.getElementById('queue-suggestions');
const queueIdInput = document.getElementById('create-queue');
const stateIdInput = document.getElementById('create-state');
const toggleBtn = document.getElementById('toggle-advanced');
const advancedOptions = document.getElementById('advanced-options');
const arrow = document.getElementById('advanced-arrow');
// Restore saved state if exists, otherwise load defaults asynchronously
if (this.savedState) {
document.getElementById('create-state').value = this.savedState.state_id;
document.getElementById('create-title').value = this.savedState.title;
document.getElementById('create-queue-search').value = this.savedState.queue_name;
document.getElementById('create-queue').value = this.savedState.queue_id;
if (document.getElementById('create-type')) {
document.getElementById('create-type').value = this.savedState.type_id;
}
document.getElementById('create-owner-search').value = this.savedState.owner_name;
document.getElementById('create-owner').value = this.savedState.owner_id;
document.getElementById('create-responsible-search').value = this.savedState.responsible_name;
document.getElementById('create-responsible').value = this.savedState.responsible_id;
document.getElementById('create-user-search').value = this.savedState.user_search;
document.getElementById('create-customer-user-id').value = this.savedState.customer_user_id;
document.getElementById('create-customer-id').value = this.savedState.customer_id;
document.getElementById('create-company-search').value = this.savedState.company_search;
document.getElementById('create-priority').value = this.savedState.priority_id;
document.getElementById('create-subject').value = this.savedState.subject;
document.getElementById('create-body').value = this.savedState.body;
if (this.savedState.isAdvancedVisible) {
advancedOptions.style.display = 'grid';
if (arrow) arrow.style.transform = 'rotate(90deg)';
}
} else {
setTimeout(async () => {
// 1. Owner & Responsible pre-population with active agent
const currentAgentId = localStorage.getItem('activeAgentId') || '1';
const activeAgent = (App.lookups.users || []).find(u => String(u.id) === String(currentAgentId));
if (activeAgent) {
if (ownerSearchInput && ownerIdInput) {
ownerSearchInput.value = `${activeAgent.first_name} ${activeAgent.last_name}`;
ownerIdInput.value = activeAgent.id;
}
if (responsibleSearchInput && responsibleIdInput) {
responsibleSearchInput.value = `${activeAgent.first_name} ${activeAgent.last_name}`;
responsibleIdInput.value = activeAgent.id;
}
}
// 2. Customer User pre-population with first match
try {
const companies = await App.api('/api/customer-companies/search?q=cliente');
if (companies.length > 0 && customerIdInput && companySearchInput) {
const defaultCompany = companies[0];
customerIdInput.value = defaultCompany.customer_id;
companySearchInput.value = defaultCompany.customer_id;
// Search users for this company
const users = await App.api(`/api/customer-users/search?q=&customer_company_id=${encodeURIComponent(defaultCompany.customer_id)}`);
if (users.length > 0 && userSearchInput && customerUserIdInput) {
const defaultUser = users[0];
userSearchInput.value = `${defaultUser.first_name} ${defaultUser.last_name}`;
customerUserIdInput.value = defaultUser.login;
} else {
// Fallback: use company name as customer user ID
userSearchInput.value = defaultCompany.name;
customerUserIdInput.value = defaultCompany.customer_id;
}
}
} catch (err) {
console.error('Error pre-populating defaults:', err);
}
// 3. Queue pre-population
try {
const queues = await App.api('/api/queues/search?q=');
if (queues.length > 0 && queueSearchInput && queueIdInput) {
queueSearchInput.value = queues[0].name;
queueIdInput.value = queues[0].id;
}
} catch (err) {
console.error('Error pre-populating queues:', err);
}
}, 50);
}
// Collapsible Options Toggle
if (toggleBtn && advancedOptions && arrow) {
toggleBtn.addEventListener('click', () => {
const isHidden = advancedOptions.style.display === 'none';
advancedOptions.style.display = isHidden ? 'grid' : 'none';
arrow.style.transform = isHidden ? 'rotate(90deg)' : 'rotate(0deg)';
});
}
// User Autocomplete
let userDebounce;
if (userSearchInput) {
userSearchInput.addEventListener('input', () => {
clearTimeout(userDebounce);
const q = userSearchInput.value.trim();
if (q.length < 2) {
userSuggestionsDiv.style.display = 'none';
customerUserIdInput.value = '';
return;
}
userDebounce = setTimeout(async () => {
try {
const users = await App.api(`/api/customer-users/search?q=${encodeURIComponent(q)}`);
if (users.length === 0) {
userSuggestionsDiv.innerHTML = 'Nessun utente trovato
';
userSuggestionsDiv.style.display = 'block';
return;
}
userSuggestionsDiv.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('');
userSuggestionsDiv.style.display = 'block';
// Bind click
userSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
if (item.dataset.login) {
item.addEventListener('click', () => {
userSearchInput.value = item.dataset.name;
customerUserIdInput.value = item.dataset.login;
userSuggestionsDiv.style.display = 'none';
// Auto-fill company inside Advanced Options
if (item.dataset.customerId) {
customerIdInput.value = item.dataset.customerId;
companySearchInput.value = item.dataset.customerId;
}
});
}
});
} catch (err) {
console.error(err);
}
}, 300);
});
}
// Owner Autocomplete (dynamic backend search)
let ownerDebounce;
if (ownerSearchInput) {
ownerSearchInput.addEventListener('input', () => {
clearTimeout(ownerDebounce);
const q = ownerSearchInput.value.trim();
// Do not block empty query to allow all agent results on focus
ownerDebounce = setTimeout(async () => {
try {
const agents = await App.api(`/api/agents/search?q=${encodeURIComponent(q)}`);
if (agents.length === 0) {
ownerSuggestionsDiv.innerHTML = 'Nessun agente trovato
';
ownerSuggestionsDiv.style.display = 'block';
return;
}
ownerSuggestionsDiv.innerHTML = agents.map(u => `
${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}
(Login: ${App.escapeHtml(u.login)})
`).join('');
ownerSuggestionsDiv.style.display = 'block';
// Bind click
ownerSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
if (item.dataset.id) {
item.addEventListener('click', () => {
ownerSearchInput.value = item.dataset.name;
ownerIdInput.value = item.dataset.id;
ownerSuggestionsDiv.style.display = 'none';
});
}
});
} catch (err) {
console.error(err);
}
}, 300);
});
ownerSearchInput.addEventListener('focus', () => {
ownerSearchInput.value = '';
ownerIdInput.value = '';
ownerSearchInput.dispatchEvent(new Event('input'));
});
}
// Responsible Autocomplete (dynamic backend search)
let responsibleDebounce;
if (responsibleSearchInput) {
responsibleSearchInput.addEventListener('input', () => {
clearTimeout(responsibleDebounce);
const q = responsibleSearchInput.value.trim();
// Do not block empty query to allow all agent results on focus
responsibleDebounce = setTimeout(async () => {
try {
const agents = await App.api(`/api/agents/search?q=${encodeURIComponent(q)}`);
if (agents.length === 0) {
responsibleSuggestionsDiv.innerHTML = 'Nessun agente trovato
';
responsibleSuggestionsDiv.style.display = 'block';
return;
}
responsibleSuggestionsDiv.innerHTML = agents.map(u => `
${App.escapeHtml(u.first_name)} ${App.escapeHtml(u.last_name)}
(Login: ${App.escapeHtml(u.login)})
`).join('');
responsibleSuggestionsDiv.style.display = 'block';
// Bind click
responsibleSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
if (item.dataset.id) {
item.addEventListener('click', () => {
responsibleSearchInput.value = item.dataset.name;
responsibleIdInput.value = item.dataset.id;
responsibleSuggestionsDiv.style.display = 'none';
});
}
});
} catch (err) {
console.error(err);
}
}, 300);
});
responsibleSearchInput.addEventListener('focus', () => {
responsibleSearchInput.value = '';
responsibleIdInput.value = '';
responsibleSearchInput.dispatchEvent(new Event('input'));
});
}
// Queue Autocomplete
let queueDebounce;
if (queueSearchInput) {
queueSearchInput.addEventListener('input', () => {
clearTimeout(queueDebounce);
const q = queueSearchInput.value.trim();
queueDebounce = setTimeout(async () => {
try {
const queues = await App.api(`/api/queues/search?q=${encodeURIComponent(q)}`);
if (queues.length === 0) {
queueSuggestionsDiv.innerHTML = 'Nessuna coda trovata
';
queueSuggestionsDiv.style.display = 'block';
return;
}
queueSuggestionsDiv.innerHTML = queues.map(q => `
${App.escapeHtml(q.name)}
`).join('');
queueSuggestionsDiv.style.display = 'block';
// Bind click
queueSuggestionsDiv.querySelectorAll('.autocomplete-suggestion-item').forEach(item => {
if (item.dataset.id) {
item.addEventListener('click', () => {
queueSearchInput.value = item.dataset.name;
queueIdInput.value = item.dataset.id;
queueSuggestionsDiv.style.display = 'none';
});
}
});
} catch (err) {
console.error(err);
}
}, 150);
});
queueSearchInput.addEventListener('focus', () => {
queueSearchInput.value = '';
queueIdInput.value = '';
queueSearchInput.dispatchEvent(new Event('input'));
});
}
// Close suggestions on click outside
document.addEventListener('click', (e) => {
if (userSearchInput && e.target !== userSearchInput && e.target !== userSuggestionsDiv) {
userSuggestionsDiv.style.display = 'none';
}
if (ownerSearchInput && e.target !== ownerSearchInput && e.target !== ownerSuggestionsDiv) {
ownerSuggestionsDiv.style.display = 'none';
}
if (responsibleSearchInput && e.target !== responsibleSearchInput && e.target !== responsibleSuggestionsDiv) {
responsibleSuggestionsDiv.style.display = 'none';
}
if (queueSearchInput && e.target !== queueSearchInput && e.target !== queueSuggestionsDiv) {
queueSuggestionsDiv.style.display = 'none';
}
});
submitBtn.addEventListener('click', async () => {
const title = document.getElementById('create-title').value.trim();
const queue_id = queueIdInput.value;
const state_id = stateIdInput.value;
const priority_id = document.getElementById('create-priority').value;
const type_id = document.getElementById('create-type')?.value;
const customerId = customerIdInput.value;
let customerUserId = customerUserIdInput.value;
const ownerId = ownerIdInput.value;
const responsibleId = responsibleIdInput.value;
// Validation
if (!title) {
Toast.warning('Il titolo è obbligatorio');
document.getElementById('create-title').focus();
return;
}
if (!queue_id) {
Toast.warning('Seleziona una coda');
queueSearchInput.focus();
return;
}
if (!state_id) {
Toast.warning('Seleziona uno stato');
document.getElementById('create-state').focus();
return;
}
if (!customerId && !customerUserId) {
Toast.warning('Seleziona un Utente Cliente');
userSearchInput.focus();
return;
}
// Company fallback if no individual user selected
if (customerId && !customerUserId) {
customerUserId = customerId;
}
const payload = {
title,
queue_id: parseInt(queue_id),
state_id: parseInt(state_id),
priority_id: parseInt(priority_id),
user_id: ownerId ? parseInt(ownerId) : undefined,
responsible_user_id: responsibleId ? parseInt(responsibleId) : undefined,
type_id: type_id ? parseInt(type_id) : undefined,
customer_id: customerId || undefined,
customer_user_id: customerUserId || undefined,
subject: document.getElementById('create-subject').value.trim() || undefined,
body: document.getElementById('create-body').value.trim() || undefined,
attachments: this.attachments.length > 0 ? this.attachments : undefined
};
try {
submitBtn.disabled = true;
submitBtn.innerHTML = ' Creazione...';
const result = await App.api('/api/tickets', {
method: 'POST',
body: JSON.stringify(payload),
});
Toast.success(`Ticket #${result.tn} creato!`);
this.savedState = null; // Clear cached state on success
this.attachments = []; // Clear attachments array
// Navigate to the new ticket
window.location.hash = `#/tickets/${result.id}`;
} catch (err) {
Toast.error('Errore creazione: ' + err.message);
submitBtn.disabled = false;
submitBtn.innerHTML = `
Crea Ticket
`;
}
});
// Attachments Upload Handlers
const attachBtn = document.getElementById('btn-add-attachments');
const fileInput = document.getElementById('create-attachments');
if (attachBtn && fileInput) {
attachBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
const files = Array.from(e.target.files);
for (const file of files) {
const reader = new FileReader();
reader.onload = () => {
const base64Data = reader.result.split(',')[1];
this.attachments.push({
filename: file.name,
content: base64Data,
content_type: file.type
});
this.updateAttachmentList();
};
reader.readAsDataURL(file);
}
fileInput.value = ''; // Reset input to allow re-selecting the same file
});
}
// Save state on any input/change in the form
document.querySelectorAll('.create-form input, .create-form select, .create-form textarea').forEach(el => {
el.addEventListener('input', () => this.saveState());
el.addEventListener('change', () => this.saveState());
});
},
};