106 lines
4 KiB
JavaScript
Executable file
106 lines
4 KiB
JavaScript
Executable file
// ticketHistory.js — fetches recent tickets and renders them into the card.
|
|
// Filters to tickets belonging to the asset's assigned contact.
|
|
// Falls back to the 5 most recent customer tickets if no contact is assigned
|
|
// or if the contact has no tickets.
|
|
import { getTickets } from '../api/syncro.js';
|
|
|
|
let _loaded = false;
|
|
let _open = false;
|
|
|
|
export function initTicketHistory(asset) {
|
|
_loaded = false;
|
|
_open = false;
|
|
|
|
const toggle = document.getElementById('ticket-toggle');
|
|
const list = document.getElementById('ticket-list');
|
|
if (!toggle || !list) return;
|
|
|
|
toggle.classList.remove('open');
|
|
list.classList.remove('visible');
|
|
list.innerHTML = `<div class="contact-loading">${spinnerHTML()} Loading tickets…</div>`;
|
|
|
|
toggle.onclick = async () => {
|
|
_open = !_open;
|
|
toggle.classList.toggle('open', _open);
|
|
list.classList.toggle('visible', _open);
|
|
|
|
if (_open && !_loaded) {
|
|
await loadTickets(list, asset.customer_id, asset.contact_id ?? null);
|
|
_loaded = true;
|
|
}
|
|
};
|
|
|
|
// Wire up asset history toggle (static content, no async load needed)
|
|
const historyToggle = document.getElementById('history-toggle');
|
|
const historyList = document.getElementById('history-list');
|
|
if (historyToggle && historyList) {
|
|
let historyOpen = false;
|
|
historyToggle.classList.remove('open');
|
|
historyList.classList.remove('visible');
|
|
historyToggle.onclick = () => {
|
|
historyOpen = !historyOpen;
|
|
historyToggle.classList.toggle('open', historyOpen);
|
|
historyList.classList.toggle('visible', historyOpen);
|
|
};
|
|
}
|
|
}
|
|
|
|
async function loadTickets(listEl, customerId, contactId) {
|
|
listEl.innerHTML = `<div class="contact-loading">${spinnerHTML()} Loading tickets…</div>`;
|
|
try {
|
|
const all = await getTickets(customerId);
|
|
|
|
let tickets;
|
|
let scopeLabel;
|
|
|
|
if (contactId) {
|
|
const contactMatches = all.filter(t => t.contact_id === contactId);
|
|
if (contactMatches.length > 0) {
|
|
tickets = contactMatches.slice(0, 10);
|
|
scopeLabel = null; // no label needed — these are contact-scoped
|
|
} else {
|
|
// Contact exists but has no tickets — fall back to recent customer tickets
|
|
tickets = all.slice(0, 5);
|
|
scopeLabel = 'No tickets for assigned contact — showing recent customer tickets';
|
|
}
|
|
} else {
|
|
tickets = all.slice(0, 5);
|
|
scopeLabel = 'No contact assigned — showing recent customer tickets';
|
|
}
|
|
|
|
if (!tickets.length) {
|
|
listEl.innerHTML = `<div class="contact-empty">No tickets found.</div>`;
|
|
return;
|
|
}
|
|
|
|
listEl.innerHTML =
|
|
(scopeLabel ? `<div class="contact-empty" style="font-style:italic;margin-bottom:6px">${esc(scopeLabel)}</div>` : '') +
|
|
tickets.map(t => ticketItemHTML(t)).join('');
|
|
} catch (err) {
|
|
listEl.innerHTML = `<div class="contact-empty" style="color:var(--red)">Failed to load tickets: ${esc(err.message)}</div>`;
|
|
}
|
|
}
|
|
|
|
function ticketItemHTML(t) {
|
|
const statusKey = (t.status ?? '').toLowerCase().replace(/\s+/g, '-');
|
|
const date = t.created_at
|
|
? new Date(t.created_at).toLocaleDateString('en-US', { month:'short', day:'numeric', year:'numeric' })
|
|
: '';
|
|
const contactName = t.contact_fullname ? ` · ${esc(t.contact_fullname)}` : '';
|
|
return `
|
|
<div class="ticket-item status-${statusKey}">
|
|
<div>
|
|
<div class="ticket-subject">${esc(t.subject ?? t.problem_type ?? 'Ticket #' + (t.number ?? t.id))}</div>
|
|
<div class="ticket-meta">#${t.number ?? t.id} · ${date}${contactName}</div>
|
|
</div>
|
|
<div class="ticket-status ${statusKey || 'default'}">${esc(t.status ?? 'Unknown')}</div>
|
|
</div>`;
|
|
}
|
|
|
|
function spinnerHTML() {
|
|
return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="animation:spin .7s linear infinite;display:inline-block;vertical-align:middle"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>`;
|
|
}
|
|
|
|
function esc(s) {
|
|
return String(s ?? '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
}
|