Files
dejanandClaude Opus 5 6a0b36ddcc Geviertstriche aus Text, Kommentaren und Doku entfernen
467 Stellen. Nicht durch einen anderen Strich ersetzt, sondern durch das
Satzzeichen, das an der jeweiligen Stelle richtig ist: Doppelpunkt, wo eine
Erlaeuterung folgt; Komma, wo der Satz weiterlaeuft; Punkt, wo zwei
Hauptsaetze aneinanderstiessen. Ein Halbgeviertstrich waere nur derselbe
Griff mit kuerzerem Strich gewesen.

Regelbasiert ersetzt und danach nachgeprueft, weil eine Regel allein nicht
reicht. Gefunden und einzeln korrigiert:

- Eingeschobene Saetze mit Strichpaar. Nur der oeffnende Strich passte ins
  Muster, der schliessende blieb als "—," stehen. Drei Stellen, jetzt mit
  Klammern oder als eigener Satz.

- Doppelte Doppelpunkte, wo beide Striche eines Paars zu Doppelpunkten wurden
  ("Globale Regeln: Schrift, Groessen, Abstaende: kommen aus HiveGrid").
  Sieben Stellen in Doku und Oberflaeche, jetzt in Klammern.

- Pfade, denen ein Komma statt eines Doppelpunkts folgte.

- Ausgerichtete Kommentartabellen, in denen der Strich eine Spalte war.

Nicht angefasst: drei Geviertstriche, die keine Prosa sind, sondern
Tabellenplatzhalter fuer "kein Wert" (Trichter, Auftraege, Kunden). Dort ist
der Strich die Angabe selbst; ihn zu entfernen liesse leere Zellen zurueck.

tsc, lint, build, verify:carbon und verify:layout laufen sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
2026-08-31 20:51:19 +02:00

159 lines
5.3 KiB
TypeScript

