/workspace/calls.html. Calls sind KEINE eigene Terminart neben dem Kalender, sondern dieselben Termine mit art = 'call' oder 'review'. Ein zweiter Terminspeicher waere die sichere Art, irgendwann zwei widerspruechliche Uhrzeiten zu haben. Diese Seite fuegt nur hinzu, was ein Gespraech vom blossen Termin unterscheidet: das Protokoll danach. Die Sichtbarkeitsregel ist Wort fuer Wort dieselbe wie im Kalender -- ein Termin darf nicht in der einen Ansicht auftauchen und in der anderen fehlen. Der tragende Satz von Seite 13, woertlich genommen: "Jeder Call endet mit klaren To-dos, die direkt ins Board uebernommen werden." To-dos werden deshalb NICHT als Text im Protokoll abgelegt, sondern sofort zu echten Aufgaben -- mit dem Gespraech als Herkunft in der Beschreibung, zugeordnet an das Gegenueber des Calls. Sonst steht die Verabredung in einem Dokument, das niemand mehr oeffnet. Geprueft: Protokoll geschrieben -> Aufgaben stehen im Brett. Weitere Punkte: - Reihenfolge der Gruppen ist Dringlichkeit, nicht Chronologie: Ganz oben stehen vergangene Gespraeche OHNE Protokoll. Das ist der einzige Zustand, der etwas von einem verlangt. Nur diese Gruppe ist optisch hervorgehoben; wenn alles schreit, sieht man nichts mehr. - Ein festgehaltenes Protokoll ist danach nur noch lesbar. Ein Gespraech nachtraeglich umschreiben zu koennen waere genau das, was ein Protokoll wertlos macht. - "Naechster Termin" legt den Folgetermin direkt in den Kalender -- mit demselben Gegenueber und demselben Meeting-Link. - Alles in EINER Transaktion. Geprueft mit einem absichtlich kaputten To-do: kein Protokoll, keine halben Aufgaben, kein Folgetermin. - Enter im To-do-Feld legt die naechste Zeile an, damit man eine Liste tippen kann, ohne zur Maus zu greifen. - Meeting-Link nur bei anstehenden Gespraechen und nur, wenn es wirklich eine http(s)-Adresse ist. "bei mir zu Hause" bleibt schlichter Text. Zur Videotechnik selbst bewusst KEINE Entscheidung getroffen: Ein eigener WebRTC-Raum braucht einen TURN-Server, damit Verbindungen hinter Mobilfunk-NAT zustande kommen. Das ist eine Infrastrukturfrage mit laufenden Kosten und gehoert besprochen, nicht nebenbei entschieden. Bis dahin traegt der Termin einfach den Link des Dienstes, der ohnehin benutzt wird. Nebenbei: .knopf-still lag in scouting.css und fehlte dadurch auf calls.html -- dort standen prompt nackte Systemknoepfe. Der Baustein gehoert ins gemeinsame start.css, dort liegt er jetzt.
129 lines
5.1 KiB
JavaScript
129 lines
5.1 KiB
JavaScript
/* ===================================================================
|
|
Startseite nach dem Anmelden.
|
|
Die Seite ist bereits serverseitig geschützt (workspace.js). Dieses
|
|
Skript holt nur noch, WER angemeldet ist, und regelt das Abmelden.
|
|
=================================================================== */
|
|
(() => {
|
|
'use strict';
|
|
|
|
const ROLLENTEXT = {
|
|
admin: 'Management · Gesamtübersicht und Freigaben',
|
|
creator: 'Creator · dein eigener Bereich',
|
|
scout: 'Scout · deine Pipeline und Kontakte',
|
|
};
|
|
|
|
/* Bereiche aus dem Konzept, mit der Phase aus dem Umsetzungsplan.
|
|
`rollen` steuert, wem ein Bereich überhaupt angezeigt wird - schon
|
|
jetzt, damit die Trennung von Anfang an sichtbar ist. */
|
|
const BEREICHE = [
|
|
{ name: 'Dashboard', phase: 1, rollen: ['admin', 'creator', 'scout'] },
|
|
{ name: 'Personen & Zugänge', phase: 1, rollen: ['admin'], ziel: 'personen.html' },
|
|
{ name: 'Creator-Profile', phase: 1, rollen: ['admin'], ziel: 'profil.html' },
|
|
{ name: 'Mein Profil', phase: 1, rollen: ['creator'], ziel: 'profil.html' },
|
|
{ name: 'Aufgaben', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'aufgaben.html' },
|
|
{ name: 'Kalender', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'kalender.html' },
|
|
{ name: 'Dateien', phase: 1, rollen: ['admin', 'creator', 'scout'], ziel: 'dateien.html' },
|
|
{ name: 'LIVE-Analyse', phase: 2, rollen: ['admin', 'creator'], ziel: 'bereich.html?b=live' },
|
|
{ name: 'Content-Planung', phase: 2, rollen: ['admin', 'creator'], ziel: 'bereich.html?b=content' },
|
|
{ name: 'Technik', phase: 2, rollen: ['admin', 'creator'], ziel: 'bereich.html?b=technik' },
|
|
{ name: 'Community', phase: 2, rollen: ['admin', 'creator'], ziel: 'bereich.html?b=community' },
|
|
{ name: 'Schutz & Regeln', phase: 2, rollen: ['admin', 'creator'], ziel: 'bereich.html?b=schutz' },
|
|
{ name: 'Reports & Review', phase: 2, rollen: ['admin', 'creator'], ziel: 'report.html' },
|
|
{ name: 'Calls & Protokolle', phase: 3, rollen: ['admin', 'creator', 'scout'], ziel: 'calls.html' },
|
|
{ name: 'Scout-Pipeline', phase: 3, rollen: ['admin', 'scout'], ziel: 'scouting.html' },
|
|
{ name: 'Automationen', phase: 4, rollen: ['admin'] },
|
|
];
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
|
|
async function laden() {
|
|
let ich;
|
|
try {
|
|
const antwort = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
|
|
if (!antwort.ok) { location.assign('/workspace/'); return; }
|
|
ich = await antwort.json();
|
|
} catch {
|
|
location.assign('/workspace/');
|
|
return;
|
|
}
|
|
|
|
$('wer').textContent = `${ich.name} · ${ich.rolle}`;
|
|
$('gruss').textContent = `Willkommen, ${ich.name}`;
|
|
$('rollentext').textContent = ROLLENTEXT[ich.rolle] || ich.rolle;
|
|
|
|
const liste = $('bereiche');
|
|
for (const b of BEREICHE.filter((b) => b.rollen.includes(ich.rolle))) {
|
|
const li = document.createElement('li');
|
|
li.className = 'bereich';
|
|
li.dataset.phase = String(b.phase);
|
|
if (b.ziel) li.dataset.fertig = 'ja';
|
|
|
|
const name = document.createElement('span');
|
|
name.className = 'bereich__name';
|
|
name.textContent = b.name;
|
|
|
|
const stand = document.createElement('span');
|
|
stand.className = 'bereich__stand';
|
|
stand.textContent = b.ziel ? 'öffnen' : (b.phase === 1 ? 'als Nächstes' : `Phase ${b.phase}`);
|
|
|
|
if (b.ziel) {
|
|
/* Fertige Bereiche sind echte Verweise -- Tastatur und
|
|
Rechtsklick funktionieren damit wie gewohnt. */
|
|
const a = document.createElement('a');
|
|
a.className = 'bereich__link';
|
|
a.href = b.ziel;
|
|
a.append(name, stand);
|
|
li.append(a);
|
|
} else {
|
|
li.append(name, stand);
|
|
}
|
|
liste.append(li);
|
|
}
|
|
|
|
await zahlenLaden();
|
|
}
|
|
|
|
/* Die Zahlen kommen aus derselben Quelle wie das Brett und sind bereits
|
|
serverseitig auf die eigene Rolle eingegrenzt. */
|
|
const ZAHLEN = [
|
|
{ schluessel: 'ueberfaellig', name: 'Überfällig', warn: true },
|
|
{ schluessel: 'heute', name: 'Heute fällig' },
|
|
{ schluessel: 'offen', name: 'Offen' },
|
|
{ schluessel: 'arbeit', name: 'In Arbeit' },
|
|
{ schluessel: 'review', name: 'Review' },
|
|
{ schluessel: 'erledigt', name: 'Erledigt' },
|
|
];
|
|
|
|
async function zahlenLaden() {
|
|
let daten;
|
|
try {
|
|
const antwort = await fetch('/workspace/api/uebersicht', { credentials: 'same-origin' });
|
|
if (!antwort.ok) return;
|
|
daten = await antwort.json();
|
|
} catch { return; }
|
|
|
|
const liste = $('zahlen');
|
|
liste.textContent = '';
|
|
for (const z of ZAHLEN) {
|
|
const wert = daten[z.schluessel] ?? 0;
|
|
const li = document.createElement('li');
|
|
li.className = 'zahl';
|
|
if (z.warn && wert > 0) li.dataset.warn = 'ja';
|
|
|
|
const n = document.createElement('span');
|
|
n.className = 'zahl__wert';
|
|
n.textContent = String(wert);
|
|
|
|
const t = document.createElement('span');
|
|
t.className = 'zahl__name';
|
|
t.textContent = z.name;
|
|
|
|
li.append(n, t);
|
|
liste.append(li);
|
|
}
|
|
}
|
|
|
|
|
|
laden();
|
|
})();
|