Dashboard-Reihenfolge, Drei-Punkte-Menü, Mehrrollen-Kürzel, Login-Rechte
- Meine Aufgaben: Projekte in der Reihenfolge der Projektliste, je Konto per Ziehen umsortierbar (users.dashboard_reihenfolge) - Projektliste: Öffnen/Löschen hinter einem "⋯"-Menü - Avatare je Person statt je Rolle; mehrere Rollen als senkrechte Farbstreifen; Titelleiste zieht Rollenänderungen sofort nach - Nach dem Anmelden vollständiges Konto über /whoami laden (Seitenleiste zeigte sonst erst nach Neuladen alle Einträge) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b7e0bd0d92
commit
a2266662ad
11 files changed
+358
-36
No files matched your search
@@ -130,7 +130,9 @@ async function renderDashboard(){
|
||||
html += dashAbschnitt('Meine Projekte', daten.projekte.length, null,
|
||||
renderDashProjekte(daten),
|
||||
daten.projekte.length > 1
|
||||
? '<button type="button" class="dash-proj-alle" id="dashAlleKnopf" onclick="dashAlleUmschalten()">'
|
||||
? '<button type="button" class="dash-proj-alle" id="dashReihenfolgeReset" onclick="dashReihenfolgeZuruecksetzen()"'
|
||||
+ (dashEigeneReihenfolge().length ? '' : ' hidden') + ' title="Reihenfolge wieder wie in der Projektliste">Wie Projektliste</button>'
|
||||
+ '<button type="button" class="dash-proj-alle" id="dashAlleKnopf" onclick="dashAlleUmschalten()">'
|
||||
+ (alleOffen ? 'Alle einklappen' : 'Alle aufklappen') + '</button>'
|
||||
: '');
|
||||
html += '</div>';
|
||||
@@ -214,12 +216,9 @@ function dashDaten(){
|
||||
relevant.sort((a, b) => (a.datum || '9999').localeCompare(b.datum || '9999'));
|
||||
projekte.push({ p, rollen, termine: relevant, alleTermine: termine });
|
||||
});
|
||||
// Projekte mit dem dringendsten Termin zuerst, Projekte ohne Termin am Ende.
|
||||
projekte.sort((a, b) => {
|
||||
const da = (a.termine.find(t => t.datum) || {}).datum || '9999';
|
||||
const db_ = (b.termine.find(t => t.datum) || {}).datum || '9999';
|
||||
return da.localeCompare(db_) || String(a.p.nr || '').localeCompare(String(b.p.nr || ''));
|
||||
});
|
||||
// Reihenfolge wie in der Projektliste (Gruppe, dann Position darin) —
|
||||
// oder die eigene, auf dem Dashboard gezogene (dashSortieren()).
|
||||
dashSortieren(projekte);
|
||||
|
||||
// Kalendereinträge: alle datierten Termine dieser Projekte (auch vergangene
|
||||
// Meilensteine — beim Zurückblättern sollen sie da sein).
|
||||
@@ -273,6 +272,109 @@ function dashZuSchreiben(set){
|
||||
}
|
||||
function dashProjKey(p){ return p.projectUid || p.nr || p.id; }
|
||||
|
||||
// ---------- Reihenfolge der Projekte ----------
|
||||
// Grundlage ist die Reihenfolge der Projektliste (GROUP_ORDER, dann p.order).
|
||||
// Jedes Konto kann sie auf dem Dashboard durch Ziehen am Griff ändern; die
|
||||
// eigene Reihenfolge liegt am Server (users.dashboard_reihenfolge, über
|
||||
// currentUser.dashboardReihenfolge), gilt also auf jedem Gerät. Projekte,
|
||||
// die darin noch fehlen (neu zugeordnet), stehen nach ihrer Stelle in der
|
||||
// Projektliste dahinter.
|
||||
function dashListenRang(p){
|
||||
const g = GROUP_ORDER.indexOf(p.group);
|
||||
return (g === -1 ? 999 : g) * 1e6 + (Number(p.order) || 0);
|
||||
}
|
||||
function dashEigeneReihenfolge(){
|
||||
return (currentUser && Array.isArray(currentUser.dashboardReihenfolge)) ? currentUser.dashboardReihenfolge : [];
|
||||
}
|
||||
function dashSortieren(projekte){
|
||||
const eigen = dashEigeneReihenfolge();
|
||||
const pos = new Map(eigen.map((k, i) => [k, i]));
|
||||
projekte.sort((a, b) => {
|
||||
const ia = pos.has(dashProjKey(a.p)) ? pos.get(dashProjKey(a.p)) : Infinity;
|
||||
const ib = pos.has(dashProjKey(b.p)) ? pos.get(dashProjKey(b.p)) : Infinity;
|
||||
if(ia !== ib) return ia - ib;
|
||||
return dashListenRang(a.p) - dashListenRang(b.p);
|
||||
});
|
||||
}
|
||||
function dashReihenfolgeSpeichern(liste){
|
||||
if(currentUser) currentUser.dashboardReihenfolge = liste;
|
||||
fetch(baseUrl() + '/dashboard-reihenfolge', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json; charset=utf-8' },
|
||||
body: JSON.stringify({ reihenfolge: liste })
|
||||
}).then(r => { if(!r.ok) throw new Error(r.status); })
|
||||
.catch(() => alert('Die Reihenfolge konnte nicht gespeichert werden.'));
|
||||
}
|
||||
// dashReihenfolgeZuruecksetzen: wieder wie in der Projektliste.
|
||||
function dashReihenfolgeZuruecksetzen(){
|
||||
dashReihenfolgeSpeichern([]);
|
||||
renderDashboard();
|
||||
}
|
||||
|
||||
// Ziehen: Der Block wird nur ziehbar, solange der Griff gedrückt ist —
|
||||
// sonst ließe sich Text in den Terminen nicht mehr markieren.
|
||||
let dashGezogen = null;
|
||||
function dashGriffDruck(griff){
|
||||
const block = griff.closest('.dash-proj');
|
||||
if(block) block.draggable = true;
|
||||
}
|
||||
document.addEventListener('mouseup', () => {
|
||||
if(dashGezogen) return;
|
||||
document.querySelectorAll('.dash-proj[draggable="true"]').forEach(b => { b.draggable = false; });
|
||||
});
|
||||
document.addEventListener('dragstart', e => {
|
||||
const block = e.target && e.target.closest && e.target.closest('.dash-proj[draggable="true"]');
|
||||
if(!block) return;
|
||||
dashGezogen = block;
|
||||
block.classList.add('ist-gezogen');
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
try { e.dataTransfer.setData('text/plain', block.dataset.key || ''); } catch(err){ /* egal */ }
|
||||
});
|
||||
document.addEventListener('dragover', e => {
|
||||
if(!dashGezogen) return;
|
||||
const liste = dashGezogen.parentElement;
|
||||
if(!liste || !liste.contains(e.target)) return;
|
||||
e.preventDefault();
|
||||
const andere = [...liste.querySelectorAll('.dash-proj:not(.ist-gezogen)')];
|
||||
const danach = andere.find(b => { const r = b.getBoundingClientRect(); return e.clientY < r.top + r.height / 2; });
|
||||
if(danach) liste.insertBefore(dashGezogen, danach); else liste.appendChild(dashGezogen);
|
||||
});
|
||||
document.addEventListener('drop', e => { if(dashGezogen) e.preventDefault(); });
|
||||
document.addEventListener('dragend', () => {
|
||||
if(!dashGezogen) return;
|
||||
const block = dashGezogen;
|
||||
dashGezogen = null;
|
||||
block.classList.remove('ist-gezogen');
|
||||
block.draggable = false;
|
||||
const neu = [...block.parentElement.querySelectorAll('.dash-proj')].map(b => b.dataset.key);
|
||||
const alt = dashEigeneReihenfolge();
|
||||
// Schon bekannte, gerade nicht angezeigte Projekte (z. B. vorübergehend
|
||||
// ohne Zuordnung) behalten ihren Platz am Ende.
|
||||
const liste = neu.concat(alt.filter(k => !neu.includes(k)));
|
||||
if(JSON.stringify(liste) !== JSON.stringify(alt)) dashReihenfolgeSpeichern(liste);
|
||||
const reset = document.getElementById('dashReihenfolgeReset');
|
||||
if(reset) reset.hidden = false;
|
||||
});
|
||||
|
||||
// dashVerschieben: Tastatur-Alternative zum Ziehen (Griff fokussiert,
|
||||
// Pfeil hoch/runter).
|
||||
function dashGriffTaste(e, griff){
|
||||
if(e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return;
|
||||
e.preventDefault();
|
||||
const block = griff.closest('.dash-proj');
|
||||
const liste = block && block.parentElement;
|
||||
if(!liste) return;
|
||||
if(e.key === 'ArrowUp' && block.previousElementSibling) liste.insertBefore(block, block.previousElementSibling);
|
||||
else if(e.key === 'ArrowDown' && block.nextElementSibling) liste.insertBefore(block.nextElementSibling, block);
|
||||
else return;
|
||||
griff.focus();
|
||||
const neu = [...liste.querySelectorAll('.dash-proj')].map(b => b.dataset.key);
|
||||
const alt = dashEigeneReihenfolge();
|
||||
dashReihenfolgeSpeichern(neu.concat(alt.filter(k => !neu.includes(k))));
|
||||
const reset = document.getElementById('dashReihenfolgeReset');
|
||||
if(reset) reset.hidden = false;
|
||||
}
|
||||
|
||||
// dashProjUmschalten: klappt einen Projektblock auf oder zu — direkt am DOM,
|
||||
// ohne das Dashboard neu zu laden. Parameter: knopf (der Kopf-Button).
|
||||
function dashProjUmschalten(knopf){
|
||||
@@ -316,7 +418,7 @@ function renderDashProjekte(daten){
|
||||
if(!daten.projekte.length) return dashLeer('Sie sind in der aktuellen Sitzung keinem Projekt zugeordnet und haben keine offenen Aufgaben.');
|
||||
const heute = heuteIso();
|
||||
const zu = dashZuLesen();
|
||||
return daten.projekte.map(x => {
|
||||
return '<div class="dash-proj-liste">' + daten.projekte.map(x => {
|
||||
const p = x.p;
|
||||
const key = dashProjKey(p);
|
||||
const istZu = zu.has(key);
|
||||
@@ -347,6 +449,7 @@ function renderDashProjekte(daten){
|
||||
+ '<span class="dash-proj-zahl">' + kommend.length + ' offen</span>';
|
||||
return '<div class="dash-karte dash-proj' + (istZu ? ' ist-zu' : '') + '" data-key="' + esc(key) + '">'
|
||||
+ '<div class="dash-proj-kopf">'
|
||||
+ (daten.projekte.length > 1 ? '<span class="dash-proj-griff" tabindex="0" role="button" title="Ziehen, um die Reihenfolge zu ändern (oder Pfeiltasten)" aria-label="Reihenfolge ändern" onmousedown="dashGriffDruck(this)" ontouchstart="dashGriffDruck(this)" onkeydown="dashGriffTaste(event, this)">' + icon('grip') + '</span>' : '')
|
||||
+ '<button type="button" class="dash-proj-toggle" aria-expanded="' + (!istZu) + '" onclick="dashProjUmschalten(this)" title="Ein-/ausklappen">'
|
||||
+ '<span class="dash-proj-chev" aria-hidden="true">›</span>'
|
||||
+ '<span class="dash-proj-nr">' + esc(p.nr || '') + '</span><span class="dash-proj-titel">' + esc(p.name || '') + '</span>'
|
||||
@@ -360,7 +463,7 @@ function renderDashProjekte(daten){
|
||||
+ '<div class="dash-proj-termine">' + zeilen + mehr + '</div>'
|
||||
+ '</div>'
|
||||
+ '</div>';
|
||||
}).join('');
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
|
||||
// renderDashTermin: eine Terminzeile im Projektblock und in der Tagesliste
|
||||
|
||||
Reference in new issue
Block a user