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();
|
populateCompanySelector();
|
||||||
// Avaa oikea tabi URL-hashin perusteella (tai customers oletuks)
|
// Avaa oikea tabi URL-hashin perusteella (tai customers oletuks)
|
||||||
const hash = window.location.hash.replace('#', '');
|
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'];
|
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
|
// ohjeet, laitetilat, archive ovat nyt sub-tabeja — switchToTab hoitaa uudelleenohjauksen
|
||||||
const startTab = validTabs.includes(mainHash) ? mainHash : 'support';
|
const startTab = validTabs.includes(mainHash) ? mainHash : 'support';
|
||||||
switchToTab(startTab, subHash);
|
switchToTab(startTab, subHash, extraHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateCompanySelector() {
|
function populateCompanySelector() {
|
||||||
@@ -258,7 +261,7 @@ async function switchCompany(companyId) {
|
|||||||
|
|
||||||
// ==================== TABS ====================
|
// ==================== TABS ====================
|
||||||
|
|
||||||
function switchToTab(target, subTab) {
|
function switchToTab(target, subTab, extra) {
|
||||||
// Yhteensopivuus: vanhat hash-linkit → uusi rakenne
|
// Yhteensopivuus: vanhat hash-linkit → uusi rakenne
|
||||||
if (target === 'ohjeet') { target = 'support'; subTab = 'ohjeet'; }
|
if (target === 'ohjeet') { target = 'support'; subTab = 'ohjeet'; }
|
||||||
if (target === 'archive') { target = 'customers'; subTab = 'archive'; }
|
if (target === 'archive') { target = 'customers'; subTab = 'archive'; }
|
||||||
@@ -299,10 +302,17 @@ function switchToTab(target, subTab) {
|
|||||||
if (target === 'changelog') loadChangelog();
|
if (target === 'changelog') loadChangelog();
|
||||||
if (target === 'todo') { loadTodos(); if (subTab) switchTodoSubTab(subTab); }
|
if (target === 'todo') { loadTodos(); if (subTab) switchTodoSubTab(subTab); }
|
||||||
if (target === 'support') {
|
if (target === 'support') {
|
||||||
loadTickets(); showTicketListView();
|
loadTickets();
|
||||||
if (document.getElementById('ticket-auto-refresh').checked) startTicketAutoRefresh();
|
if (document.getElementById('ticket-auto-refresh').checked) startTicketAutoRefresh();
|
||||||
const supportSubMap = { ohjeet: 'support-ohjeet', saannot: 'support-saannot', vastauspohjat: 'support-vastauspohjat', asetukset: 'support-asetukset' };
|
if (subTab === 'ticket' && extra) {
|
||||||
switchSupportSubTab(supportSubMap[subTab] || 'support-tickets');
|
// 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 (target === 'documents') {
|
||||||
if (subTab && subTab !== 'kokoukset') {
|
if (subTab && subTab !== 'kokoukset') {
|
||||||
@@ -1618,12 +1628,13 @@ async function showTicketDetail(id, companyId = '') {
|
|||||||
const ticket = await apiCall('ticket_detail&id=' + encodeURIComponent(id) + ticketCompanyParam());
|
const ticket = await apiCall('ticket_detail&id=' + encodeURIComponent(id) + ticketCompanyParam());
|
||||||
currentTicketId = id;
|
currentTicketId = id;
|
||||||
currentTicketData = ticket;
|
currentTicketData = ticket;
|
||||||
|
window.location.hash = 'support/ticket/' + encodeURIComponent(id);
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
document.getElementById('ticket-detail-header').innerHTML = `
|
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 style="display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:1rem;margin-bottom:1.25rem;">
|
||||||
<div>
|
<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">
|
<div style="font-size:0.85rem;color:#888;" id="ticket-sender-line">
|
||||||
${esc(ticket.from_name)} <${esc(ticket.from_email)}> · Luotu ${esc(ticket.created)}
|
${esc(ticket.from_name)} <${esc(ticket.from_email)}> · Luotu ${esc(ticket.created)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1783,6 +1794,15 @@ async function showTicketDetail(id, companyId = '') {
|
|||||||
createTodoFromTicket(ticket);
|
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
|
// Tags: add new tag on Enter
|
||||||
document.getElementById('ticket-tag-input').addEventListener('keydown', async (e) => {
|
document.getElementById('ticket-tag-input').addEventListener('keydown', async (e) => {
|
||||||
if (e.key !== 'Enter') return;
|
if (e.key !== 'Enter') return;
|
||||||
@@ -1979,6 +1999,7 @@ function showTicketListView() {
|
|||||||
document.getElementById('ticket-detail-view').style.display = 'none';
|
document.getElementById('ticket-detail-view').style.display = 'none';
|
||||||
document.getElementById('ticket-list-view').style.display = 'block';
|
document.getElementById('ticket-list-view').style.display = 'block';
|
||||||
currentTicketId = null;
|
currentTicketId = null;
|
||||||
|
window.location.hash = 'support';
|
||||||
// Reset bulk selection
|
// Reset bulk selection
|
||||||
bulkSelectedIds.clear();
|
bulkSelectedIds.clear();
|
||||||
const selectAll = document.getElementById('bulk-select-all');
|
const selectAll = document.getElementById('bulk-select-all');
|
||||||
|
|||||||
Reference in New Issue
Block a user