Scouting: links die normalen Creator, rechts die Premium
Filipe: "ich will dass es da in der mitte eine trennung gibt. links sollen normale creator sein und rechts premium creator. es gibt bei tiktok naemlich diese zwei optionen von streamer ... auch dass wenn man die eintraegt soll man aussuchen koennen." Jede Stufe der Pipeline hat jetzt zwei Spalten mit einer Trennlinie dazwischen. Die Trennung sitzt IN der Stufe, nicht einmal ueber der ganzen Seite: Zwei komplette Pipelines nebeneinander haetten jede Stufenueberschrift verdoppelt und die beiden Seiten waeren nie auf gleicher Hoehe gewesen. GENAU ZWEI WERTE, kein "weiss nicht". Anders als bei `netzwerk`, wo "noch nicht gefragt" eine eigene gueltige Antwort ist: Dort wird eine fremde Tatsache festgehalten, hier eine eigene Absicht -- und deren Normalfall ist "normal". NULL wird als "normal" gelesen, der eine vorhandene Lead steht damit links, ohne dass ihm etwas unterstellt wird. DIE ART UEBERLEBT DIE UEBERGABE. Beim Creator-Onboarding wandert sie auf die Person und ist in der Personenverwaltung aenderbar. Ohne das endet die Angabe genau dort, wo sie zum ersten Mal vertraglich zaehlt. Die Schwelle misst den STUFENKASTEN (@container), nicht das Fenster -- der Fehler vom 06.09.2026 im selben Haus. Auf dem Handy liegen die Spalten untereinander, die senkrechte Linie faellt weg. ZWEI FUNDE BEIM BAUEN, beide von Hauswachen: * `pruef-fingermass` fand, dass meine neue Handy-Messung mit `setViewportSize` statt eigenem Kontext lief -- ohne `hasTouch` greift keine einzige Regel aus `@media (pointer: coarse)`. * Die Klassen hiessen zuerst `.spalte__*` -- die gehoeren `aufgaben.css`, und scouting.html laedt die VOR scouting.css. `margin-left: auto` aus dem Aufgabenbrett zog die Zahl 560 px von ihrem Wort weg, ohne dass eine Pruefung rot wurde. Jetzt `.art-spalte__*`, und der Abstand wird gemessen. pruef-scouting-felder: 28 -> 64 Pruefungen, gruen. Zwei Gegenproben gefahren (Trennung deaktiviert, Spaltenreihenfolge gedreht) -- beide schlugen an. Dazu gruen: haus-trennung, css-klassen, personen-liste, personen-kachel, hand-personen, schranke, formulare, lesbarkeit, agentur, deutsche-texte, tippziele, leerzustand, fingermass. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -426,6 +426,12 @@ Der Code gilt einmal fuer die `
|
||||
/* ---------- Liste ----------------------------------------------------- */
|
||||
|
||||
let betreuerListe = [];
|
||||
/* Normal und Premium kommen vom Server (siehe die Personenliste).
|
||||
Die Vorgabe hier ist die Rueckfallebene fuer den Fall, dass ein
|
||||
alter Serverstand die Liste noch nicht mitschickt -- dann fehlt
|
||||
die Auswahl nicht, sie ist nur nicht nachgefuehrt. */
|
||||
let artenListe = ['normal', 'premium'];
|
||||
let artName = { normal: 'Normal', premium: 'Premium' };
|
||||
|
||||
/** Ein oder zwei Buchstaben aus einem Namen.
|
||||
*
|
||||
@@ -562,6 +568,22 @@ Der Code gilt einmal fuer die `
|
||||
anderen grauen Woertern -- auf einer Kachel, die ohnehin blasser
|
||||
ist, hat es niemand gesehen. Jetzt ist es eine Marke in der
|
||||
Warnfarbe. */
|
||||
/* PREMIUM STEHT NEBEN DER ROLLE (06.10.2026).
|
||||
|
||||
"Creator" allein sagt nicht mehr alles, seit es die beiden Arten
|
||||
gibt, die TikTok kennt. Die Marke steht deshalb direkt neben der
|
||||
Rolle und nicht im Zahlenband -- sie beschreibt, WER jemand ist,
|
||||
nicht wie viel er tut.
|
||||
|
||||
Nur Premium bekommt eine Marke, genau wie in der Pipeline:
|
||||
"Normal" auf jeder zweiten Kachel wiederholte nur, was ohnehin
|
||||
der Normalfall ist. */
|
||||
if (p.rolle === 'creator' && p.creator_art === 'premium') {
|
||||
const a = el('span', 'marke-rolle', 'Premium');
|
||||
a.dataset.art = 'premium';
|
||||
a.title = 'Premium-Creator – die zweite der beiden Arten, die TikTok kennt.';
|
||||
marken.append(a);
|
||||
}
|
||||
if (!p.aktiv) {
|
||||
const g = el('span', 'marke-rolle', 'gesperrt');
|
||||
g.dataset.rolle = 'gesperrt';
|
||||
@@ -637,6 +659,44 @@ Der Code gilt einmal fuer die `
|
||||
links.append(el('div', 'person__bezug', `gehört zu ${p.manager_name}`));
|
||||
}
|
||||
|
||||
/* NORMAL ODER PREMIUM — änderbar (06.10.2026).
|
||||
|
||||
Beim Creator-Onboarding kommt die Art aus der Scout-Pipeline mit.
|
||||
Danach hängt sie an der Person, und hier wird sie geändert: Ein
|
||||
Vertrag kann wechseln, und ein Lead, aus dem die Angabe einmal
|
||||
stammte, kann längst gelöscht sein.
|
||||
|
||||
Dieselbe Bauart wie „Betreut von" direkt darunter — ein Auswahlfeld,
|
||||
das beim Umstellen sofort speichert. Kein eigener Speichern-Knopf:
|
||||
Bei einer Auswahl mit zwei Werten wäre er ein zweiter Klick, den
|
||||
man vergisst, und dann steht etwas anderes da, als man sieht. */
|
||||
if (p.rolle === 'creator') {
|
||||
const a = el('div', 'betreuung');
|
||||
const as = el('label', 'betreuung__schild', 'Art');
|
||||
as.htmlFor = 'creator-art-' + p.id;
|
||||
const aw = el('select', 'betreuung__wahl');
|
||||
aw.id = 'creator-art-' + p.id;
|
||||
for (const w of artenListe) aw.append(new Option(artName[w] || w, w));
|
||||
aw.value = p.creator_art === 'premium' ? 'premium' : 'normal';
|
||||
aw.addEventListener('change', async () => {
|
||||
aw.disabled = true; melde('');
|
||||
try {
|
||||
const antwort = await hole(`/workspace/api/verwaltung/personen/${p.id}/creator-art`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ creator_art: aw.value }),
|
||||
});
|
||||
if (!antwort.ok) {
|
||||
melde(sagWas((await antwort.json().catch(() => ({}))).fehler, 'Ging nicht.'));
|
||||
return;
|
||||
}
|
||||
await laden();
|
||||
} catch { /* umgeleitet */ } finally { aw.disabled = false; }
|
||||
});
|
||||
a.append(as, aw);
|
||||
links.append(a);
|
||||
}
|
||||
|
||||
/* Zuständigkeit. Steht nur bei Creator und nur dann, wenn es
|
||||
überhaupt einen Scout gibt, dem man sie geben könnte. */
|
||||
if (p.rolle === 'creator' && betreuerListe.length) {
|
||||
@@ -1257,8 +1317,13 @@ Der Code gilt einmal fuer die `
|
||||
try {
|
||||
const a = await hole('/workspace/api/verwaltung/personen');
|
||||
if (!a.ok) { melde('Personen konnten nicht geladen werden.'); return; }
|
||||
const { personen, betreuer, zusatzrollen } = await a.json();
|
||||
const { personen, betreuer, zusatzrollen, arten, art_name } = await a.json();
|
||||
betreuerListe = betreuer || [];
|
||||
/* Nur uebernehmen, wenn wirklich etwas kommt: `|| []` haette hier
|
||||
eine leere Auswahl ergeben -- ein Feld ohne einen einzigen
|
||||
Eintrag sieht aus wie ein Fehler und ist einer. */
|
||||
if (Array.isArray(arten) && arten.length) artenListe = arten;
|
||||
if (art_name) artName = art_name;
|
||||
zusatzAbschnitte = Array.isArray(zusatzrollen) ? zusatzrollen : [];
|
||||
rollenwahlErgaenzen(zusatzrollen);
|
||||
alle = personen;
|
||||
|
||||
@@ -48,6 +48,15 @@
|
||||
Oberflaeche fuehrt keine eigenen. Leere Vorgabe, damit ein
|
||||
Ausfall der Abfrage kein kaputtes Formular ergibt. */
|
||||
let laenderListe = [], landName = {}, netzwerkListe = [], netzwerkName = {};
|
||||
/* Normal und Premium -- die beiden Arten, die TikTok kennt.
|
||||
|
||||
DIE RUECKFALLLISTE IST KEIN SCHMUCK: Sie steht hier, damit die
|
||||
Trennung auch dann zwei Spalten hat, wenn ein alter Server die
|
||||
Listen noch nicht mitschickt. Ohne sie waere die Seite in genau dem
|
||||
Moment leer, in dem jemand sie zum ersten Mal nach einem Deploy
|
||||
aufmacht -- der Server neu, die Datei aus dem Zwischenspeicher alt,
|
||||
oder umgekehrt. */
|
||||
let artenListe = ['normal', 'premium'], artName = { normal: 'Normal', premium: 'Premium' };
|
||||
let heute = '';
|
||||
let daten = [];
|
||||
let offen = new Set();
|
||||
@@ -111,7 +120,13 @@
|
||||
/* Kopf */
|
||||
const kopf = el('div', 'kk__kopf');
|
||||
const links = el('div');
|
||||
links.append(el('h3', 'kk__name', l.name));
|
||||
/* h4, NICHT h3 (06.10.2026). Ueber der Karte stehen jetzt drei
|
||||
Ebenen: "Pipeline" (h1), die Stufe (h2) und die Spalte Normal /
|
||||
Premium (h3). Bliebe der Name ein h3, saesse er auf derselben
|
||||
Ebene wie die Spalte, in der er liegt -- wer sich per
|
||||
Ueberschriften durch die Seite bewegt, bekaeme die Trennung nicht
|
||||
mehr zu hoeren. */
|
||||
links.append(el('h4', 'kk__name', l.name));
|
||||
const unter = [l.plattform, l.handle].filter(Boolean).join(' · ');
|
||||
if (unter) links.append(el('p', 'kk__unter', unter));
|
||||
kopf.append(links);
|
||||
@@ -134,6 +149,23 @@
|
||||
n.title = 'Noch nicht geklärt, ob die Person schon bei einer Agentur ist.';
|
||||
marken.append(n);
|
||||
}
|
||||
/* PREMIUM TRAEGT EIN ZEICHEN, NORMAL NICHT (06.10.2026).
|
||||
|
||||
Dieselbe Regel wie bei "Schon im Netzwerk": Nur der Fall, der vom
|
||||
Normalfall abweicht, bekommt eine Marke. Eine Marke "Normal" auf
|
||||
jeder zweiten Karte waere ein Wort, das nie etwas Neues sagt.
|
||||
|
||||
WARUM UEBERHAUPT, WO DOCH DIE SPALTE ES SCHON SAGT: Die beiden
|
||||
Spalten werden verschieden lang. Steht links eine lange Liste und
|
||||
rechts eine kurze, ist eine Karte weit unten vom Kopf ihrer Spalte
|
||||
durch einen halben Bildschirm getrennt -- und auf dem Handy liegen
|
||||
die Spalten ohnehin untereinander. */
|
||||
if (l.creator_art === 'premium') {
|
||||
const a = el('span', 'marke-punkt', artName.premium || 'Premium');
|
||||
a.dataset.art = 'premium';
|
||||
a.title = 'Premium-Creator – die zweite der beiden Arten, die TikTok kennt.';
|
||||
marken.append(a);
|
||||
}
|
||||
const prio = el('span', 'marke-punkt', l.prioritaet === 'hoch' ? 'Hoch'
|
||||
: l.prioritaet === 'niedrig' ? 'Niedrig' : 'Mittel');
|
||||
prio.dataset.prio = l.prioritaet;
|
||||
@@ -240,6 +272,16 @@
|
||||
const o = document.createElement('option'); o.value = w; o.textContent = t; f.append(o);
|
||||
}
|
||||
});
|
||||
/* Die Art laesst sich auch nachtraeglich aendern -- ein Lead rutscht
|
||||
dabei in die andere Spalte. Genau das war der Mangel vom
|
||||
11.09.2026 ("man kan da nichts machen"): ein Feld, das man beim
|
||||
Anlegen setzt und danach nie wieder. */
|
||||
const fArt = mach('Art', 'select', l.creator_art || 'normal', (f) => {
|
||||
for (const w of artenListe) {
|
||||
const o = document.createElement('option');
|
||||
o.value = w; o.textContent = artName[w] || w; f.append(o);
|
||||
}
|
||||
});
|
||||
const fStufe = mach('Stufe', 'select', l.status, (f) => {
|
||||
for (const [w, t] of Object.entries(stufen)) {
|
||||
const o = document.createElement('option'); o.value = w; o.textContent = t; f.append(o);
|
||||
@@ -310,6 +352,7 @@
|
||||
const r = await schicke('/workspace/api/leads/' + l.id, 'PATCH', {
|
||||
notizen: notiz.value, letzte_nachricht: fNachricht.value,
|
||||
naechster_followup: fFollowup.value, prioritaet: fPrio.value, status: fStufe.value,
|
||||
creator_art: fArt.value,
|
||||
netzwerk: fNetz.value, land: fLand.value, follower: fFollower.value,
|
||||
woher: fWoher.value, kontaktweg: fWeg.value, live_zeiten: fZeiten.value,
|
||||
aktivitaet: fAktiv.value, potenzial: fPot.value,
|
||||
@@ -475,13 +518,58 @@
|
||||
if (STUFE_WAS[wert]) kt.append(el('p', 'stufe__was', STUFE_WAS[wert]));
|
||||
kopf.append(kt, el('span', 'stufe__zahl', String(drin.length)));
|
||||
gruppe.append(kopf);
|
||||
const box = el('div', 'stufe__karten');
|
||||
for (const l of drin) {
|
||||
const k = karte(l);
|
||||
k.id = 'lead-' + l.id;
|
||||
box.append(k);
|
||||
|
||||
/* ====== LINKS NORMAL, RECHTS PREMIUM (06.10.2026) ==============
|
||||
|
||||
Filipe: "ich will dass es da in der mitte eine trennung gibt.
|
||||
links sollen normale creator sein und rechts premium creator.
|
||||
es gibt bei tiktok nämlich diese zwei optionen von streamer."
|
||||
|
||||
DIE TRENNUNG SITZT IN JEDER STUFE, nicht einmal ueber der
|
||||
ganzen Seite. Zwei komplette Pipelines nebeneinander haetten
|
||||
jede Stufenueberschrift zweimal gebraucht, und die beiden
|
||||
Seiten waeren nie auf gleicher Hoehe gewesen -- "Angesprochen"
|
||||
links auf Hoehe von "Interessiert" rechts. So bleibt die Stufe
|
||||
die Ueberschrift und die Art die Spalte darunter.
|
||||
|
||||
DIE REIHENFOLGE KOMMT VOM SERVER (artenListe) und ist dort
|
||||
"normal, premium" -- links, rechts. Sie steht nicht zweimal da;
|
||||
waere sie hier noch einmal hingeschrieben, koennte eine der
|
||||
beiden Stellen gedreht werden und die Spalten wuerden
|
||||
vertauscht, ohne dass irgendetwas kaputtginge.
|
||||
|
||||
EINE LEERE SPALTE BLEIBT STEHEN und sagt "noch niemand". Wer
|
||||
sie wegliesse, haette eine Stufe, die ploetzlich eine Spalte
|
||||
hat statt zwei -- und man wuesste nicht, ob da niemand ist oder
|
||||
ob die Trennung an dieser Stelle nicht gilt. Die Zahl im Kopf
|
||||
beantwortet es ohne Nachzaehlen. */
|
||||
const spalten = el('div', 'stufe__arten');
|
||||
for (const art of artenListe) {
|
||||
const inDieser = drin.filter((l) => (l.creator_art || 'normal') === art);
|
||||
const sp = el('section', 'art-spalte');
|
||||
sp.dataset.art = art;
|
||||
|
||||
const spKopf = el('div', 'art-spalte__kopf');
|
||||
const spTitel = el('h3', 'art-spalte__titel', artName[art] || art);
|
||||
/* Die Ueberschrift benennt den Bereich fuer die Sprachausgabe --
|
||||
sonst liest sie zwei Kartenstapel vor, ohne zu sagen, welcher
|
||||
welcher ist. */
|
||||
spTitel.id = `art-spalte-${wert}-${art}`;
|
||||
sp.setAttribute('aria-labelledby', spTitel.id);
|
||||
spKopf.append(spTitel, el('span', 'art-spalte__zahl', String(inDieser.length)));
|
||||
sp.append(spKopf);
|
||||
|
||||
const box = el('div', 'stufe__karten');
|
||||
if (!inDieser.length) box.append(el('p', 'art-spalte__leer', 'noch niemand'));
|
||||
for (const l of inDieser) {
|
||||
const k = karte(l);
|
||||
k.id = 'lead-' + l.id;
|
||||
box.append(k);
|
||||
}
|
||||
sp.append(box);
|
||||
spalten.append(sp);
|
||||
}
|
||||
gruppe.append(box);
|
||||
gruppe.append(spalten);
|
||||
ziel.append(gruppe);
|
||||
}
|
||||
}
|
||||
@@ -494,12 +582,38 @@
|
||||
stufen = d.stufen; heute = d.heute; daten = d.leads;
|
||||
laenderListe = d.laender || []; landName = d.land_name || {};
|
||||
netzwerkListe = d.netzwerk || []; netzwerkName = d.netzwerk_name || {};
|
||||
/* Nur uebernehmen, wenn wirklich etwas kommt -- sonst stuende hier
|
||||
eine leere Liste und die Seite haette null Spalten statt zwei.
|
||||
`|| []` waere an dieser Stelle genau der stille Ausfall, den die
|
||||
Rueckfallliste oben verhindern soll. */
|
||||
if (Array.isArray(d.arten) && d.arten.length) artenListe = d.arten;
|
||||
if (d.art_name) artName = d.art_name;
|
||||
artwahlFuellen();
|
||||
zeichne();
|
||||
} catch { /* umgeleitet */ }
|
||||
}
|
||||
|
||||
/* ---------- Neuer Kontakt ------------------------------------------------ */
|
||||
|
||||
/* Die Auswahl im Anlege-Formular wird aus derselben Liste gefuellt wie
|
||||
die Spalten. In scouting.html stehen Normal und Premium zwar schon
|
||||
fest drin -- das ist die Vorgabe, damit das Feld auch vor der ersten
|
||||
Antwort bedienbar ist. Sobald der Server geantwortet hat, gilt
|
||||
seine Liste, und zwar auch dann, wenn dort eines Tages etwas anderes
|
||||
steht. Die bereits getroffene Wahl bleibt erhalten. */
|
||||
function artwahlFuellen() {
|
||||
const f = $('n-art');
|
||||
if (!f) return;
|
||||
const vorher = f.value;
|
||||
f.textContent = '';
|
||||
for (const w of artenListe) {
|
||||
const o = document.createElement('option');
|
||||
o.value = w; o.textContent = artName[w] || w;
|
||||
f.append(o);
|
||||
}
|
||||
f.value = artenListe.includes(vorher) ? vorher : (artenListe[0] || '');
|
||||
}
|
||||
|
||||
$('neu-auf').addEventListener('click', () => {
|
||||
$('neu-block').hidden = false;
|
||||
$('neu-auf').hidden = true;
|
||||
@@ -523,6 +637,7 @@
|
||||
body: JSON.stringify({
|
||||
name: $('n-name').value, plattform: $('n-plattform').value,
|
||||
handle: $('n-handle').value, prioritaet: $('n-prio').value,
|
||||
creator_art: $('n-art').value,
|
||||
aktivitaet: $('n-aktivitaet').value, potenzial: $('n-potenzial').value,
|
||||
naechster_followup: $('n-followup').value,
|
||||
scout_id: $('n-scout').value || null,
|
||||
|
||||
Reference in New Issue
Block a user