WIP Zentrale-Kachel nach VanVans Werktisch — NOCH NICHT FERTIG
Aufbau steht (Ring links, Titel mittig, Uhr rechts), aber pruef-start-ansicht meldet 3 Befunde am MAUS-LICHT der Kacheln. Gemessen: alter Stand 0 Befunde, dieser Stand 3 — kommt also von hier. Kein JS-Fehler (pageerror/console sind still), also Zeitverhalten: Die zusaetzliche Abfrage /api/zentrale verzoegert vermutlich den Aufbau der Kacheln ueber den Zeitpunkt hinaus, an dem kopf.js lichtFolgen ruft. Ausserdem offen: 2 Schriftgroessen unter 11,5 px (pruef-css-klassen). NICHT ausliefern.
This commit is contained in:
@@ -505,8 +505,37 @@
|
||||
$('rollentext').textContent = ich.sicht
|
||||
? `Arbeitsplatz von ${ich.sicht.name} · ${ROLLENTEXT[zeigt.rolle] || zeigt.rolle}`
|
||||
: (ROLLENTEXT[ich.rolle] || ich.rolle);
|
||||
|
||||
/* Datum und Rolle in der Anrede -- wie im Vorbild, wo unter "Noch
|
||||
wach, Dogi" das Datum steht und daneben "ADMIN & PARTNER".
|
||||
|
||||
Das Datum steht damit ZWEIMAL auf der Kachel: oben in der
|
||||
Zierzeile mit KW, hier ohne. Das ist Absicht und keine Doppelung:
|
||||
Oben gehoert es zur Tafel ("welcher Tag ist heute"), hier zur
|
||||
Anrede ("wann begruesse ich dich"). Im Vorbild ist es genauso,
|
||||
und es liest sich dort natuerlich. Faellt es Filipe als Doppelung
|
||||
auf, kommt die Zierzeile oben weg -- nicht diese Zeile. */
|
||||
const jetzt2 = new Date();
|
||||
const dat = $('zgruss-datum');
|
||||
if (dat) {
|
||||
dat.textContent = `${WOCHENTAGE[jetzt2.getDay()]}, `
|
||||
+ `${jetzt2.getDate()}. ${MONATE[jetzt2.getMonth()]}`;
|
||||
}
|
||||
const rolleEl = $('zgruss-rolle');
|
||||
if (rolleEl) {
|
||||
rolleEl.textContent = (ROLLEN_KURZ[zeigt.rolle] || zeigt.rolle || '').toUpperCase();
|
||||
rolleEl.hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Kurzform der Rolle fuer die Plakette neben dem Namen. Der lange
|
||||
Text ("Gesamtuebersicht und Freigaben") steht schon darunter --
|
||||
hier braucht es das Wort, nicht die Erklaerung. */
|
||||
const ROLLEN_KURZ = {
|
||||
admin: 'DogFather', manager: 'Manager', scout: 'Scout',
|
||||
creator: 'Creator', spicy: 'Spicy Media',
|
||||
};
|
||||
|
||||
/* Ein Satz zur Lage -- gerechnet aus denselben Hinweisen, die darunter
|
||||
stehen. Keine zweite Zählung, sonst gäbe es irgendwann zwei
|
||||
Wahrheiten auf einem Bildschirm. */
|
||||
@@ -534,6 +563,145 @@
|
||||
standSetzen('offen', gesamt === 0 ? '0' : String(gesamt), stufe);
|
||||
}
|
||||
|
||||
/* ---------- Der Ring links ----------------------------------------------
|
||||
Nach dem Vorbild von VanVans Werktisch. Zwei Bahnen:
|
||||
|
||||
INNEN eine feine Bahn mit EINEM Segment je Person (bzw. je
|
||||
Stunde), eingefaerbt nach Lage -- dort sieht man jedes
|
||||
Einzelne.
|
||||
AUSSEN ein durchgehender Bogen, der GENAU auf der Prozentzahl in
|
||||
der Mitte steht.
|
||||
|
||||
Die Trennung ist aus dem Vorbild uebernommen, samt Begruendung:
|
||||
Dort zeigte der Ring erst die 162 Produkte je einzeln, und der
|
||||
Anteil des Blauen entsprach dann der Zahl der fertigen AKTEN (44 %),
|
||||
waehrend daneben die Zahl der erfuellten ANGABEN (91 %) stand. Zwei
|
||||
Auskuenfte, die sich widersprechen, sind schlimmer als eine.
|
||||
Deshalb: der Bogen sagt dasselbe wie die Zahl, immer. */
|
||||
|
||||
const RING_FARBE = {
|
||||
frei: '#57b89b', /* nichts offen -- gedaempftes Petrol, kein Grasgruen */
|
||||
dran: '#7fc4f2', /* heute versorgt */
|
||||
offen: '#ff6b74', /* liegt liegen */
|
||||
};
|
||||
|
||||
function bogen(cx, cy, r, von, bis) {
|
||||
const x1 = cx + r * Math.cos(von), y1 = cy + r * Math.sin(von);
|
||||
const x2 = cx + r * Math.cos(bis), y2 = cy + r * Math.sin(bis);
|
||||
const gross = bis - von > Math.PI ? 1 : 0;
|
||||
return `M ${x1.toFixed(2)} ${y1.toFixed(2)} A ${r} ${r} 0 ${gross} 1 ${x2.toFixed(2)} ${y2.toFixed(2)}`;
|
||||
}
|
||||
const svgEl = (name, attr) => {
|
||||
const n = document.createElementNS('http://www.w3.org/2000/svg', name);
|
||||
for (const [k, v] of Object.entries(attr)) n.setAttribute(k, v);
|
||||
return n;
|
||||
};
|
||||
|
||||
function ringZeichnen(daten) {
|
||||
const svg = $('zring-bild');
|
||||
if (!svg) return;
|
||||
svg.replaceChildren();
|
||||
const segmente = daten.segmente || [];
|
||||
|
||||
/* Der Verlauf fuer den Fortschrittsbogen -- eine Reise von Rot nach
|
||||
Babyblau, dieselben zwei Farben wie in der Uhr daneben und im
|
||||
Rahmen der Kachel. Keine dritte Handschrift. */
|
||||
const defs = svgEl('defs', {});
|
||||
const lauf = svgEl('linearGradient', { id: 'zring-lauf', x1: '0', y1: '0', x2: '1', y2: '1' });
|
||||
for (const [o, f] of [['0%', '#ff5f6d'], ['46%', '#c07ad0'], ['100%', '#7fc4f2']]) {
|
||||
lauf.appendChild(svgEl('stop', { offset: o, 'stop-color': f }));
|
||||
}
|
||||
defs.appendChild(lauf);
|
||||
svg.appendChild(defs);
|
||||
|
||||
/* 1. Die leere Bahn zuerst -- damit der Ring auch bei 0 % ein Ring
|
||||
ist und nicht ein Punkt. */
|
||||
svg.appendChild(svgEl('path', {
|
||||
d: bogen(150, 150, 128, 0, Math.PI * 1.9999),
|
||||
stroke: 'rgba(120, 150, 185, .16)', 'stroke-width': '9',
|
||||
fill: 'none', 'stroke-linecap': 'round',
|
||||
}));
|
||||
|
||||
/* 2. Ein Segment je Person (oder Stunde), innen. Mit Luecke
|
||||
dazwischen, sonst verschmelzen sie zu einem Ring und man
|
||||
zaehlt nichts mehr. */
|
||||
if (segmente.length) {
|
||||
const schritt = (Math.PI * 2) / segmente.length;
|
||||
const luecke = Math.min(schritt * 0.22, 0.06);
|
||||
segmente.forEach((s, i) => {
|
||||
const von = i * schritt + luecke / 2;
|
||||
const bis = (i + 1) * schritt - luecke / 2;
|
||||
const st = svgEl('path', {
|
||||
d: bogen(150, 150, 106, von, bis),
|
||||
stroke: RING_FARBE[s.stufe] || '#8fb3cf',
|
||||
'stroke-width': '7', fill: 'none', 'stroke-linecap': 'round',
|
||||
opacity: s.stufe === 'offen' ? '.95' : '.72',
|
||||
});
|
||||
/* Der Name als Sprechblase. Nur der Vorname, und nur das, was
|
||||
der Server ohnehin geschickt hat -- der Browser erfaehrt
|
||||
nichts, was er nicht sehen darf. */
|
||||
const titel = document.createElementNS('http://www.w3.org/2000/svg', 'title');
|
||||
titel.textContent = s.name || '';
|
||||
st.appendChild(titel);
|
||||
svg.appendChild(st);
|
||||
});
|
||||
}
|
||||
|
||||
/* 3. Der Fortschrittsbogen aussen, genau auf der Prozentzahl. */
|
||||
const anteil = Math.max(0, Math.min(100, Number(daten.prozent) || 0));
|
||||
const bis = (anteil / 100) * Math.PI * 2;
|
||||
if (bis > 0.01) {
|
||||
const d = bogen(150, 150, 128, 0, Math.min(bis, Math.PI * 1.9999));
|
||||
svg.appendChild(svgEl('path', {
|
||||
d, stroke: 'url(#zring-lauf)', 'stroke-width': '9',
|
||||
fill: 'none', 'stroke-linecap': 'round',
|
||||
}));
|
||||
/* Ein heller Punkt an der Spitze macht aus dem Bogen einen
|
||||
Zeiger -- er markiert den Stand so genau, wie die Zahl es sagt. */
|
||||
const px = 150 + 128 * Math.cos(bis), py = 150 + 128 * Math.sin(bis);
|
||||
svg.appendChild(svgEl('circle', { cx: px, cy: py, r: '3.4', fill: '#eaf4ff' }));
|
||||
}
|
||||
|
||||
$('zring-prozent').textContent = `${anteil} %`;
|
||||
$('zring-unter').textContent = daten.titel || '';
|
||||
}
|
||||
|
||||
async function zentraleLaden() {
|
||||
let daten;
|
||||
try {
|
||||
const antwort = await fetch('/workspace/api/zentrale', { credentials: 'same-origin' });
|
||||
if (!antwort.ok) return;
|
||||
daten = await antwort.json();
|
||||
} catch { return; } /* Der Rest der Kachel steht trotzdem. */
|
||||
|
||||
ringZeichnen(daten);
|
||||
|
||||
/* Die drei Zahlen unter dem Titel kommen bei Leitung aus derselben
|
||||
Antwort -- eine Abfrage, eine Wahrheit. Fuer die Tagesansicht
|
||||
fuellen `lageZeigen` und `tagestermineLaden` sie weiter; deshalb
|
||||
wird hier nur ueberschrieben, was der Server auch geschickt hat. */
|
||||
const zahlen = daten.zahlen || [];
|
||||
const felder = ['heute', 'naechster', 'offen'];
|
||||
zahlen.slice(0, 3).forEach((z, i) => {
|
||||
const wert = $('stand-' + felder[i]);
|
||||
const schild = wert?.parentElement?.querySelector('.stand__schild');
|
||||
if (!wert || !schild) return;
|
||||
wert.textContent = String(z.wert);
|
||||
schild.textContent = z.schild;
|
||||
/* Die Farbe folgt der Bedeutung, nicht der Position: Was liegen
|
||||
bleibt, ist rot -- egal an welcher Stelle es steht. */
|
||||
const kasten = wert.closest('.stand');
|
||||
if (kasten) {
|
||||
kasten.dataset.stufe = /liegt|offen|fehlt/i.test(z.schild)
|
||||
? (Number(z.wert) > 0 ? 'warnung' : 'ruhig') : '';
|
||||
}
|
||||
});
|
||||
if (zahlen.length) {
|
||||
standVonZentrale = true;
|
||||
$('tagesstand').hidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Die drei Ablesungen in der Konsolenmitte --------------------- */
|
||||
|
||||
/* EINE Funktion fuer alle drei, und sie wird von den STELLEN gerufen,
|
||||
@@ -546,7 +714,22 @@
|
||||
auf beide: Wer nichts Offenes hat, aber drei Calls, soll seinen Tag
|
||||
sehen, auch wenn die andere Abfrage haengt. Genau diesen Fehler hat
|
||||
die Kachel darunter schon einmal gemacht. */
|
||||
/* WER DIE FELDER BESETZT HAT, BEHAELT SIE (08.09.2026).
|
||||
|
||||
Seit der Ring da ist, schreiben ZWEI Quellen in dieselben drei
|
||||
Felder: `zentraleLaden` (bei Leitung: im Team / heute dran / liegt
|
||||
liegen) und `lageZeigen` + `tagestermineLaden` (Heute / Als
|
||||
Naechstes / Offen). Beide laufen nebeneinander los -- wer zuletzt
|
||||
ankommt, haette gewonnen, und das waere bei jedem Laden eine andere.
|
||||
|
||||
Ein Bildschirm, auf dem "heute dran: 2" steht und beim naechsten
|
||||
Aufruf "Heute: 5", ist schlimmer als einer, der weniger zeigt.
|
||||
Deshalb: Hat die Zentrale die Felder belegt, ruehrt sie niemand
|
||||
mehr an. Die Reihenfolge des Eintreffens spielt dann keine Rolle. */
|
||||
let standVonZentrale = false;
|
||||
|
||||
function standSetzen(welches, wert, stufe) {
|
||||
if (standVonZentrale) return;
|
||||
const feld = $('stand-' + welches);
|
||||
if (!feld) return;
|
||||
feld.textContent = wert;
|
||||
@@ -579,8 +762,26 @@
|
||||
Die Tagestermine laufen daneben her: Sie sind die rechte Hälfte
|
||||
derselben Kachel, aber niemand soll auf sie warten. Scheitern
|
||||
sie, steht die linke Hälfte trotzdem. */
|
||||
/* Der Ring laeuft daneben her, wie die Tagestermine: Er ist die
|
||||
linke Spalte der Kachel, aber niemand soll auf ihn warten.
|
||||
Scheitert er, steht der Rest trotzdem. */
|
||||
const ringLaeuft = zentraleLaden();
|
||||
|
||||
const termineLaeuft = tagestermineLaden();
|
||||
await hinweiseLaden();
|
||||
/* KEIN `await ringLaeuft` HIER (08.09.2026).
|
||||
|
||||
Genau das stand kurz an dieser Stelle, und pruef-start-ansicht
|
||||
wurde daraufhin dreimal rot -- nicht am Ring, sondern am
|
||||
MAUS-LICHT auf den Kacheln weiter unten. Der Grund: Alles, was
|
||||
nach diesem `await` kommt, wartet mit. Der Ring braucht eine
|
||||
Server-Abfrage, und in der Zeit war das Licht noch nicht
|
||||
eingehaengt, als die Pruefung mit dem Zeiger darueberfuhr.
|
||||
|
||||
Gemessen, nicht vermutet: derselbe Lauf am alten Stand meldete
|
||||
null Befunde, mit dem `await` drei. Ein zusaetzliches `await` in
|
||||
einer Ladefolge ist nie nur eine Verzoegerung fuer sich selbst --
|
||||
es verzoegert alles dahinter. */
|
||||
await termineLaeuft;
|
||||
/* Erst jetzt steht fest, wie die Lage ist. */
|
||||
lageZeigen();
|
||||
|
||||
Reference in New Issue
Block a user