diff --git a/docs/07-anleitung-benutzer.md b/docs/07-anleitung-benutzer.md index c52fc2a..90d5bec 100644 --- a/docs/07-anleitung-benutzer.md +++ b/docs/07-anleitung-benutzer.md @@ -96,7 +96,7 @@ geschnitten wird - Tag, Monat, Quartal oder Jahr. | Kachel | Was gezählt wird | | --- | --- | -| **Bewerbungen** | Einträge in der Akquise im Zeitraum. Jeder Eintrag ist eine versendete Bewerbung | +| **Bewerbungen** | Einträge in der Akquise mit Funddatum im Zeitraum. Jeder Eintrag ist eine versendete Bewerbung - **auch die, aus denen inzwischen eine Opportunity geworden ist**. Die Zahl ist deshalb regelmäßig höher als die Akquise-Liste lang ist; unter den Kacheln steht, wie viele übernommen wurden. Ein von Hand gelöschter Eintrag zählt nicht mehr mit | | **Zu Opportunity** | Bewerbungen, aus denen eine Opportunity geworden ist - dazu die Quote | | **Gewonnen** | Opportunities, die in die Phase *Beauftragt* gekommen sind | | **Beim Kunden** | Opportunities, die **jetzt** in *Kundenvorstellung* oder *Interview Kunde* stehen - unabhängig vom Zeitraum | @@ -106,7 +106,9 @@ geschnitten wird - Tag, Monat, Quartal oder Jahr. Das Säulendiagramm zeigt dieselben drei Reihen über der Zeit: Bewerbungen, daraus entstandene Opportunities und gewonnene Projekte. Leere Abschnitte bleiben sichtbar - eine Woche ohne Bewerbung ist eine Aussage. Der Mauszeiger auf einer -Säule nennt Abschnitt, Reihe und Wert. +Säule nennt Abschnitt, Reihe und Wert. Passen nicht alle Abschnitte nebeneinander, +zeigt das Diagramm das **Ende** des Zeitraums und lässt sich nach links scrollen - +die aktuellen Tage stehen rechts. Rechts daneben steht **Bewerbungen je Berater** für denselben Zeitraum. Einträge ohne hinterlegten Berater erscheinen als *Ohne Berater*. diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index ff478f0..4bb105e 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -42,7 +42,20 @@ const BUCKETS: { id: DashboardBucket; label: string }[] = [ { id: 'jahr', label: 'Jahr' }, ]; -const iso = (date: Date) => date.toISOString().slice(0, 10); +/** + * Ortsdatum als `JJJJ-MM-TT`. + * + * Bewusst nicht `toISOString()`: das rechnet nach UTC und liefert in Zeitzonen + * oestlich von Greenwich fuer den lokalen Tagesanfang den Vortag. Die + * Tagesschluessel liefen damit einen Tag hinter denen der Datenbank her - die + * Saeulen fielen neben die Achse und das Diagramm blieb leer, obwohl die + * Kennzahlen Werte zeigten. + */ +function iso(date: Date): string { + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${date.getFullYear()}-${month}-${day}`; +} /** Anfang und Ende einer Vorgabe - `to` ist immer heute. */ function rangeDates(range: (typeof RANGES)[number]): { from: string; to: string } { @@ -295,15 +308,19 @@ export const Dashboard: React.FC = ({ opportunities, events, isL value={totals.applications} accent="border-l-sky-500" icon="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" - hint="Einträge in der Akquise im gewählten Zeitraum" - footnote={`versendet · ${periodLabel}`} + hint="Alle Akquise-Einträge mit Funddatum im Zeitraum - auch die, aus denen inzwischen eine Opportunity geworden ist und die deshalb nicht mehr in der Akquise-Liste stehen." + footnote={ + totals.converted > 0 + ? `versendet · ${totals.converted} davon übernommen` + : `versendet · ${periodLabel}` + } /> = ({ opportunities, events, isL /> + {/* + * Erklaerung direkt unter den Kacheln: die Zahl der Bewerbungen ist + * regelmaessig hoeher als die Akquise-Liste lang ist, weil uebernommene + * Eintraege dort verschwinden. Ohne diesen Satz sieht das nach einem + * Fehler aus. + */} +

+ Bewerbungen zählt alle + Akquise-Einträge mit Funddatum im Zeitraum + {totals.converted > 0 && ( + <> + {' '} + - davon sind {totals.converted} zu + einer Opportunity geworden und stehen deshalb nicht mehr in der Akquise-Liste + + )} + . Ein von Hand gelöschter Eintrag zählt nicht mehr mit - ein Fehleintrag soll die + Zahlen nicht verfälschen. +

+ {/* Verlauf und Berater */}
= ({ points }) => { const [grown, setGrown] = useState(false); + const scrollRef = useRef(null); useEffect(() => { setGrown(false); @@ -137,6 +138,16 @@ export const SeriesChart: React.FC<{ points: SeriesPoint[] }> = ({ points }) => return () => clearTimeout(timer); }, [points]); + /* + * Passen nicht alle Abschnitte nebeneinander, wird das Ende gezeigt: bei 30 + * Tagen stehen die aktuellen Tage rechts, und die interessieren. Ohne das + * sieht man den Anfang des Zeitraums - haeufig lauter leere Tage. + */ + useEffect(() => { + const box = scrollRef.current; + if (box) box.scrollLeft = box.scrollWidth; + }, [points]); + const max = Math.max(1, ...points.flatMap((p) => [p.applications, p.converted, p.won])); // Runde Obergrenze, damit die Hilfslinien lesbare Werte tragen. const step = Math.max(1, Math.ceil(max / 4)); @@ -153,8 +164,8 @@ export const SeriesChart: React.FC<{ points: SeriesPoint[] }> = ({ points }) => ))}
-
-
+
+
{/* Werteachse */}
{[4, 3, 2, 1, 0].map((line) => (