/**
* Deutsche Zahlen-, Waehrungs- und Datumsformatierung.
* Regeln aus UI_UX_GUIDELINES.md (Abschnitt 3, Zahlenformatierung):
* Punkt als Tausender-Trenner, Komma als Dezimaltrenner, grosse Zahlen abkuerzen.
*/
const NF = new Intl.NumberFormat("de-DE");
/** 12450 -> "12.450" · 1250000 -> "1,3 Mio." */
export function formatZahl(value: number, dezimalstellen = 0): string {
if (Math.abs(value) >= 1_000_000) {
return `${(value / 1_000_000).toFixed(1).replace(".", ",")} Mio.`;
}
return new Intl.NumberFormat("de-DE", {
minimumFractionDigits: dezimalstellen,
maximumFractionDigits: dezimalstellen,
}).format(value);
}
/** 8420 -> "8.420 €" · 245000 -> "245.000 €" (immer voll, fuer KPI-Werte) */
export function formatEuro(value: number): string {
return `${NF.format(Math.round(value))} €`;
}
/**
* Kompakte Waehrung fuer Achsen und Balken-Labels.
* 8420 -> "8,4 T€" · 245000 -> "245 T€" · 1250000 -> "1,25 Mio. €"
*/
export function formatEuroKompakt(value: number): string {
const abs = Math.abs(value);
if (abs >= 1_000_000) {
return `${(value / 1_000_000).toFixed(2).replace(".", ",")} Mio. €`;
}
if (abs >= 1_000) {
return `${(value / 1_000).toFixed(1).replace(".", ",")} T€`;
}
return `${NF.format(Math.round(value))} €`;
}
/**
* Noch kompakter für Achsenbeschriftungen: dort zählt jede Stelle,
* und Nachkommastellen sind bei einer Skala ohnehin überflüssig.
* 34000 -> "34 T€" · 1250000 -> "1,3 Mio. €"
*/
export function formatEuroAchse(value: number): string {
// Die Nulllinie traegt keine Einheit: "0 €" neben "10 T€" liest sich wie ein
// Einheitenwechsel mitten in der Skala.
if (value === 0) return "0";
const abs = Math.abs(value);
if (abs >= 1_000_000) return `${(value / 1_000_000).toFixed(1).replace(".", ",")} Mio. €`;
if (abs >= 1_000) return `${Math.round(value / 1_000)} T€`;
return `${NF.format(Math.round(value))} €`;
}
/**
* Achsenformat, einmal aus dem Maximum gewaehlt.
*
* formatEuroAchse entscheidet die Einheit pro Wert, dadurch steht "0 €"
* neben "10 T€". Eine Achse traegt aber genau eine Einheit.
*/
export function achsenFormat(max: number): (wert: number) => string {
if (max >= 1_000_000) {
return (wert) =>
wert === 0 ? "0" : `${(wert / 1_000_000).toFixed(1).replace(".", ",")} Mio. €`;
}
if (max >= 1_000) {
return (wert) => (wert === 0 ? "0" : `${NF.format(Math.round(wert / 1_000))} T€`);
}
return (wert) => (wert === 0 ? "0" : formatEuro(wert));
}
/** 45.32 -> "45,3 %" */
export function formatProzent(value: number, dezimalstellen = 1): string {
return `${value.toFixed(dezimalstellen).replace(".", ",")} %`;
}
const NF1 = new Intl.NumberFormat("de-DE", {
minimumFractionDigits: 1,
maximumFractionDigits: 1,
});
/** 2.4 -> "2,4 Tage" · 1234.5 -> "1.234,5 Tage" */
export function formatTage(value: number): string {
return `${NF1.format(value)} Tage`;
}
/** 2812.5 -> "2.812,5 h" */
export function formatStunden(value: number): string {
return `${NF1.format(value)} h`;
}
/**
* Ganze Tage mit korrektem Numerus: "1 Tag", "3 Tage".
* Ohne das steht in der Terminspalte "1 Tage ueber".
*/
export function tageLabel(anzahl: number, suffix = ""): string {
const wort = Math.abs(anzahl) === 1 ? "Tag" : "Tage";
return `${NF.format(anzahl)} ${wort}${suffix ? ` ${suffix}` : ""}`;
}
/** Dativform fuer "seit …": "seit 1 Tag" / "seit 99 Tagen". */
export function tageLabelDativ(anzahl: number): string {
const wort = Math.abs(anzahl) === 1 ? "Tag" : "Tagen";
return `${NF.format(anzahl)} ${wort}`;
}
/** "2026-07-30" -> "30.07.2026" */
export function formatDatum(iso: string): string {
const d = new Date(iso);
return d.toLocaleDateString("de-DE", {
day: "2-digit",
month: "2-digit",
year: "numeric",
});
}
/** "2026-07-30" -> "30.07." */
export function formatDatumKurz(iso: string): string {
const d = new Date(iso);
return d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit" });
}
/** "2026-07" -> "Jul 26" */
export function formatMonatKurz(iso: string): string {
const [jahr, monat] = iso.split("-");
const namen = ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"];
return `${namen[Number(monat) - 1]} ${jahr.slice(2)}`;
}
/** Tage zwischen heute und einem ISO-Datum (positiv = in der Vergangenheit). */
export function tageSeit(iso: string, referenz: Date): number {
const diff = referenz.getTime() - new Date(iso).getTime();
return Math.floor(diff / 86_400_000);
}
/**
* Trend zwischen aktueller und vorheriger Periode.
* Gibt Prozentpunkte und Richtung zurueck; < 0,5 % gilt als unveraendert.
*/
export function berechneTrend(
aktuell: number,
vorher: number
): { wert: number; richtung: "up" | "down" | "flat" } {
if (vorher === 0) {
return aktuell > 0 ? { wert: 100, richtung: "up" } : { wert: 0, richtung: "flat" };
}
const veraenderung = ((aktuell - vorher) / vorher) * 100;
if (Math.abs(veraenderung) < 0.5) return { wert: 0, richtung: "flat" };
return {
wert: Math.abs(veraenderung),
richtung: veraenderung > 0 ? "up" : "down",
};
}
/** Text auf maxLaenge kuerzen, Ellipse anhaengen. */
export function kuerze(text: string, maxLaenge: number): string {
return text.length <= maxLaenge ? text : `${text.slice(0, maxLaenge - 1)}…`;
}