Teams-Einstellungen im Admin-Bereich überarbeitet
- Breites, mehrzeiliges Adressfeld mit Prüfung und Hinweis - Statuszeile, Schalter für die Benachrichtigungen, „Adresse ändern“ Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e7243f6f26
commit
51dfbe8cd8
4 files changed
+121
-13
No files matched your search
@@ -4247,6 +4247,13 @@ mit Link direkt auf die Aufgabe. Kein Kanal.
|
|||||||
Projekt, Fällig/Zeitraum, Aufwand, Zugewiesen von, bei mehreren auch
|
Projekt, Fällig/Zeitraum, Aufwand, Zugewiesen von, bei mehreren auch
|
||||||
Zuständig, Knopf „Aufgabe öffnen“). Ohne Konto/E-Mail keine Nachricht
|
Zuständig, Knopf „Aufgabe öffnen“). Ohne Konto/E-Mail keine Nachricht
|
||||||
(nur ein Log-Eintrag). Testnachricht geht an die E-Mail des Admins.
|
(nur ein Log-Eintrag). Testnachricht geht an die E-Mail des Admins.
|
||||||
|
- **Admin-Karte** (`loadAdminTeams()`): Statuszeile (eingerichtet + Host
|
||||||
|
bzw. „noch nicht eingerichtet“), breites mehrzeiliges Adressfeld
|
||||||
|
(`teamsFeldPruefen()`: Speichern erst bei https ohne Leerzeichen; bei
|
||||||
|
hinterlegter Adresse erst nach „Adresse ändern“ sichtbar), Schalter für
|
||||||
|
die Benachrichtigungen (gesperrt ohne Adresse), „Testnachricht an mich
|
||||||
|
senden“, „Anbindung entfernen“. Stile: Block „Admin → Teams“ am Ende von
|
||||||
|
`styles.css`.
|
||||||
- **Speicher:** Tabelle `integrationen` (`teams_webhook`,
|
- **Speicher:** Tabelle `integrationen` (`teams_webhook`,
|
||||||
`teams_neue_aufgaben`), bewusst NICHT `app_settings` (das darf jedes
|
`teams_neue_aufgaben`), bewusst NICHT `app_settings` (das darf jedes
|
||||||
Konto lesen) und nicht in `IMPORT_TABLES`; die Adresse geht nie an den
|
Konto lesen) und nicht in `IMPORT_TABLES`; die Adresse geht nie an den
|
||||||
|
|||||||
+2
-2
@@ -38,7 +38,7 @@
|
|||||||
<link rel="icon" type="image/png" href="/logo.png">
|
<link rel="icon" type="image/png" href="/logo.png">
|
||||||
<link rel="alternate icon" href="/logo.ico">
|
<link rel="alternate icon" href="/logo.ico">
|
||||||
<link rel="apple-touch-icon" href="/logo.png">
|
<link rel="apple-touch-icon" href="/logo.png">
|
||||||
<link rel="stylesheet" href="/styles.css?v=110">
|
<link rel="stylesheet" href="/styles.css?v=111">
|
||||||
<link rel="stylesheet" href="/plan-styles.css?v=48">
|
<link rel="stylesheet" href="/plan-styles.css?v=48">
|
||||||
<!-- Glas-Darstellung (public/glas.css, als letztes Stylesheet): greift nur mit
|
<!-- Glas-Darstellung (public/glas.css, als letztes Stylesheet): greift nur mit
|
||||||
der Klasse html.glas. Gesetzt wird sie hier, vor dem ersten Zeichnen,
|
der Klasse html.glas. Gesetzt wird sie hier, vor dem ersten Zeichnen,
|
||||||
@@ -2017,7 +2017,7 @@
|
|||||||
Tab-Ordner, keine neue Abhängigkeit nach vorn. -->
|
Tab-Ordner, keine neue Abhängigkeit nach vorn. -->
|
||||||
<script src="/js/wand/wand.js?v=21"></script>
|
<script src="/js/wand/wand.js?v=21"></script>
|
||||||
<script src="/js/settings/settings.js?v=14"></script>
|
<script src="/js/settings/settings.js?v=14"></script>
|
||||||
<script src="/js/admin/admin.js?v=22"></script>
|
<script src="/js/admin/admin.js?v=23"></script>
|
||||||
<script src="/js/admin-users.js?v=10"></script>
|
<script src="/js/admin-users.js?v=10"></script>
|
||||||
<script src="/js/admin/rollen.js?v=1"></script>
|
<script src="/js/admin/rollen.js?v=1"></script>
|
||||||
<script src="/js/einfuehrung.js?v=2"></script>
|
<script src="/js/einfuehrung.js?v=2"></script>
|
||||||
|
|||||||
+55
-11
@@ -1113,22 +1113,66 @@ async function loadAdminTeams(){
|
|||||||
try { d = await fetch(baseUrl() + '/admin/teams').then(r => r.json()); }
|
try { d = await fetch(baseUrl() + '/admin/teams').then(r => r.json()); }
|
||||||
catch(e){ el.innerHTML = '<span class="settings-warn">Konnte nicht geladen werden.</span>'; return; }
|
catch(e){ el.innerHTML = '<span class="settings-warn">Konnte nicht geladen werden.</span>'; return; }
|
||||||
if(!d || !d.ok){ el.innerHTML = '<span class="settings-warn">Keine Berechtigung.</span>'; return; }
|
if(!d || !d.ok){ el.innerHTML = '<span class="settings-warn">Keine Berechtigung.</span>'; return; }
|
||||||
|
// Ist schon eine Adresse hinterlegt, bleibt das Eingabefeld zu, bis man
|
||||||
|
// „Ändern“ wählt — die Adresse selbst kommt ohnehin nie zurück.
|
||||||
|
const feldOffen = !d.konfiguriert;
|
||||||
el.innerHTML = `
|
el.innerHTML = `
|
||||||
<div class="settings-kv-row"><span class="settings-kv-key">Status</span>
|
<div class="teams-status${d.konfiguriert ? ' ist-an' : ''}">
|
||||||
<span>${d.konfiguriert ? '<span class="sitzung-chip ist-geoeffnet">Eingerichtet</span> <span class="hint" style="margin:0">' + esc(d.host) + '</span>'
|
<span class="teams-punkt" aria-hidden="true"></span>
|
||||||
: '<span class="sitzung-chip ist-gesperrt">Nicht eingerichtet</span>'}</span></div>
|
<span class="teams-status-text">${d.konfiguriert
|
||||||
<label class="settings-field"><span>${d.konfiguriert ? 'Neue Adresse (ersetzt die bisherige)' : 'Adresse aus dem Teams-Workflow'}</span>
|
? '<b>Eingerichtet</b><span>Workflow bei ' + esc(d.host) + '</span>'
|
||||||
<input type="password" id="teamsWebhook" placeholder="https://…" autocomplete="off"></label>
|
: '<b>Noch nicht eingerichtet</b><span>Adresse aus dem Teams-Workflow unten einfügen</span>'}</span>
|
||||||
<label class="settings-glas-schalter"><input type="checkbox" id="teamsNeueAufgaben" ${d.neueAufgaben ? 'checked' : ''}
|
${d.konfiguriert ? '<button type="button" class="teams-text-knopf" onclick="teamsFeldZeigen()">Adresse ändern</button>' : ''}
|
||||||
onchange="teamsSpeichern({ neueAufgaben: this.checked })"> Zuständige über neue Aufgaben benachrichtigen</label>
|
</div>
|
||||||
<div class="kalender-aktionen" style="margin-top:10px">
|
|
||||||
<button type="button" onclick="teamsSpeichern({ webhook: document.getElementById('teamsWebhook').value })">Adresse speichern</button>
|
<div class="teams-feld" id="teamsFeld"${feldOffen ? '' : ' hidden'}>
|
||||||
<button type="button" class="secondary" id="teamsTestKnopf" onclick="teamsTesten()" ${d.konfiguriert ? '' : 'disabled'}>Testnachricht senden</button>
|
<label for="teamsWebhook">${d.konfiguriert ? 'Neue Workflow-Adresse' : 'Workflow-Adresse'}</label>
|
||||||
${d.konfiguriert ? '<button type="button" class="secondary" onclick="teamsEntfernen()">Entfernen</button>' : ''}
|
<textarea id="teamsWebhook" rows="3" spellcheck="false" autocomplete="off"
|
||||||
|
placeholder="https://… (beim Auslöser des Teams-Workflows kopieren und hier einfügen)"
|
||||||
|
oninput="teamsFeldPruefen()"></textarea>
|
||||||
|
<div class="teams-feld-fuss">
|
||||||
|
<span class="teams-feld-hinweis" id="teamsFeldHinweis">Bleibt geheim: Nach dem Speichern wird die Adresse nicht mehr angezeigt.</span>
|
||||||
|
${d.konfiguriert ? '<button type="button" class="secondary small" onclick="loadAdminTeams()">Abbrechen</button>' : ''}
|
||||||
|
<button type="button" class="small" id="teamsSpeichernKnopf" disabled onclick="teamsSpeichern({ webhook: document.getElementById('teamsWebhook').value })">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="teams-schalter">
|
||||||
|
<input type="checkbox" id="teamsNeueAufgaben" ${d.neueAufgaben ? 'checked' : ''} ${d.konfiguriert ? '' : 'disabled'}
|
||||||
|
onchange="teamsSpeichern({ neueAufgaben: this.checked })">
|
||||||
|
<span class="teams-schalter-bahn" aria-hidden="true"></span>
|
||||||
|
<span class="teams-schalter-text"><b>Zuständige über neue Aufgaben benachrichtigen</b>
|
||||||
|
<span>Persönliche Nachricht mit Link zur Aufgabe</span></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="teams-aktionen">
|
||||||
|
<button type="button" class="secondary small" id="teamsTestKnopf" onclick="teamsTesten()" ${d.konfiguriert ? '' : 'disabled'}>Testnachricht an mich senden</button>
|
||||||
|
${d.konfiguriert ? '<button type="button" class="teams-text-knopf ist-gefahr" onclick="teamsEntfernen()">Anbindung entfernen</button>' : ''}
|
||||||
</div>
|
</div>
|
||||||
<p class="field-error" id="teamsFehler" hidden></p>`;
|
<p class="field-error" id="teamsFehler" hidden></p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// teamsFeldZeigen: blendet das Eingabefeld für eine neue Adresse ein.
|
||||||
|
function teamsFeldZeigen(){
|
||||||
|
const f = document.getElementById('teamsFeld');
|
||||||
|
if(f){ f.hidden = false; document.getElementById('teamsWebhook').focus(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// teamsFeldPruefen: Speichern erst bei einer https-Adresse; Hinweis passt sich an.
|
||||||
|
function teamsFeldPruefen(){
|
||||||
|
const wert = (document.getElementById('teamsWebhook').value || '').trim();
|
||||||
|
const ok = /^https:\/\/\S+$/.test(wert);
|
||||||
|
const knopf = document.getElementById('teamsSpeichernKnopf');
|
||||||
|
const hinweis = document.getElementById('teamsFeldHinweis');
|
||||||
|
if(knopf) knopf.disabled = !ok;
|
||||||
|
if(hinweis){
|
||||||
|
hinweis.classList.toggle('ist-fehler', !!wert && !ok);
|
||||||
|
hinweis.textContent = !wert ? 'Bleibt geheim: Nach dem Speichern wird die Adresse nicht mehr angezeigt.'
|
||||||
|
: ok ? 'Sieht gut aus — jetzt speichern und danach eine Testnachricht senden.'
|
||||||
|
: 'Die Adresse muss mit https:// beginnen und darf keine Leerzeichen enthalten.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function teamsSpeichern(patch){
|
async function teamsSpeichern(patch){
|
||||||
if('webhook' in patch && !String(patch.webhook || '').trim()){ alert('Bitte die Adresse aus dem Teams-Workflow einfügen.'); return; }
|
if('webhook' in patch && !String(patch.webhook || '').trim()){ alert('Bitte die Adresse aus dem Teams-Workflow einfügen.'); return; }
|
||||||
const r = await fetch(baseUrl() + '/admin/teams', {
|
const r = await fetch(baseUrl() + '/admin/teams', {
|
||||||
|
|||||||
@@ -6232,3 +6232,60 @@ button.verlauf-weitere { white-space: nowrap; }
|
|||||||
.kalender-anleitung summary { cursor: pointer; color: var(--accent); }
|
.kalender-anleitung summary { cursor: pointer; color: var(--accent); }
|
||||||
.kalender-anleitung ol { margin: 8px 0; padding-left: 20px; display: grid; gap: 6px; }
|
.kalender-anleitung ol { margin: 8px 0; padding-left: 20px; display: grid; gap: 6px; }
|
||||||
.kalender-aktionen { display: flex; gap: 8px; }
|
.kalender-aktionen { display: flex; gap: 8px; }
|
||||||
|
|
||||||
|
/* ---------- Admin → Teams (loadAdminTeams(), js/admin/admin.js) ----------
|
||||||
|
Eigene Klassen statt .settings-field: das Adressfeld ist lang (≈ 300
|
||||||
|
Zeichen) und braucht eine breite, mehrzeilige Fläche. */
|
||||||
|
#adminTeamsInhalt { display: grid; gap: 14px; margin: 12px 0 6px; }
|
||||||
|
.teams-status {
|
||||||
|
display: flex; align-items: center; gap: 12px; padding: 12px 14px;
|
||||||
|
border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface-sunk);
|
||||||
|
}
|
||||||
|
.teams-punkt { width: 10px; height: 10px; border-radius: 50%; background: var(--text-subtle); flex: none; }
|
||||||
|
.teams-status.ist-an .teams-punkt { background: var(--green); box-shadow: 0 0 0 4px color-mix(in srgb, var(--green) 20%, transparent); }
|
||||||
|
.teams-status-text { display: grid; gap: 2px; flex: 1; min-width: 0; }
|
||||||
|
.teams-status-text b { font-size: 13.5px; }
|
||||||
|
.teams-status-text span { font-size: 12px; color: var(--text-muted); overflow-wrap: anywhere; }
|
||||||
|
button.teams-text-knopf, button.teams-text-knopf:hover, button.teams-text-knopf:active {
|
||||||
|
all: unset; cursor: pointer; font-size: 12.5px; color: var(--accent); white-space: nowrap; padding: 4px 2px;
|
||||||
|
}
|
||||||
|
button.teams-text-knopf:hover { text-decoration: underline; }
|
||||||
|
button.teams-text-knopf:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
|
||||||
|
button.teams-text-knopf.ist-gefahr { color: var(--red); }
|
||||||
|
|
||||||
|
.teams-feld[hidden] { display: none; }
|
||||||
|
.teams-feld { display: grid; gap: 6px; }
|
||||||
|
.teams-feld label { font-size: 12px; font-weight: 600; color: var(--text-muted); }
|
||||||
|
.teams-feld textarea {
|
||||||
|
width: 100%; box-sizing: border-box; resize: vertical; min-height: 64px;
|
||||||
|
padding: 10px 12px; border: 1px solid var(--line) !important; border-radius: var(--r-md) !important;
|
||||||
|
background: var(--surface) !important; color: var(--text); box-shadow: none !important;
|
||||||
|
font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; word-break: break-all;
|
||||||
|
}
|
||||||
|
.teams-feld textarea:focus { outline: none; border-color: var(--accent) !important; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent) !important; }
|
||||||
|
.teams-feld-fuss { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||||
|
.teams-feld-hinweis { flex: 1; min-width: 200px; font-size: 12px; color: var(--text-subtle); }
|
||||||
|
.teams-feld-hinweis.ist-fehler { color: var(--red); }
|
||||||
|
|
||||||
|
/* Schalter statt nackter Checkbox */
|
||||||
|
.teams-schalter { display: flex; align-items: center; gap: 12px; cursor: pointer; }
|
||||||
|
.teams-schalter input { position: absolute; opacity: 0; width: 1px; height: 1px; }
|
||||||
|
.teams-schalter-bahn {
|
||||||
|
flex: none; width: 38px; height: 22px; border-radius: 11px; background: var(--line); position: relative;
|
||||||
|
transition: background var(--dur, 150ms);
|
||||||
|
}
|
||||||
|
.teams-schalter-bahn::after {
|
||||||
|
content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
|
||||||
|
background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.3); transition: transform var(--dur, 150ms) var(--ease-out, ease);
|
||||||
|
}
|
||||||
|
.teams-schalter input:checked + .teams-schalter-bahn { background: var(--accent); }
|
||||||
|
.teams-schalter input:checked + .teams-schalter-bahn::after { transform: translateX(16px); }
|
||||||
|
.teams-schalter input:focus-visible + .teams-schalter-bahn { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||||
|
.teams-schalter input:disabled + .teams-schalter-bahn { opacity: .45; }
|
||||||
|
.teams-schalter:has(input:disabled) { cursor: default; }
|
||||||
|
.teams-schalter-text { display: grid; gap: 2px; }
|
||||||
|
.teams-schalter-text b { font-size: 13px; font-weight: 600; }
|
||||||
|
.teams-schalter-text span { font-size: 12px; color: var(--text-muted); }
|
||||||
|
.teams-schalter:has(input:disabled) .teams-schalter-text { opacity: .55; }
|
||||||
|
|
||||||
|
.teams-aktionen { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
|
||||||
Reference in new issue
Block a user