Add deep linking to tickets for easy sharing
Tickets now get a unique URL hash (#support/ticket/ID) when opened. Copy link button in ticket header copies the full URL to clipboard. Opening a ticket link navigates directly to the ticket detail view. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
35
script.js
35
script.js
@@ -209,11 +209,14 @@ async function showDashboard() {
|
||||
populateCompanySelector();
|
||||
// Avaa oikea tabi URL-hashin perusteella (tai customers oletuks)
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
const [mainHash, subHash] = hash.split('/');
|
||||
const hashParts = hash.split('/');
|
||||
const mainHash = hashParts[0];
|
||||
const subHash = hashParts[1];
|
||||
const extraHash = hashParts[2]; // esim. ticket ID: #support/ticket/123
|
||||
const validTabs = ['customers', 'leads', 'tekniikka', 'ohjeet', 'todo', 'documents', 'laitetilat', 'netadmin', 'archive', 'changelog', 'support', 'users', 'settings', 'companies'];
|
||||
// ohjeet, laitetilat, archive ovat nyt sub-tabeja — switchToTab hoitaa uudelleenohjauksen
|
||||
const startTab = validTabs.includes(mainHash) ? mainHash : 'support';
|
||||
switchToTab(startTab, subHash);
|
||||
switchToTab(startTab, subHash, extraHash);
|
||||
}
|
||||
|
||||
function populateCompanySelector() {
|
||||
@@ -258,7 +261,7 @@ async function switchCompany(companyId) {
|
||||
|
||||
// ==================== TABS ====================
|
||||
|
||||
function switchToTab(target, subTab) {
|
||||
function switchToTab(target, subTab, extra) {
|
||||
// Yhteensopivuus: vanhat hash-linkit → uusi rakenne
|
||||
if (target === 'ohjeet') { target = 'support'; subTab = 'ohjeet'; }
|
||||
if (target === 'archive') { target = 'customers'; subTab = 'archive'; }
|
||||
@@ -299,10 +302,17 @@ function switchToTab(target, subTab) {
|
||||
if (target === 'changelog') loadChangelog();
|
||||
if (target === 'todo') { loadTodos(); if (subTab) switchTodoSubTab(subTab); }
|
||||
if (target === 'support') {
|
||||
loadTickets(); showTicketListView();
|
||||
loadTickets();
|
||||
if (document.getElementById('ticket-auto-refresh').checked) startTicketAutoRefresh();
|
||||
const supportSubMap = { ohjeet: 'support-ohjeet', saannot: 'support-saannot', vastauspohjat: 'support-vastauspohjat', asetukset: 'support-asetukset' };
|
||||
switchSupportSubTab(supportSubMap[subTab] || 'support-tickets');
|
||||
if (subTab === 'ticket' && extra) {
|
||||
// Suora linkki tikettiin: #support/ticket/ID
|
||||
switchSupportSubTab('support-tickets');
|
||||
showTicketDetail(decodeURIComponent(extra));
|
||||
} else {
|
||||
showTicketListView();
|
||||
const supportSubMap = { ohjeet: 'support-ohjeet', saannot: 'support-saannot', vastauspohjat: 'support-vastauspohjat', asetukset: 'support-asetukset' };
|
||||
switchSupportSubTab(supportSubMap[subTab] || 'support-tickets');
|
||||
}
|
||||
}
|
||||
if (target === 'documents') {
|
||||
if (subTab && subTab !== 'kokoukset') {
|
||||
@@ -1618,12 +1628,13 @@ async function showTicketDetail(id, companyId = '') {
|
||||
const ticket = await apiCall('ticket_detail&id=' + encodeURIComponent(id) + ticketCompanyParam());
|
||||
currentTicketId = id;
|
||||
currentTicketData = ticket;
|
||||
window.location.hash = 'support/ticket/' + encodeURIComponent(id);
|
||||
|
||||
// Header
|
||||
document.getElementById('ticket-detail-header').innerHTML = `
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;margin-bottom:1.25rem;">
|
||||
<div>
|
||||
<h2 style="color:#0f3460;margin-bottom:0.25rem;font-size:1.2rem;">${ticket.ticket_number ? `<span style="color:#888;font-weight:normal;font-size:0.9rem;">#${ticket.ticket_number}</span> ` : ''}${esc(ticket.subject)}</h2>
|
||||
<h2 style="color:#0f3460;margin-bottom:0.25rem;font-size:1.2rem;">${ticket.ticket_number ? `<span style="color:#888;font-weight:normal;font-size:0.9rem;">#${ticket.ticket_number}</span> ` : ''}${esc(ticket.subject)} <button id="btn-copy-ticket-link" title="Kopioi linkki" style="background:none;border:none;cursor:pointer;font-size:0.9rem;vertical-align:middle;opacity:0.5;">🔗</button></h2>
|
||||
<div style="font-size:0.85rem;color:#888;" id="ticket-sender-line">
|
||||
${esc(ticket.from_name)} <${esc(ticket.from_email)}> · Luotu ${esc(ticket.created)}
|
||||
</div>
|
||||
@@ -1783,6 +1794,15 @@ async function showTicketDetail(id, companyId = '') {
|
||||
createTodoFromTicket(ticket);
|
||||
});
|
||||
|
||||
// Kopioi linkki tikettiin
|
||||
document.getElementById('btn-copy-ticket-link')?.addEventListener('click', () => {
|
||||
const url = window.location.origin + window.location.pathname + '#support/ticket/' + encodeURIComponent(id);
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
const btn = document.getElementById('btn-copy-ticket-link');
|
||||
if (btn) { btn.textContent = '✓'; setTimeout(() => { btn.textContent = '🔗'; }, 1500); }
|
||||
}).catch(() => { prompt('Kopioi linkki:', url); });
|
||||
});
|
||||
|
||||
// Tags: add new tag on Enter
|
||||
document.getElementById('ticket-tag-input').addEventListener('keydown', async (e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
@@ -1979,6 +1999,7 @@ function showTicketListView() {
|
||||
document.getElementById('ticket-detail-view').style.display = 'none';
|
||||
document.getElementById('ticket-list-view').style.display = 'block';
|
||||
currentTicketId = null;
|
||||
window.location.hash = 'support';
|
||||
// Reset bulk selection
|
||||
bulkSelectedIds.clear();
|
||||
const selectAll = document.getElementById('bulk-select-all');
|
||||
|
||||
Reference in New Issue
Block a user