247 lines
11 KiB
HTML
247 lines
11 KiB
HTML
<!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 & 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>
|