Agentur: Eintraege gehoeren allen -- und Events bekommen ein eigenes Formular
Gemessen am 07.09.2026, bevor irgendetwas geaendert wurde: Von drei Agentur-Eintraegen sah eine Creatorin genau EINEN -- den, der ihr zugeordnet war. Ein Event fuer alle traf weder `creator_id = ich` noch `erstellt_von = ich`; die Agentur-Seite war fuer jeden Creator leer. Nicht kaputt, nicht fehlerhaft: leer, so wie eine Seite aussieht, auf der noch nichts steht. Die Pruefung dazu war gruen. Sie legte ihre Testeintraege mit `creator_id: idLuna` an und pruefte damit einen Fall, den es im Alltag nicht gibt. - Bereichseinstellung `fuerAlle` + `ohneCreatorBezug`, daraus abgeleitet sichtbarEintrag(). BEWUSST neben sichtbar() statt darin: an derselben Funktion haengen Aufgaben, Dateien, Termine und Calls -- wer dort "1=1" einschleust, gibt nebenbei fremde Akten frei. Eine Gegenprobe mit einer zweiten Creatorin haelt das fest. - Die Zuordnung bietet nur noch "Agentur" an. Der Server verwirft eine Zuordnung ausserdem selbst -- inklusive des frei getippten Namens, und zwar NACH externPruefen: davor haette der Name den Riegel wieder aufgemacht. - "Event & Kampagne" heisst jetzt "Agentur-Events" und hat ein eigenes Formular: Von/Bis, Titel, Beschreibung, Aufgaben (Punkte und Preise), Regeln. Die Karte zeigt den Zustand als WORT (laeuft bis / startet / vorbei seit), nicht nur als Farbe. - Drei neue Spalten -- und sie stehen auch im Tabellenneubau vom 06.09. Der laeuft NACH dem Spaltennachtrag und haette sie samt Inhalt weggeworfen, ohne Fehler und mit stimmender Zeilenzahl. - Creator sehen weiterhin alles und tragen weiterhin nichts ein (403). Die Unterzeile sagt jetzt "alles, was hier steht" statt "alles, was zu dir gehoert" -- eine vollstaendige Liste soll sich nicht wie ein Ausschnitt lesen. pruef-agentur: 61 Pruefungen (vorher 40), alle gruen. Zwei eigene Fehler nebenbei gefunden und behoben: eine Beschriftung mit 11,2 px (Grenze 11,5) und ein Testdatum aus UTC statt Ortszeit. Zusaetzlich gruen: css-klassen, struktur, formulare, barrierefrei-workspace, handy. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -298,3 +298,102 @@
|
||||
.ampel .schritt { margin-left: 0; }
|
||||
.ampel__knopf { flex: 1 1 auto; text-align: center; }
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
AGENTUR-EVENTS (07.09.2026)
|
||||
|
||||
Ein Event ist die einzige Eintragsart, die eine LAUFZEIT hat. Alles
|
||||
andere in diesen Bereichen ist ein Vermerk zu einem Tag; ein Event
|
||||
ist eine Einladung, an der man teilnehmen kann -- oder eben nicht
|
||||
mehr. Deshalb ist der Zustand (kommt / läuft / vorbei) das Erste auf
|
||||
der Karte und nicht eine Fussnote.
|
||||
|
||||
Farbe wird hier als AUSKUNFT eingesetzt, nicht als Schmuck: gedecktes
|
||||
Grün heisst "du kannst mitmachen", Bernstein "noch nicht", Grau
|
||||
"vorbei". Die Farben stammen aus derselben Familie wie die Ampel
|
||||
(--k-gut / --k-mittel), damit nicht zwei Bedeutungssysteme
|
||||
nebeneinander laufen. Und weil Farbe allein nie ausreicht, steht der
|
||||
Zustand zusaetzlich als WORT da ("läuft bis 30.09.2026").
|
||||
===================================================================== */
|
||||
|
||||
.event-zeitraum {
|
||||
display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
|
||||
margin: 2px 0 10px;
|
||||
font-size: .82rem;
|
||||
}
|
||||
.event-zeitraum__marke {
|
||||
--f: var(--k-neutral);
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 3px 10px;
|
||||
border: 1px solid color-mix(in srgb, var(--f) 34%, transparent);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--f) 12%, transparent);
|
||||
color: color-mix(in srgb, var(--f) 82%, #fff);
|
||||
font-weight: 600; letter-spacing: .01em;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* Der Punkt davor ist reine Zierde und darf deshalb NICHT vorgelesen
|
||||
werden -- ein Screenreader saehe sonst ein Aufzaehlungszeichen mitten
|
||||
im Satz. */
|
||||
.event-zeitraum__marke::before {
|
||||
content: ""; width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--f); flex: none;
|
||||
}
|
||||
.event-zeitraum[data-stand="laeuft"] .event-zeitraum__marke { --f: var(--k-gut); }
|
||||
.event-zeitraum[data-stand="kommt"] .event-zeitraum__marke { --f: var(--k-mittel); }
|
||||
.event-zeitraum[data-stand="vorbei"] .event-zeitraum__marke { --f: var(--k-neutral); }
|
||||
.event-zeitraum__spanne {
|
||||
color: var(--text-leise);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Ein vorbeigelaufenes Event bleibt lesbar, tritt aber zurueck. Kein
|
||||
display:none -- was gelaufen ist, will man nachschlagen koennen.
|
||||
Gedaempft wird nur die Flaeche, nicht die Schrift: Text unter 4.5:1
|
||||
waere nicht mehr lesbar, und "alt" ist kein Grund, jemanden
|
||||
auszuschliessen. */
|
||||
.eintrag-karte[data-event="vorbei"] { opacity: .82; }
|
||||
|
||||
.event-block {
|
||||
margin: 10px 0 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--rand);
|
||||
border-radius: var(--radius-klein);
|
||||
background: rgba(255, 255, 255, .022);
|
||||
}
|
||||
.event-block__schild {
|
||||
margin: 0 0 4px;
|
||||
/* .74rem = 11.84 px, nicht .7rem. Die Schriftgroessen-Pruefung zieht
|
||||
die Grenze bei 11.5 px, und sie hat recht: In Grossbuchstaben mit
|
||||
Sperrung wirkt eine Schrift kleiner, als die Zahl sagt -- und das
|
||||
hier ist die Beschriftung, an der man die Regeln vom Aufgabenteil
|
||||
unterscheidet. */
|
||||
font-size: .74rem; font-weight: 700;
|
||||
letter-spacing: .09em; text-transform: uppercase;
|
||||
color: var(--text-still);
|
||||
}
|
||||
.event-block__text {
|
||||
margin: 0;
|
||||
font-size: .87rem; line-height: 1.6; color: var(--text);
|
||||
/* pre-line, nicht pre-wrap: Zeilenumbrueche bleiben (die Aufzaehlung
|
||||
ist der Sinn dieser Felder), aber eine versehentlich eingefuegte
|
||||
Einrueckung aus einer Chatnachricht reisst die Karte nicht auf. */
|
||||
white-space: pre-line;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Hinweis unter einem Formularfeld -- erklaert das Feld, statt es
|
||||
kommentarlos wegzunehmen. */
|
||||
.feld-hinweis {
|
||||
margin: 5px 0 0;
|
||||
font-size: .76rem; line-height: 1.45;
|
||||
color: var(--text-still);
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
/* Auf dem Handy untereinander: Marke und Spanne nebeneinander
|
||||
ergaeben bei "vorbei seit 30.09.2026" einen Umbruch mitten im
|
||||
Datum. */
|
||||
.event-zeitraum { gap: 6px; }
|
||||
.event-zeitraum__spanne { flex-basis: 100%; }
|
||||
}
|
||||
|
||||
+166
-15
@@ -51,6 +51,32 @@
|
||||
const melde = (t) => { $('fehler').textContent = t || ''; };
|
||||
const datum = (iso) => (iso ? iso.slice(0, 10).split('-').reverse().join('.') : '—');
|
||||
|
||||
/* ---------- Agentur-Events ---------------------------------------------
|
||||
Ein Event ist kein Eintrag mit Ueberschrift und Text, sondern eine
|
||||
Aktion mit Zeitraum, Aufgaben und Regeln. Welche Art das ist, sagt
|
||||
der Server (die Arten kommen aus workspace-bereiche.js) -- die
|
||||
Kennung steht hier trotzdem woertlich, weil das Formular je Art
|
||||
anders aussieht und es dafuer keine allgemeine Regel gibt. Sie
|
||||
steht an EINER Stelle, damit sie sich nicht verteilt. */
|
||||
const EVENT_BEREICH = 'agentur';
|
||||
const EVENT_ART = 'kampagne';
|
||||
const istEventArt = (art) => bereich === EVENT_BEREICH && art === EVENT_ART;
|
||||
|
||||
/* Laeuft das Event noch, kommt es erst, ist es vorbei?
|
||||
Der Vergleich laeuft auf den ISO-Zeichenketten, nicht auf Date --
|
||||
"2026-09-12" < "2026-09-30" ist als Text schon richtig, und ein
|
||||
Date() haette hier eine Zeitzone erfunden, die niemand gemeint hat.
|
||||
Ohne Ende gilt der Beginntag als der ganze Zeitraum. */
|
||||
function eventStand(e) {
|
||||
const heute = window.heuteLokal ? window.heuteLokal() : new Date().toISOString().slice(0, 10);
|
||||
const von = (e.datum || '').slice(0, 10);
|
||||
const bis = (e.event_ende || von).slice(0, 10);
|
||||
if (!von) return null;
|
||||
if (heute < von) return { schluessel: 'kommt', text: 'startet ' + datum(von) };
|
||||
if (heute > bis) return { schluessel: 'vorbei', text: 'vorbei seit ' + datum(bis) };
|
||||
return { schluessel: 'laeuft', text: von === bis ? 'heute' : 'läuft bis ' + datum(bis) };
|
||||
}
|
||||
|
||||
async function hole(pfad, optionen = {}) {
|
||||
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
|
||||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
||||
@@ -91,10 +117,45 @@
|
||||
}
|
||||
k.append(kopf);
|
||||
|
||||
/* ---- Agentur-Event: Zeitraum, Aufgaben, Regeln -------------------
|
||||
Die Reihenfolge ist nicht beliebig. Wer auf ein Event schaut,
|
||||
fragt in genau dieser Folge: Laeuft das ueberhaupt noch? Was
|
||||
muss ich tun? Was gilt dabei? Steht der Zeitraum unten im Fuss
|
||||
-- da, wo bei allen anderen Karten das Datum steht --, liest man
|
||||
erst die Aufgaben und danach, dass es seit einer Woche vorbei
|
||||
ist. */
|
||||
const stand = istEventArt(e.art) ? eventStand(e) : null;
|
||||
if (stand) {
|
||||
k.dataset.event = stand.schluessel;
|
||||
const zeile = el('p', 'event-zeitraum');
|
||||
zeile.dataset.stand = stand.schluessel;
|
||||
zeile.append(el('span', 'event-zeitraum__marke', stand.text));
|
||||
zeile.append(el('span', 'event-zeitraum__spanne',
|
||||
e.event_ende && e.event_ende !== e.datum
|
||||
? `${datum(e.datum)} – ${datum(e.event_ende)}`
|
||||
: datum(e.datum)));
|
||||
k.append(zeile);
|
||||
}
|
||||
|
||||
if (e.text) k.append(el('p', 'eintrag-text', e.text));
|
||||
|
||||
if (stand) {
|
||||
for (const [feld, name] of [['event_aufgaben', 'Aufgaben'], ['event_regeln', 'Regeln']]) {
|
||||
if (!e[feld]) continue;
|
||||
const block = el('div', 'event-block');
|
||||
block.append(el('h4', 'event-block__schild', name));
|
||||
/* Zeilenumbrueche bleiben stehen (white-space: pre-line im CSS).
|
||||
Eine Aufzaehlung, die zu einem Absatz zusammenlaeuft, ist
|
||||
genau das, was diese beiden Felder verhindern sollen. */
|
||||
block.append(el('p', 'event-block__text', e[feld]));
|
||||
k.append(block);
|
||||
}
|
||||
}
|
||||
|
||||
const fuss = el('div', 'eintrag-fuss');
|
||||
fuss.append(el('span', null, datum(e.datum)));
|
||||
/* Beim Event steht der Zeitraum schon oben -- das Datum hier waere
|
||||
dieselbe Zahl ein zweites Mal. */
|
||||
if (!stand) fuss.append(el('span', null, datum(e.datum)));
|
||||
if (LEITUNG.has(ich.rolle) && e.creator_name) fuss.append(el('span', null, '· ' + e.creator_name));
|
||||
if (e.erstellt_name) fuss.append(el('span', null, '· von ' + e.erstellt_name));
|
||||
if (e.status === 'erledigt') fuss.append(el('span', null, '· erledigt'));
|
||||
@@ -276,6 +337,44 @@
|
||||
|
||||
/* ---------- Neuer Eintrag ---------------------------------------------- */
|
||||
|
||||
/* Das Formular sieht bei einem Agentur-Event anders aus als bei allem
|
||||
anderen -- nicht "zusaetzlich", sondern anders: Aus "Überschrift"
|
||||
wird der Titel des Events, aus "Text" die Beschreibung, aus dem
|
||||
Datum ein Zeitraum, und Dringlichkeit hat hier nichts zu suchen
|
||||
(ein Event ist nicht dringend, es hat einen Termin).
|
||||
|
||||
Umgeschaltet wird bei JEDEM Artwechsel und beim Oeffnen. Nur beim
|
||||
Oeffnen zu schalten waere der bequeme Fehler: Man oeffnet mit
|
||||
"Schulung", waehlt "Agentur-Events" und fuellt ein Formular aus,
|
||||
das noch nach der alten Art fragt. */
|
||||
function formularAnpassen() {
|
||||
const event = istEventArt($('f-art').value);
|
||||
|
||||
$('feld-ende').hidden = !event;
|
||||
$('feld-aufgaben').hidden = !event;
|
||||
$('feld-regeln').hidden = !event;
|
||||
if (einstellung?.dringlichkeit) $('feld-dringlich').hidden = event;
|
||||
|
||||
$('schild-datum').textContent = event ? 'Von' : 'Datum';
|
||||
$('schild-titel').textContent = event ? 'Titel des Events' : 'Überschrift';
|
||||
/* textContent wuerde das <span class="leise"> mit erschlagen --
|
||||
deshalb nur der erste Textknoten, das kleine "(optional)" bleibt
|
||||
stehen. */
|
||||
const textSchild = $('schild-text').firstChild;
|
||||
if (textSchild) textSchild.nodeValue = event ? 'Beschreibung ' : 'Text ';
|
||||
|
||||
/* Leeren, was gerade unsichtbar wird. Sonst reicht ein Wechsel von
|
||||
"Agentur-Events" auf "Schulung", und das Ende des Zeitraums
|
||||
faehrt unsichtbar mit -- der Server nimmt es an, weil es zum
|
||||
Bereich gehoert, und an der Schulung stuende ein Enddatum, das
|
||||
niemand eingetragen hat und niemand sieht. */
|
||||
if (!event) {
|
||||
$('f-ende').value = '';
|
||||
$('f-aufgaben').value = '';
|
||||
$('f-regeln').value = '';
|
||||
}
|
||||
}
|
||||
|
||||
const umschalten = (auf) => {
|
||||
/* Auch hier die Regel, nicht nur einmal beim Aufbau: Diese Funktion
|
||||
ist die einzige Stelle, die das Formular je wieder sichtbar macht.
|
||||
@@ -288,11 +387,17 @@
|
||||
};
|
||||
$('neu-oeffnen').addEventListener('click', () => {
|
||||
$('f-datum').value = window.heuteLokal();
|
||||
formularAnpassen();
|
||||
umschalten(true);
|
||||
});
|
||||
$('neu-abbrechen').addEventListener('click', () => {
|
||||
$('neu').reset(); $('neu-fehler').textContent = ''; umschalten(false);
|
||||
$('neu').reset(); $('neu-fehler').textContent = ''; formularAnpassen(); umschalten(false);
|
||||
});
|
||||
/* "change" und nicht "input": Die Auswahl wird ueber wahl.js zu einer
|
||||
Knopfliste umgebaut, und die meldet ihre Wahl als change-Ereignis
|
||||
am urspruenglichen <select> -- ohne dieses Ereignis merkte das
|
||||
Formular vom Artwechsel nichts. */
|
||||
$('f-art').addEventListener('change', formularAnpassen);
|
||||
|
||||
$('neu').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
@@ -304,9 +409,19 @@
|
||||
text: $('f-text').value,
|
||||
datum: $('f-datum').value,
|
||||
};
|
||||
if (einstellung.dringlichkeit) daten.dringlichkeit = $('f-dringlich').value;
|
||||
const event = istEventArt(daten.art);
|
||||
if (einstellung.dringlichkeit && !event) daten.dringlichkeit = $('f-dringlich').value;
|
||||
if (einstellung.bewertung) daten.bewertung = $('f-bewertung').value || null;
|
||||
if (LEITUNG.has(ich.rolle)) {
|
||||
if (event) {
|
||||
daten.event_ende = $('f-ende').value || null;
|
||||
daten.event_aufgaben = $('f-aufgaben').value || null;
|
||||
daten.event_regeln = $('f-regeln').value || null;
|
||||
}
|
||||
/* Im Agentur-Bereich gehoert ein Eintrag allen -- es gibt nichts
|
||||
zuzuordnen, also wird auch nichts mitgeschickt. Der Server setzt
|
||||
beides ohnehin auf leer (ohneCreatorBezug); hier zu schweigen
|
||||
spart die stille Annahme, das Formular haette etwas zu sagen. */
|
||||
if (LEITUNG.has(ich.rolle) && !einstellung.ohneCreatorBezug) {
|
||||
daten.creator_id = $('f-creator').value || null;
|
||||
daten.creator_extern = $('f-creator').dataset.extern || null;
|
||||
}
|
||||
@@ -365,8 +480,16 @@
|
||||
if (!darfEintragen()) {
|
||||
$('neu-oeffnen').hidden = true;
|
||||
$('neu').hidden = true;
|
||||
$('unterzeile').textContent =
|
||||
(UNTERZEILE[bereich] || '') + ' Hier trägt deine Betreuung ein – du siehst alles, was zu dir gehört.';
|
||||
/* Zwei verschiedene Saetze, weil es zwei verschiedene Wahrheiten
|
||||
sind. In den Betreuungsbereichen sieht ein Creator, was ZU IHM
|
||||
gehoert -- in der Agentur sieht er ALLES. Stuende hier weiter
|
||||
"was zu dir gehört", laese sich eine vollstaendige Liste wie
|
||||
ein Ausschnitt, und die naheliegende Frage waere: Was sehe ich
|
||||
hier eigentlich nicht? */
|
||||
$('unterzeile').textContent = (UNTERZEILE[bereich] || '')
|
||||
+ (einstellung.fuerAlle
|
||||
? ' Eingetragen wird von der Agentur – du siehst alles, was hier steht.'
|
||||
: ' Hier trägt deine Betreuung ein – du siehst alles, was zu dir gehört.');
|
||||
}
|
||||
|
||||
for (const [wert, name] of Object.entries(einstellung.arten)) {
|
||||
@@ -379,16 +502,44 @@
|
||||
|
||||
if (LEITUNG.has(ich.rolle)) {
|
||||
$('feld-creator').hidden = false;
|
||||
try {
|
||||
const { personen } = await (await hole('/workspace/api/personen')).json();
|
||||
for (const p of personen.filter((p) => p.rolle === 'creator')) {
|
||||
const o = document.createElement('option');
|
||||
o.value = String(p.id); o.textContent = p.name;
|
||||
$('f-creator').append(o);
|
||||
}
|
||||
} catch { /* Zuordnung bleibt leer */ }
|
||||
window.personenwahl?.('f-creator', { hinweis: "Kein Konto im Workspace – der Eintrag gehört dann zu keiner Person und taucht in personenbezogenen Auswertungen nicht auf. Sichtbar bleibt er für dich." });
|
||||
|
||||
/* AGENTUR: EINE EINZIGE WAHL, UND DIE HEISST "AGENTUR" (07.09.2026).
|
||||
|
||||
Hier stand dieselbe Creator-Liste wie ueberall -- und sie war
|
||||
eine Falle. Wer ein Event fuer alle anlegt und dabei aus
|
||||
Gewohnheit einen Namen anklickt, hat es damit UNSICHTBAR
|
||||
gemacht: fuer alle anderen Creator, ohne Warnung, ohne dass es
|
||||
der Liste danach anzusehen waere.
|
||||
|
||||
Kein Feld auszublenden waere die halbe Loesung gewesen: Dann
|
||||
fragt man sich, wem der Eintrag gehoert. Eine Liste mit genau
|
||||
einer Wahl beantwortet die Frage, statt sie wegzunehmen.
|
||||
|
||||
Die Personenwahl (freier Name, Suchfeld) wird hier bewusst
|
||||
NICHT aufgesetzt -- sie erlaubt getippte Namen, und genau das
|
||||
soll es hier nicht geben. */
|
||||
if (einstellung.ohneCreatorBezug) {
|
||||
$('schild-creator').textContent = 'Gilt für';
|
||||
$('f-creator').textContent = '';
|
||||
const o = document.createElement('option');
|
||||
o.value = ''; o.textContent = 'Agentur';
|
||||
$('f-creator').append(o);
|
||||
const hinweis = $('hinweis-creator');
|
||||
hinweis.textContent = 'Agentur-Einträge gehören allen – jeder im Workspace sieht sie.';
|
||||
hinweis.hidden = false;
|
||||
} else {
|
||||
try {
|
||||
const { personen } = await (await hole('/workspace/api/personen')).json();
|
||||
for (const p of personen.filter((p) => p.rolle === 'creator')) {
|
||||
const o = document.createElement('option');
|
||||
o.value = String(p.id); o.textContent = p.name;
|
||||
$('f-creator').append(o);
|
||||
}
|
||||
} catch { /* Zuordnung bleibt leer */ }
|
||||
window.personenwahl?.('f-creator', { hinweis: "Kein Konto im Workspace – der Eintrag gehört dann zu keiner Person und taucht in personenbezogenen Auswertungen nicht auf. Sichtbar bleibt er für dich." });
|
||||
}
|
||||
}
|
||||
formularAnpassen();
|
||||
|
||||
/* Kommt jemand vom Bericht mit "nur die offenen", wird der Filter
|
||||
VOR dem ersten Laden gesetzt -- sonst baut die Seite alles auf,
|
||||
|
||||
Reference in New Issue
Block a user