From 408b2c4c7a9ebc87804b771a2179250f6aa06e94 Mon Sep 17 00:00:00 2001 From: patrick Date: Tue, 23 Jun 2026 13:27:48 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20agent-04=20=E2=80=93=20Team-Dashboard?= =?UTF-8?q?=20f=C3=BCr=20Manager/HR=20im=20Frontend=20angebunden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Das Backend liefert /dashboard/team (team_dashboard) bereits, die DashboardPage lud aber nur /dashboard/me und /dashboard/company → Manager/HR sahen nur das Mitarbeiter-Dashboard. Jetzt: - Manager-Rollen (MANAGER/HR/COMPANY_ADMIN/SUPER_ADMIN) laden /dashboard/team. - Neue Sektion "Team heute": KPIs (anwesend / abwesend / nicht eingestempelt / Mitarbeiterzahl), ausstehende Genehmigungen als Quick-Links, Team-Tabelle (Status-Badge, Einstempelzeit, Stunden heute). Reine Frontend-Anbindung – Backend, Reports und Exports (PDF/CSV/XLSX) waren bereits vollständig (24 Report-Tests grün). Co-Authored-By: Claude Opus 4.8 --- frontend/src/pages/DashboardPage.tsx | 95 ++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 71bf558..419c49b 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -49,6 +49,25 @@ interface CompanyDashboard { upcoming_absences: UpcomingAbsence[] } +interface TeamMemberStatus { + user_id: string + user_name: string + department: string | null + status: string // "present" | "on_leave" | "absent" + absence_type: string | null + time_in: string | null + hours_today: number | null +} + +interface TeamDashboard { + present_count: number + on_leave_count: number + absent_count: number + pending_time_approvals: number + pending_absence_approvals: number + members: TeamMemberStatus[] +} + function StatCard({ label, value, sub, highlight, }: { @@ -75,6 +94,7 @@ export function DashboardPage() { const { logout } = useAuth() const [user, setUser] = useState(null) const [emp, setEmp] = useState(null) + const [team, setTeam] = useState(null) const [comp, setComp] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState('') @@ -89,7 +109,15 @@ export function DashboardPage() { setUser(me) setEmp(empDash) + const managerRoles = ['MANAGER', 'HR', 'COMPANY_ADMIN', 'SUPER_ADMIN'] const adminRoles = ['COMPANY_ADMIN', 'SUPER_ADMIN'] + if (managerRoles.includes(me.role)) { + try { + setTeam(await api.get('/dashboard/team')) + } catch { + // kein Manager-Zugriff – still ignorieren + } + } if (adminRoles.includes(me.role)) { try { setComp(await api.get('/dashboard/company')) @@ -203,6 +231,73 @@ export function DashboardPage() { )} + {/* Team-Dashboard (Manager/HR) */} + {team && ( +
+

Team heute

+ +
+ + + 0 ? 'yellow' : undefined} /> + +
+ + {/* Ausstehende Genehmigungen */} + {(team.pending_time_approvals > 0 || team.pending_absence_approvals > 0) && ( +
+ {team.pending_time_approvals > 0 && ( + + ⏳ {team.pending_time_approvals} Zeiteinträge warten auf Genehmigung → + + )} + {team.pending_absence_approvals > 0 && ( + + ⏳ {team.pending_absence_approvals} Abwesenheitsanträge warten auf Genehmigung → + + )} +
+ )} + + {team.members.length > 0 && ( +
+
+ + + + {['Mitarbeiter', 'Abteilung', 'Status', 'Seit', 'Heute'].map(h => ( + + ))} + + + + {team.members.map(m => { + const badge = m.status === 'present' + ? { c: 'bg-green-100 text-green-700', t: 'Anwesend' } + : m.status === 'on_leave' + ? { c: 'bg-blue-100 text-blue-700', t: m.absence_type ?? 'Abwesend' } + : { c: 'bg-gray-100 text-gray-500', t: 'Nicht da' } + return ( + + + + + + + + ) + })} + +
{h}
{m.user_name}{m.department ?? '—'} + {badge.t} + {m.time_in?.slice(0, 5) ?? '—'}{m.hours_today != null ? `${m.hours_today.toFixed(1)} h` : '—'}
+
+
+ )} +
+ )} + {/* Unternehmens-Dashboard (nur Admin) */} {comp && (