Lisää Zammad-integraatio ja modulaarinen integraatiot-hallinta
- Uusi integrations-taulu tietokantaan (moduulimalli: type, enabled, config) - ZammadClient-luokka: tiketit, artikkelit, vastaukset, ryhmät - API-endpointit: integration_save, integration_test, zammad_sync, zammad_reply, zammad_groups - Synkronointi: Zammad-tiketit → intran tiketit, artikkelit → viestit - Vastaukset: Zammad-tiketteihin vastaus kulkee Zammad API:n kautta (→ O365) - UI: Integraatiot-osio API-välilehdellä, toggle-kytkimet, Zammad-konfiguraatio - tickets.zammad_ticket_id ja ticket_messages.zammad_article_id linkitys Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
204
script.js
204
script.js
@@ -1332,6 +1332,7 @@ document.getElementById('profile-form').addEventListener('submit', async (e) =>
|
||||
|
||||
let tickets = [];
|
||||
let currentTicketId = null;
|
||||
let currentTicketData = null;
|
||||
let ticketReplyType = 'reply';
|
||||
|
||||
const ticketStatusLabels = {
|
||||
@@ -1512,6 +1513,7 @@ async function showTicketDetail(id, companyId = '') {
|
||||
currentTicketCompanyId = companyId;
|
||||
const ticket = await apiCall('ticket_detail&id=' + encodeURIComponent(id) + ticketCompanyParam());
|
||||
currentTicketId = id;
|
||||
currentTicketData = ticket;
|
||||
|
||||
// Header
|
||||
document.getElementById('ticket-detail-header').innerHTML = `
|
||||
@@ -1881,19 +1883,26 @@ document.getElementById('btn-send-reply').addEventListener('click', async () =>
|
||||
btn.textContent = 'Lähetetään...';
|
||||
|
||||
try {
|
||||
const action = ticketReplyType === 'note' ? 'ticket_note' : 'ticket_reply';
|
||||
const payload = { id: currentTicketId, body };
|
||||
if (ticketReplyType !== 'note') {
|
||||
const mbSel = document.getElementById('reply-mailbox-select');
|
||||
const toFld = document.getElementById('reply-to');
|
||||
const ccFld = document.getElementById('reply-cc');
|
||||
const useSig = document.getElementById('reply-use-signature');
|
||||
if (mbSel) payload.mailbox_id = mbSel.value;
|
||||
if (toFld && toFld.value.trim()) payload.to = toFld.value.trim();
|
||||
if (ccFld) payload.cc = ccFld.value.trim();
|
||||
if (useSig && !useSig.checked) payload.no_signature = true;
|
||||
// Tarkista onko Zammad-tiketti
|
||||
const isZammadTicket = currentTicketData?.zammad_ticket_id;
|
||||
if (isZammadTicket && ticketReplyType !== 'note') {
|
||||
// Lähetä Zammad API:n kautta
|
||||
await apiCall('zammad_reply' + ticketCompanyParam(), 'POST', { ticket_id: currentTicketId, body });
|
||||
} else {
|
||||
const action = ticketReplyType === 'note' ? 'ticket_note' : 'ticket_reply';
|
||||
const payload = { id: currentTicketId, body };
|
||||
if (ticketReplyType !== 'note') {
|
||||
const mbSel = document.getElementById('reply-mailbox-select');
|
||||
const toFld = document.getElementById('reply-to');
|
||||
const ccFld = document.getElementById('reply-cc');
|
||||
const useSig = document.getElementById('reply-use-signature');
|
||||
if (mbSel) payload.mailbox_id = mbSel.value;
|
||||
if (toFld && toFld.value.trim()) payload.to = toFld.value.trim();
|
||||
if (ccFld) payload.cc = ccFld.value.trim();
|
||||
if (useSig && !useSig.checked) payload.no_signature = true;
|
||||
}
|
||||
await apiCall(action + ticketCompanyParam(), 'POST', payload);
|
||||
}
|
||||
await apiCall(action + ticketCompanyParam(), 'POST', payload);
|
||||
// Reload the detail view
|
||||
await showTicketDetail(currentTicketId, currentTicketCompanyId);
|
||||
} catch (e) {
|
||||
@@ -2352,6 +2361,10 @@ async function loadSettings() {
|
||||
document.getElementById('settings-telegram-chat').value = config.telegram_chat_id || '';
|
||||
} catch (e) { console.error(e); }
|
||||
|
||||
// Integraatiot
|
||||
loadIntegrations();
|
||||
loadZammadConfig();
|
||||
|
||||
// Vastauspohjat
|
||||
loadTemplates();
|
||||
}
|
||||
@@ -2422,6 +2435,173 @@ document.getElementById('btn-test-api').addEventListener('click', async () => {
|
||||
} catch (e) { result.textContent = 'Virhe: ' + e.message; }
|
||||
});
|
||||
|
||||
// ==================== INTEGRAATIOT ====================
|
||||
|
||||
const INTEGRATION_TYPES = {
|
||||
zammad: { name: 'Zammad', icon: '📧', desc: 'Synkronoi tiketit Zammad-helpdeskistä (O365-sähköpostit)' },
|
||||
};
|
||||
|
||||
async function loadIntegrations() {
|
||||
const container = document.getElementById('integrations-list');
|
||||
if (!container) return;
|
||||
try {
|
||||
const integrations = await apiCall('integrations');
|
||||
renderIntegrations(integrations);
|
||||
} catch (e) {
|
||||
console.error('loadIntegrations:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function renderIntegrations(integrations) {
|
||||
const container = document.getElementById('integrations-list');
|
||||
const enabledMap = {};
|
||||
integrations.forEach(i => { enabledMap[i.type] = i.enabled; });
|
||||
|
||||
let html = '';
|
||||
for (const [type, meta] of Object.entries(INTEGRATION_TYPES)) {
|
||||
const enabled = enabledMap[type] || false;
|
||||
html += `
|
||||
<div class="integration-item" data-type="${type}">
|
||||
<label class="integration-toggle">
|
||||
<input type="checkbox" class="integration-enabled" data-type="${type}" ${enabled ? 'checked' : ''}>
|
||||
<span class="integration-icon">${meta.icon}</span>
|
||||
<span class="integration-info">
|
||||
<strong>${esc(meta.name)}</strong>
|
||||
<small>${esc(meta.desc)}</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>`;
|
||||
}
|
||||
container.innerHTML = html;
|
||||
|
||||
// Toggle-napit
|
||||
container.querySelectorAll('.integration-enabled').forEach(cb => {
|
||||
cb.addEventListener('change', async () => {
|
||||
const type = cb.dataset.type;
|
||||
const configCard = document.getElementById(type + '-config-card');
|
||||
if (configCard) configCard.style.display = cb.checked ? '' : 'none';
|
||||
// Tallenna toggle-tila
|
||||
try {
|
||||
const existing = await apiCall('integrations');
|
||||
const old = existing.find(i => i.type === type);
|
||||
const config = old?.config || {};
|
||||
await apiCall('integration_save', 'POST', { type, enabled: cb.checked, config });
|
||||
} catch (e) { console.error(e); }
|
||||
});
|
||||
});
|
||||
|
||||
// Näytä config-kortit käytössä oleville
|
||||
integrations.forEach(i => {
|
||||
const card = document.getElementById(i.type + '-config-card');
|
||||
if (card) card.style.display = i.enabled ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
async function loadZammadConfig() {
|
||||
try {
|
||||
const integrations = await apiCall('integrations');
|
||||
const zammad = integrations.find(i => i.type === 'zammad');
|
||||
if (zammad && zammad.config) {
|
||||
document.getElementById('zammad-url').value = zammad.config.url || '';
|
||||
document.getElementById('zammad-token').value = zammad.config.token || '';
|
||||
// Renderöi ryhmät jos tallennettu
|
||||
if (zammad.config.group_ids && zammad.config.group_names) {
|
||||
renderZammadGroupCheckboxes(
|
||||
zammad.config.group_names.map((name, i) => ({
|
||||
id: zammad.config.group_ids[i],
|
||||
name: name,
|
||||
})),
|
||||
zammad.config.group_ids
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
}
|
||||
|
||||
function renderZammadGroupCheckboxes(groups, selectedIds = []) {
|
||||
const container = document.getElementById('zammad-groups-list');
|
||||
if (!groups.length) { container.innerHTML = '<span style="color:#888;">Ei ryhmiä.</span>'; return; }
|
||||
container.innerHTML = groups.map(g => `
|
||||
<label style="display:flex;align-items:center;gap:0.4rem;margin-bottom:0.3rem;cursor:pointer;">
|
||||
<input type="checkbox" class="zammad-group-cb" value="${g.id}" data-name="${esc(g.name)}" ${selectedIds.includes(g.id) || selectedIds.includes(String(g.id)) ? 'checked' : ''}>
|
||||
${esc(g.name)}
|
||||
</label>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Zammad — Tallenna
|
||||
document.getElementById('btn-save-zammad')?.addEventListener('click', async () => {
|
||||
const url = document.getElementById('zammad-url').value.trim();
|
||||
const token = document.getElementById('zammad-token').value.trim();
|
||||
if (!url || !token) { alert('URL ja token vaaditaan'); return; }
|
||||
|
||||
const groupCbs = document.querySelectorAll('.zammad-group-cb:checked');
|
||||
const groupIds = Array.from(groupCbs).map(cb => cb.value);
|
||||
const groupNames = Array.from(groupCbs).map(cb => cb.dataset.name);
|
||||
|
||||
try {
|
||||
await apiCall('integration_save', 'POST', {
|
||||
type: 'zammad',
|
||||
enabled: document.querySelector('.integration-enabled[data-type="zammad"]')?.checked || false,
|
||||
config: { url, token, group_ids: groupIds, group_names: groupNames },
|
||||
});
|
||||
alert('Zammad-asetukset tallennettu!');
|
||||
} catch (e) { alert('Virhe: ' + e.message); }
|
||||
});
|
||||
|
||||
// Zammad — Testaa yhteys
|
||||
document.getElementById('btn-test-zammad')?.addEventListener('click', async () => {
|
||||
const result = document.getElementById('zammad-test-result');
|
||||
result.style.display = 'block';
|
||||
result.style.background = '#f8f9fb';
|
||||
result.textContent = 'Testataan yhteyttä...';
|
||||
try {
|
||||
const res = await apiCall('integration_test', 'POST', { type: 'zammad' });
|
||||
result.style.background = '#d4edda';
|
||||
result.textContent = `✅ Yhteys OK! Käyttäjä: ${res.user}, Ryhmiä: ${res.groups}`;
|
||||
} catch (e) {
|
||||
result.style.background = '#f8d7da';
|
||||
result.textContent = '❌ ' + e.message;
|
||||
}
|
||||
});
|
||||
|
||||
// Zammad — Lataa ryhmät
|
||||
document.getElementById('btn-load-zammad-groups')?.addEventListener('click', async () => {
|
||||
try {
|
||||
const groups = await apiCall('zammad_groups');
|
||||
const activeGroups = groups.filter(g => g.active);
|
||||
|
||||
// Hae tallennetut valitut ryhmät
|
||||
const integrations = await apiCall('integrations');
|
||||
const zammad = integrations.find(i => i.type === 'zammad');
|
||||
const selectedIds = zammad?.config?.group_ids || [];
|
||||
|
||||
renderZammadGroupCheckboxes(activeGroups, selectedIds);
|
||||
} catch (e) { alert('Virhe: ' + e.message); }
|
||||
});
|
||||
|
||||
// Zammad — Synkronoi nyt
|
||||
document.getElementById('btn-sync-zammad')?.addEventListener('click', async () => {
|
||||
const result = document.getElementById('zammad-sync-result');
|
||||
result.style.display = 'block';
|
||||
result.style.background = '#f8f9fb';
|
||||
result.innerHTML = '⏳ Synkronoidaan...';
|
||||
try {
|
||||
const res = await apiCall('zammad_sync', 'POST', {});
|
||||
result.style.background = '#d4edda';
|
||||
result.innerHTML = `✅ Synkronointi valmis!<br>
|
||||
Tikettejä löytyi: <strong>${res.tickets_found}</strong><br>
|
||||
Uusia tikettejä: <strong>${res.created}</strong><br>
|
||||
Päivitettyjä: <strong>${res.updated}</strong><br>
|
||||
Uusia viestejä: <strong>${res.messages_added}</strong>`;
|
||||
// Päivitä tikettilista jos ollaan tukivälilehdellä
|
||||
if (typeof loadTickets === 'function') loadTickets();
|
||||
} catch (e) {
|
||||
result.style.background = '#f8d7da';
|
||||
result.innerHTML = '❌ ' + e.message;
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== MODALS ====================
|
||||
|
||||
customerModal.addEventListener('click', (e) => { if (e.target === customerModal) customerModal.style.display = 'none'; });
|
||||
|
||||
Reference in New Issue
Block a user