feat(team): Oberfläche der Teamübersicht

Vier Seiten, eingebettet in die bestehende App-Shell — keine zweite Sidebar,
keine zweite Kopfzeile, kein eigenes Property-On-Branding:

- Teamübersicht: Auswertung (drei Kennzahlen, gemeinsam filterbar nach Zeitraum,
  Bereich und Mitarbeiter), Kernteam, Kanäle & Systeme — in dieser Reihenfolge
- Bearbeitungsverlauf: zwei Reiter, neun Filterdimensionen, Detail-Drawer mit
  Ergebnis, Eingabedaten, Verarbeitungsschritten, Fundstellen, Nachrichtenverlauf
  und Aktionshistorie; Freigeben, Anpassen, Zurückweisen, Rückfrage beantworten
- Personalverwaltung: dreigeteilt (Hauptnavigation, Agentenliste, Dossier) mit
  fünf Reitern — Aufgaben, Kanäle, Systeme, Einstellungen, Protokoll
- Kanäle & Systeme: neun Verbindungen, mehrstufiger Assistent, ERP-Auswahl,
  deterministischer Verbindungstest

Navigation: NavItem um `children` erweitert, "Teamübersicht" steht exakt zwischen
"Meine Objekte" und "Reminder Manager", die drei Subreiter bleiben eingerückt
sichtbar, solange man im Funktionsbereich ist.

Subreiter sind echte Routen, keine lokalen Tabs. Die App kannte bisher keine
Feature-Route mit eigenem Outlet und baut Tabs sonst über lokalen useState — hier
trug das nicht: die Subreiter müssen in der Sidebar stehen und deep-linkbar sein.
Die Routen sind flache Geschwister, kein Outlet-Baum.

Freigabepflichtige Aktionen laufen über einen Bestätigungsdialog mit sichtbarer
Konsequenz; die 1-Klick-Bestätigung ist die fachlich vorgesehene Ausnahme. Jeder
Status trägt neben der Farbe immer einen Text.

Null rohe Hex-Werte: check:tokens bleibt unverändert bei 2249. Der in der
Spezifikation genannte dunkelblaue Banner "Heute geleistet …" existiert in dieser
Codebasis nicht — er wird deshalb auch nicht erzeugt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-08-03 16:56:00 +02:00
parent 52dc7de25e
commit 78e2dab655
39 changed files with 5638 additions and 2 deletions
+99
View File
@@ -0,0 +1,99 @@
import { useCallback } from 'react'
import { useNavigate } from 'react-router'
import { Box, Button } from '@mui/material'
import { RotateCcw } from 'lucide-react'
import {
TeamPageHeader,
TeamSectionHeader,
TeamKpiSection,
AgentCard,
ConnectionSummaryList,
} from '../../components/team'
import { CardSkeleton, ErrorState } from '../../components/ui'
import { useTeamAgents } from '../../hooks/useTeamAgents'
import { useAgentConnections } from '../../hooks/useAgentConnections'
import { useResetTeamDemo } from '../../hooks/useTeamDemo'
import { ROUTES } from '../../lib/constants'
/**
* Startseite des Property-On-Bereichs.
* Verbindliche Reihenfolge: Auswertung, Kernteam, Kanäle & Systeme (§4.1).
*/
export default function Teamuebersicht() {
const navigate = useNavigate()
const { data: agents = [], isLoading, isError, refetch } = useTeamAgents()
const { data: connections = [] } = useAgentConnections()
const resetDemo = useResetTeamDemo()
const openAgent = useCallback(
(agentId: string) => navigate(`${ROUTES.SUPPLY.TEAM_PERSONNEL}/${agentId}`),
[navigate],
)
return (
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
<TeamPageHeader
title="Teamübersicht"
description="Sieben digitale Mitarbeiter arbeiten in den Systemen der Bewirtschaftung. Sie führen aus, Sie entscheiden."
actions={
<Button
size="small"
variant="outlined"
startIcon={<RotateCcw size={14} />}
disabled={resetDemo.isPending}
onClick={() => resetDemo.mutate()}
sx={{ textTransform: 'none', fontWeight: 600 }}
>
{resetDemo.isPending ? 'Wird zurückgesetzt …' : 'Demo zurücksetzen'}
</Button>
}
/>
<Box sx={{ flex: 1, overflowY: 'auto', px: 3, py: 3 }}>
{isError ? (
<ErrorState message="Das digitale Team konnte nicht geladen werden." onRetry={() => refetch()} />
) : (
<>
<TeamKpiSection agents={agents} />
<Box component="section" sx={{ mb: 4 }}>
<TeamSectionHeader
title="Kernteam"
description="Alle sieben digitalen Mitarbeiter mit Zuständigkeit und aktueller Kennzahl."
quickLink={{ label: 'Zur Personalverwaltung', to: ROUTES.SUPPLY.TEAM_PERSONNEL }}
/>
<Box
sx={{
display: 'grid',
gap: 1.5,
gridTemplateColumns: {
xs: '1fr',
sm: 'repeat(2, 1fr)',
lg: 'repeat(3, 1fr)',
xl: 'repeat(4, 1fr)',
},
}}
>
{isLoading
? [0, 1, 2, 3].map((i) => <CardSkeleton key={i} />)
: agents.map((agent) => (
<AgentCard key={agent.id} agent={agent} onOpen={openAgent} />
))}
</Box>
</Box>
<Box component="section">
<TeamSectionHeader
title="Kanäle & Systeme"
description="Womit das digitale Team heute verbunden ist."
quickLink={{ label: 'Alle Kanäle & Systeme', to: ROUTES.SUPPLY.TEAM_CONNECTIONS }}
/>
<ConnectionSummaryList connections={connections} agents={agents} />
</Box>
</>
)}
</Box>
</Box>
)
}