diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx
index 937a8d0..6b6745b 100644
--- a/app/(auth)/login/page.tsx
+++ b/app/(auth)/login/page.tsx
@@ -157,7 +157,7 @@ function BrandPanel() {
- Die Organisation, so wie sie heute wirklich aussieht.
+ Alles im Blick. Alles Manner.
Stammdaten, Planstellen und Berichtslinien der Alpenwerk Industrie GmbH — jederzeit auch zu einem beliebigen
diff --git a/lib/reports.ts b/lib/reports.ts
index 3ca4f24..1cda040 100644
--- a/lib/reports.ts
+++ b/lib/reports.ts
@@ -1,6 +1,7 @@
import { ABSENCE_TYPES } from "./absence";
import { BEENDIGUNGSART_WERTE } from "./beendigung";
import { fmtName, todayIso, yearsBetweenIso } from "./format";
+import { HAY_GRADE_WERTE } from "./hay-grade";
import { WOCHENTAGE } from "./wochentage";
import type { EmploymentStatus, HistoryEventType, Weekday } from "./types";
@@ -235,20 +236,43 @@ export function groupKeyFor(e: ReportEmployee, dim: GroupDimension, lookups: Org
}
}
-function weekdayRank(key: string): number {
- const i = WOCHENTAGE.indexOf(key as Weekday);
- return i === -1 ? WOCHENTAGE.length : i;
+/**
+ * Dimensionen, deren Werte eine eigene Reihenfolge haben.
+ *
+ * Alles andere wird nach der Kennzahl sortiert, gross zuerst — bei „Bereich"
+ * oder „Standort" ist das die Antwort auf die Frage, die der Bericht stellt.
+ * Bei einer Leiter ist es keine: Montag vor Dienstag und HG09 vor HG10 sind
+ * die Reihenfolge, in der die Werte *sind*, und eine nach Häufigkeit
+ * umgestellte Leiter liest sich als Zufall.
+ *
+ * HG09 ist die unterste Stufe, HG20 die oberste. Der „Generic Grade" (`-`)
+ * steht vor allen: er ist keine Stufe, sondern ihr Fehlen, und vor der
+ * niedrigsten ist der Platz, an dem das am wenigsten nach einer Aussage
+ * aussieht.
+ */
+const EIGENE_REIHENFOLGE: Partial> = {
+ weekday: WOCHENTAGE,
+ paygrade: HAY_GRADE_WERTE,
+};
+
+function rang(dim: GroupDimension, key: string): number {
+ const liste = EIGENE_REIHENFOLGE[dim];
+ if (!liste) return 0;
+ const i = liste.indexOf(key);
+ // Unbekanntes hinten, nicht vorn: ein Wert, den die Liste nicht kennt, soll
+ // auffallen und nicht die Leiter anführen.
+ return i === -1 ? liste.length : i;
}
-function sortByWeekday(items: T[]): T[] {
- return [...items].sort((a, b) => weekdayRank(a.key) - weekdayRank(b.key));
+function sortiereNachReihenfolge(items: T[], dim: GroupDimension): T[] {
+ return [...items].sort((a, b) => rang(dim, a.key) - rang(dim, b.key));
}
// Reused by ReportsPageClient (split legend) and the report export route
-// (split columns) to render a `weekday` split chronologically rather than
+// (split columns) to render a split in the order its values have rather than
// in first-encountered order; a no-op for every other dimension.
export function sortKeysForDimension(keys: string[], dim: GroupDimension): string[] {
- return dim === "weekday" ? [...keys].sort((a, b) => weekdayRank(a) - weekdayRank(b)) : keys;
+ return EIGENE_REIHENFOLGE[dim] ? [...keys].sort((a, b) => rang(dim, a) - rang(dim, b)) : keys;
}
// Every dimension other than `weekday` is a strict single-key partition
@@ -326,11 +350,11 @@ export function aggregateReport(
value: measureValue(sRows, measure, asOf),
count: sRows.length,
}));
- row.split = split === "weekday" ? sortByWeekday(splitRows) : splitRows;
+ row.split = EIGENE_REIHENFOLGE[split] ? sortiereNachReihenfolge(splitRows, split) : splitRows;
}
rows.push(row);
}
- return group === "weekday" ? sortByWeekday(rows) : rows.sort((a, b) => b.value - a.value);
+ return EIGENE_REIHENFOLGE[group] ? sortiereNachReihenfolge(rows, group) : rows.sort((a, b) => b.value - a.value);
}
export function sumValues(rows: { value: number }[]): number {
diff --git a/tests/unit/reports.test.ts b/tests/unit/reports.test.ts
index 6421e1a..746e077 100644
--- a/tests/unit/reports.test.ts
+++ b/tests/unit/reports.test.ts
@@ -251,6 +251,30 @@ describe("aggregateReport", () => {
const rows = aggregateReport(employees, "headcount", "division", "weekday", lookups);
expect(rows[0].split?.map((s) => s.key)).toEqual(["Mo", "Fr"]);
});
+
+ it("sorts Hay-Grades aufsteigend statt nach Häufigkeit", () => {
+ // Die Leiter ist die Aussage: HG09 unten, HG20 oben. Nach Kennzahl
+ // sortiert stünde hier HG13 vorn, weil dort zufällig zwei Personen
+ // sitzen — und die Reihenfolge läse sich als Zufall.
+ const employees = [
+ emp({ id: "1", paygrade: "HG20" }),
+ emp({ id: "2", paygrade: "HG09" }),
+ emp({ id: "3", paygrade: "HG13" }),
+ emp({ id: "4", paygrade: "HG13" }),
+ emp({ id: "5", paygrade: "HG19P" }),
+ emp({ id: "6", paygrade: "HG19" }),
+ ];
+ const rows = aggregateReport(employees, "headcount", "paygrade", null, lookups);
+ expect(rows.map((r) => r.key)).toEqual(["HG09", "HG13", "HG19", "HG19P", "HG20"]);
+ });
+
+ it("stellt den Generic Grade vor die unterste Stufe", () => {
+ // Er ist keine Stufe, sondern ihr Fehlen. Zwischen HG13 und HG14 stünde
+ // er wie eine.
+ const employees = [emp({ id: "1", paygrade: "HG13" }), emp({ id: "2", paygrade: "-" })];
+ const rows = aggregateReport(employees, "headcount", "paygrade", null, lookups);
+ expect(rows.map((r) => r.key)).toEqual(["-", "HG13"]);
+ });
});
describe("deriveStatusAsOf", () => {