Files
IT-Nexus/frontend/public/addin/taskpane.html

247 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>IT Nexus Ticket erstellen</title>
<script src="https://appsforoffice.microsoft.com/lib/1.1/hosted/office.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Segoe UI', sans-serif; font-size: 13px; background: #1a1a2e; color: #e0e0e0; min-height: 100vh; }
.container { padding: 16px; }
.logo { display: flex; align-items: center; gap: 8px; margin-bottom: 16px; font-size: 15px; font-weight: 600; color: #7c9ef8; }
label { display: block; font-size: 11px; color: #9aa3b2; margin-bottom: 4px; margin-top: 10px; text-transform: uppercase; letter-spacing: 0.5px; }
input, textarea, select { width: 100%; padding: 8px 10px; background: #252540; border: 1px solid #3a3a5c; border-radius: 6px; color: #e0e0e0; font-size: 13px; font-family: inherit; outline: none; transition: border-color 0.2s; }
input:focus, textarea:focus, select:focus { border-color: #7c9ef8; }
textarea { resize: vertical; min-height: 80px; }
select option { background: #252540; }
.btn { display: block; width: 100%; padding: 10px; margin-top: 14px; background: #7c9ef8; color: #fff; border: none; border-radius: 6px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s; }
.btn:hover { background: #5b7ef5; }
.btn:disabled { background: #3a3a5c; cursor: not-allowed; }
.btn-secondary { background: transparent; border: 1px solid #3a3a5c; color: #9aa3b2; margin-top: 8px; }
.btn-secondary:hover { border-color: #7c9ef8; color: #7c9ef8; }
.btn-ms { background: #2f6baf; display: flex; align-items: center; justify-content: center; gap: 8px; }
.btn-ms:hover { background: #1e5a9e; }
.alert { padding: 10px 12px; border-radius: 6px; margin-top: 12px; font-size: 12px; }
.alert-success { background: #1a3a2a; border: 1px solid #2a6a4a; color: #5edb9a; }
.alert-error { background: #3a1a1a; border: 1px solid #6a2a2a; color: #f08080; }
.alert-info { background: #1a2a3a; border: 1px solid #2a4a6a; color: #7cb8f8; }
#login-section, #form-section, #success-section { display: none; }
.loading { text-align: center; padding: 30px; color: #9aa3b2; }
.ticket-num { font-size: 20px; font-weight: 700; color: #7c9ef8; text-align: center; margin: 10px 0 4px; }
.ticket-sub { text-align: center; font-size: 12px; color: #9aa3b2; }
</style>
</head>
<body>
<div class="container">
<div class="logo">🎫 IT Nexus</div>
<!-- Login -->
<div id="login-section">
<div class="alert alert-info" style="margin-bottom:12px;">Mit Microsoft-Konto anmelden</div>
<button class="btn btn-ms" id="btn-ms-login">
<svg width="16" height="16" viewBox="0 0 21 21" fill="none"><rect x="1" y="1" width="9" height="9" fill="#f25022"/><rect x="11" y="1" width="9" height="9" fill="#7fba00"/><rect x="1" y="11" width="9" height="9" fill="#00a4ef"/><rect x="11" y="11" width="9" height="9" fill="#ffb900"/></svg>
Mit Microsoft anmelden
</button>
<div id="login-error"></div>
</div>
<!-- Ticket-Formular -->
<div id="form-section">
<div id="user-info" style="font-size:11px;color:#9aa3b2;margin-bottom:8px;"></div>
<label>Titel</label>
<input type="text" id="f-title" maxlength="200" />
<label>Beschreibung</label>
<textarea id="f-desc" rows="5"></textarea>
<label>Kategorie</label>
<select id="f-category">
<option value="Allgemein">Allgemein</option>
<option value="Hardware">Hardware</option>
<option value="Software">Software</option>
<option value="Netzwerk">Netzwerk</option>
<option value="Zugang & Berechtigungen">Zugang &amp; Berechtigungen</option>
<option value="E-Mail">E-Mail</option>
<option value="Drucker">Drucker</option>
<option value="Sonstiges">Sonstiges</option>
</select>
<label>Priorität</label>
<select id="f-priority">
<option value="niedrig">Niedrig</option>
<option value="mittel" selected>Mittel</option>
<option value="hoch">Hoch</option>
<option value="kritisch">Kritisch</option>
</select>
<button class="btn" id="btn-submit">Ticket erstellen</button>
<button class="btn btn-secondary" id="btn-logout">Abmelden</button>
<div id="form-error"></div>
</div>
<!-- Erfolg -->
<div id="success-section">
<div class="alert alert-success" style="text-align:center;padding:20px;">
<div style="font-size:28px;margin-bottom:8px;"></div>
<div style="font-weight:600;margin-bottom:4px;">Ticket erstellt!</div>
<div class="ticket-num" id="s-number"></div>
<div class="ticket-sub" id="s-title"></div>
</div>
<button class="btn btn-secondary" id="btn-new">Weiteres Ticket erstellen</button>
</div>
<div id="loading-section" class="loading">⏳ Wird geladen…</div>
</div>
<script>
let token = localStorage.getItem('it_nexus_addin_token') || null;
let currentUser = JSON.parse(localStorage.getItem('it_nexus_addin_user') || 'null');
function show(id) {
['login-section','form-section','success-section','loading-section'].forEach(s => {
document.getElementById(s).style.display = s === id ? 'block' : 'none';
});
}
function setError(elId, msg) {
document.getElementById(elId).innerHTML = msg ? `<div class="alert alert-error">${msg}</div>` : '';
}
// ── Microsoft SSO Login (Polling-basiert, kein messageParent) ─────────────
document.getElementById('btn-ms-login').addEventListener('click', () => {
show('loading-section');
const sessionId = Date.now().toString(36) + Math.random().toString(36).slice(2);
const dialogUrl = 'https://it-nexus.cereda-systems.de/api/auth/microsoft/addin?sessionId=' + sessionId;
Office.context.ui.displayDialogAsync(
dialogUrl,
{ height: 60, width: 30, promptBeforeOpen: false },
(result) => {
if (result.status === Office.AsyncResultStatus.Failed) {
show('login-section');
setError('login-error', 'Popup konnte nicht geöffnet werden.');
return;
}
const dialog = result.value;
let pollInterval = null;
const handleResult = (data) => {
if (pollInterval) clearInterval(pollInterval);
try { dialog.close(); } catch(e) {}
if (data.error) {
show('login-section');
const msgs = {
user_not_found: 'Kein IT Nexus Konto gefunden.',
account_inactive: 'Konto ist deaktiviert.',
};
setError('login-error', msgs[data.error] || 'Fehler: ' + data.error);
return;
}
token = data.token;
currentUser = data.user;
localStorage.setItem('it_nexus_addin_token', token);
localStorage.setItem('it_nexus_addin_user', JSON.stringify(currentUser));
loadEmailAndShowForm();
};
// Poll server every 2 seconds for login result
pollInterval = setInterval(async () => {
try {
const r = await fetch('https://it-nexus.cereda-systems.de/api/auth/microsoft/addin-result/' + sessionId);
const data = await r.json();
if (!data.pending) handleResult(data);
} catch(e) {}
}, 2000);
// Also handle if user manually closes dialog
dialog.addEventHandler(Office.EventType.DialogEventReceived, () => {
if (pollInterval) clearInterval(pollInterval);
show('login-section');
});
// Keep messageParent as fallback
dialog.addEventHandler(Office.EventType.DialogMessageReceived, (msg) => {
try { handleResult(JSON.parse(msg.message)); } catch(e) {}
});
}
);
});
// ── Logout ─────────────────────────────────────────────────────────────────
document.getElementById('btn-logout').addEventListener('click', () => {
token = null; currentUser = null;
localStorage.removeItem('it_nexus_addin_token');
localStorage.removeItem('it_nexus_addin_user');
show('login-section');
});
// ── Ticket erstellen ───────────────────────────────────────────────────────
document.getElementById('btn-submit').addEventListener('click', async () => {
const title = document.getElementById('f-title').value.trim();
if (!title) return setError('form-error', 'Titel ist erforderlich.');
document.getElementById('btn-submit').disabled = true;
setError('form-error', '');
try {
const res = await fetch('/api/tickets', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify({
title,
description: document.getElementById('f-desc').value.trim(),
category: document.getElementById('f-category').value,
priority: document.getElementById('f-priority').value,
source: 'email',
}),
});
const data = await res.json();
if (!res.ok) {
if (res.status === 401) { token = null; localStorage.removeItem('it_nexus_addin_token'); show('login-section'); return; }
throw new Error(data.message || 'Fehler beim Erstellen');
}
document.getElementById('s-number').textContent = data.data.ticket_number;
document.getElementById('s-title').textContent = title;
show('success-section');
} catch (e) {
setError('form-error', e.message);
} finally {
document.getElementById('btn-submit').disabled = false;
}
});
document.getElementById('btn-new').addEventListener('click', () => loadEmailAndShowForm());
// ── E-Mail laden ───────────────────────────────────────────────────────────
function loadEmailAndShowForm() {
show('loading-section');
if (currentUser) {
document.getElementById('user-info').textContent =
`Angemeldet als: ${currentUser.first_name || ''} ${currentUser.last_name || currentUser.username}`.trim();
}
if (typeof Office !== 'undefined' && Office.context?.mailbox) {
const item = Office.context.mailbox.item;
document.getElementById('f-title').value = item.subject || '';
item.body.getAsync(Office.CoercionType.Text, {}, (r) => {
document.getElementById('f-desc').value =
r.status === Office.AsyncResultStatus.Succeeded ? r.value.substring(0, 2000).trim() : '';
show('form-section');
});
} else {
document.getElementById('f-title').value = '';
document.getElementById('f-desc').value = '';
show('form-section');
}
}
// ── Init ───────────────────────────────────────────────────────────────────
Office.onReady(async () => {
if (token) {
try {
const res = await fetch('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } });
if (res.ok) { loadEmailAndShowForm(); return; }
} catch {}
token = null;
localStorage.removeItem('it_nexus_addin_token');
}
show('login-section');
});
</script>
</body>
</html>