Files
Dashboard/features/dashboard/pages/TeamSeite.tsx
T
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

211 lines
6.9 KiB
TypeScript

"use client";
import React from "react";
import { PageContainer, Raster } from "../components/layout/PageContainer";
import { KPICard } from "../components/cards/KPICard";
import { ChartCard } from "../components/cards/ChartCard";
import { AuslastungsBalken } from "../components/charts/AuslastungsBalken";
import { BalkenHorizontal } from "../components/charts/BalkenHorizontal";
import { Donut } from "../components/charts/Donut";
import { DataTable, type Spalte } from "../components/tables/DataTable";
import { formatEuro, formatEuroKompakt, formatProzent, formatStunden, formatZahl } from "../utils/formatters";
import { SeitenZustand } from "../components/layout/SeitenZustand";
import type { VMState } from "../view-models";
import type { TeamDaten, TeamZeile } from "../view-models/team.vm";
export function TeamSeite({ vm }: { vm: VMState<TeamDaten> }) {
if (vm.status !== "ready") {
return <SeitenZustand vm={vm} />;
}
const d = vm.data;
const spalten: Spalte<TeamZeile>[] = [
{
key: "name",
titel: "Mitarbeiter",
breite: "26%",
zelle: (k) => (
<div className="flex items-center gap-2.5">
<span className="flex h-7 w-7 flex-none items-center justify-center rounded-full bg-neutral-100 text-[11px] font-semibold text-text-neutral-secondary">
{k.mitarbeiter.kuerzel}
</span>
<div className="flex flex-col">
<span className="font-medium">{k.mitarbeiter.name}</span>
<span className="text-[11px] text-text-neutral-tertiary">
{k.mitarbeiter.rolle} · {k.mitarbeiter.gewerk}
</span>
</div>
</div>
),
sortWert: (k) => k.mitarbeiter.name,
},
{
key: "status",
titel: "Status",
zelle: (k) =>
k.mitarbeiter.status === "aktiv" ? (
<span className="rounded-full bg-[#defbe6] px-2 py-0.5 text-[11px] font-medium text-[#0e6027]">
aktiv
</span>
) : (
<span className="rounded-full bg-neutral-100 px-2 py-0.5 text-[11px] font-medium text-neutral-600">
inaktiv
</span>
),
sortWert: (k) => k.mitarbeiter.status,
},
{
key: "stunden",
titel: "Stunden",
numerisch: true,
zelle: (k) => formatStunden(k.stunden),
sortWert: (k) => k.stunden,
},
{
key: "produktiv",
titel: "davon produktiv",
numerisch: true,
zelle: (k) => formatStunden(k.produktivStunden),
sortWert: (k) => k.produktivStunden,
},
{
key: "auslastung",
titel: "Auslastung",
numerisch: true,
zelle: (k) => (
<span
className={
k.auslastungProzent > 95
? "font-medium text-[#8a3800]"
: k.auslastungProzent < 70
? "text-[#00539a]"
: "font-medium text-[#0e6027]"
}
>
{formatProzent(k.auslastungProzent, 0)}
</span>
),
sortWert: (k) => k.auslastungProzent,
},
{
key: "abgeschlossen",
titel: "Abgeschlossen",
numerisch: true,
zelle: (k) => formatZahl(k.abgeschlossen),
sortWert: (k) => k.abgeschlossen,
},
{
key: "umsatz",
titel: "Auftragswert",
numerisch: true,
zelle: (k) => <span className="font-medium">{formatEuro(k.umsatz)}</span>,
sortWert: (k) => k.umsatz,
},
];
return (
<>
<PageContainer>
<Raster spalten={5}>
<KPICard
label="Im Einsatz"
wert={formatZahl(d.aktive.length)}
zusatz={`von ${formatZahl(d.mitarbeiterGesamt)} gewerblich Beschäftigten`}
icon="HardHat"
bezug="stichtag"
/>
<KPICard
label="Ø Auslastung"
wert={formatProzent(d.durchschnittAuslastung, 0)}
zusatz="Produktive Stunden zu verfügbaren"
icon="Activity"
akzent={d.durchschnittAuslastung > 95 ? "warnung" : "positiv"}
/>
<KPICard
label="Produktivquote"
wert={formatProzent(d.produktivQuote, 0)}
zusatz="Anteil auftragsbezogener Stunden"
icon="Target"
/>
<KPICard
label="Erfasste Stunden"
wert={formatStunden(d.stundenGesamt)}
zusatz={`${formatStunden(d.produktivGesamt)} auf Aufträge gebucht`}
icon="Clock"
/>
<KPICard
label="Abwesenheit"
wert={formatStunden(d.abwesenheitStunden)}
zusatz="Urlaub, Krankheit, Fortbildung"
icon="CalendarOff"
richtungGut="negativ"
/>
</Raster>
<ChartCard
id="team-auslastung"
titel="Auslastung je Mitarbeiter"
untertitel="Produktive Stunden gegen vertraglich verfügbare Stunden"
hinweis="Werte über 100 % bedeuten Überstunden. Dauerhaft über 95 % ist ein Warnsignal für Terminrisiken."
>
<AuslastungsBalken
zeilen={d.aktive.map((k) => ({
name: k.mitarbeiter.name,
wert: k.auslastungProzent,
zusatz: `${k.mitarbeiter.rolle} · ${k.mitarbeiter.gewerk}`,
}))}
maxZeilen={12}
/>
</ChartCard>
<div className="grid grid-cols-1 items-start gap-4 lg:grid-cols-2">
<ChartCard
id="team-abschluesse"
titel="Abschlüsse je Mitarbeiter"
untertitel="Anzahl im gewählten Zeitraum, dahinter der zugehörige Auftragswert"
hinweis="Der Balken zeigt die Anzahl, sie ist die vergleichbare Größe. Der Auftragswert steht daneben, ist aber ausdrücklich kein Leistungsvergleich: Ein Wärmepumpen-Auftrag ist um ein Vielfaches größer als eine Wartung."
>
<BalkenHorizontal
daten={d.aktive.map((k) => ({
name: k.mitarbeiter.name,
value: k.abgeschlossen,
zweitwert: k.umsatz,
}))}
formatWert={(w) => formatZahl(w)}
formatZweitwert={formatEuroKompakt}
maxBalken={8}
/>
</ChartCard>
<ChartCard
id="team-abwesenheit"
titel="Abwesenheiten"
untertitel="Stunden im gewählten Zeitraum"
>
<Donut
daten={d.abwesenheitNach}
formatWert={(w) => formatStunden(w)}
hoehe={210}
mitteLabel="Stunden"
/>
</ChartCard>
</div>
<ChartCard
id="team-tabelle"
titel="Mitarbeiterübersicht"
untertitel="Alle gewerblich Beschäftigten mit Kennzahlen"
>
<DataTable
zeilen={d.zeilen}
spalten={spalten}
proSeite={10}
suchFelder={(k) => `${k.mitarbeiter.name} ${k.mitarbeiter.rolle} ${k.mitarbeiter.gewerk}`}
/>
</ChartCard>
</PageContainer>
</>
);
}