From 09c7c6a6de92ded9de2094f58bde27d130948b74 Mon Sep 17 00:00:00 2001 From: "Dennis (via Claude+Gemma)" Date: Sat, 23 May 2026 06:47:56 +0200 Subject: [PATCH] feat(kpi-comparison): Dashboard KPI-Karten mit Vergleich zur Vorwoche [tsc:fail] --- .phase13-state.json | 5 +- GENERATION_LOG.md | 17 ++++ apps/web/src/pages/Dashboard.tsx | 153 +++++++++++++++++++++---------- 3 files changed, 126 insertions(+), 49 deletions(-) diff --git a/.phase13-state.json b/.phase13-state.json index f05dd93..183ebf9 100644 --- a/.phase13-state.json +++ b/.phase13-state.json @@ -1,10 +1,11 @@ { "completed_features": [], - "current_feature": "aria-improvements", + "current_feature": "kpi-comparison", "started_at": "2026-05-23T06:42:42.473991", "attempted_features": [ "undo-toast", "breadcrumb-navigation", - "in-app-changelog" + "in-app-changelog", + "aria-improvements" ] } \ No newline at end of file diff --git a/GENERATION_LOG.md b/GENERATION_LOG.md index fa9cdab..46cbf6b 100644 --- a/GENERATION_LOG.md +++ b/GENERATION_LOG.md @@ -1635,3 +1635,20 @@ src/index.ts(27,25): error TS2769: No overload matches this call. Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTy +- `06:46:21` **INFO** Committed feature aria-improvements +- `06:46:22` **INFO** Pushed: rc=0 + +## Phase-3 Feature: kpi-comparison (2026-05-23 06:46:22) + +- `06:46:22` **INFO** Description: Dashboard KPI-Karten mit Vergleich zur Vorwoche +- `06:46:22` **INFO** Generating apps/web/src/pages/Dashboard.tsx (ERWEITERT — behalte alles (Stats-Cards, Chart, ActivityFeed, Export-Bu…) +- `06:47:55` **INFO** wrote 10327 chars in 93.0s (attempt 1) +- `06:47:55` **INFO** Running tsc --noEmit on api… +- `06:47:56` **WARN** tsc errors: +src/index.ts(27,25): error TS2769: No overload matches this call. + Overload 1 of 3, '(plugin: FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. + Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginCallback<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. + Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTypeProvider>, opts: { ...; }, done: (err?: Error | undefined) => void): void'. + Overload 2 of 3, '(plugin: FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>, opts?: FastifyRegisterOptions<...> | undefined): FastifyInstance<...> & PromiseLike<...>', gave the following error. + Argument of type 'Promise' is not assignable to parameter of type 'FastifyPluginAsync<{ limits: { fileSize: number; }; }, RawServerDefault, FastifyTypeProvider, FastifyBaseLogger>'. + Type 'Promise' provides no match for the signature '(instance: FastifyInstance, FastifyBaseLogger, FastifyTy diff --git a/apps/web/src/pages/Dashboard.tsx b/apps/web/src/pages/Dashboard.tsx index 8099594..f558dfa 100644 --- a/apps/web/src/pages/Dashboard.tsx +++ b/apps/web/src/pages/Dashboard.tsx @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query" import { useNavigate } from "@tanstack/react-router" import { useMemo } from "react" import { api } from "../lib/api" -import { Clock, Calendar, FolderKanban, LogOut, Activity, Download } from "lucide-react" +import { Clock, Calendar, FolderKanban, LogOut, Activity, Download, TrendingUp, TrendingDown } from "lucide-react" import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts" function ActivityFeed() { @@ -11,9 +11,11 @@ function ActivityFeed() { queryFn: () => api.listTimeEntries({ limit: 10 }) }) - if (isLoading) return
- {[...Array(5)].map((_, i) =>
)} -
+ if (isLoading) return ( +
+ {[...Array(5)].map((_, i) =>
)} +
+ ) return (
@@ -41,8 +43,9 @@ export default function Dashboard() { const today = new Date().toISOString().split("T")[0] - const getStartOfWeek = () => { + const getStartOfWeek = (offsetWeeks = 0) => { const d = new Date() + d.setDate(d.getDate() - (offsetWeeks * 7)) const day = d.getDay() const diff = d.getDate() - day + (day === 0 ? -6 : 1) const start = new Date(d.setDate(diff)) @@ -59,7 +62,8 @@ export default function Dashboard() { return dates } - const weekStart = getStartOfWeek() + const weekStart = getStartOfWeek(0) + const prevWeekStart = getStartOfWeek(1) const last7DaysDates = getLast7Days() const oldestDate = last7DaysDates[0] @@ -83,6 +87,11 @@ export default function Dashboard() { queryFn: () => api.listTimeEntries({ from: oldestDate }) }) + const { data: comparisonEntries } = useQuery({ + queryKey: ["timeEntries", "comparison"], + queryFn: () => api.listTimeEntries({ from: prevWeekStart }) + }) + const handleLogout = () => { api.logout() navigate({ to: "/login" }) @@ -94,6 +103,28 @@ export default function Dashboard() { window.open(`/api/reports/pdf?from=${from}&to=${to}`, '_blank') } + const stats = useMemo(() => { + const currentWeekHours = weekEntries?.reduce((acc, curr) => acc + (parseFloat(curr.duration) || 0), 0) || 0 + + const prevWeekHours = comparisonEntries?.reduce((acc, curr) => { + const entryDate = new Date(curr.date) + const start = new Date(prevWeekStart) + const end = new Date(weekStart) + if (entryDate >= start && entryDate < end) { + return acc + (parseFloat(curr.duration) || 0) + } + return acc + }, 0) || 0 + + const delta = prevWeekHours === 0 ? 0 : ((currentWeekHours - prevWeekHours) / prevWeekHours) * 100 + + return { + currentWeekHours: currentWeekHours.toFixed(2), + delta: delta.toFixed(1), + isPositive: delta >= 0 + } + }, [weekEntries, comparisonEntries]) + const chartData = useMemo(() => { if (!chartEntries) return [] @@ -120,15 +151,9 @@ export default function Dashboard() { ) } - const calcHours = (entries: any[]) => - entries?.reduce((acc, curr) => acc + (parseFloat(curr.duration) || 0), 0) || 0 - - const todayH = calcHours(todayEntries || []) - const weekH = calcHours(weekEntries || []) - return (
-
+

Dashboard

Willkommen zurück, {user?.name}

@@ -136,70 +161,104 @@ export default function Dashboard() {
-
+
-
-
- +
+
+
+ +
+ Heute
-
-

Heute

-

{todayH.toFixed(2)}h

+
+ + {todayEntries?.reduce((acc, curr) => acc + (parseFloat(curr.duration) || 0), 0).toFixed(2)}h + +
+ {stats.isPositive ? : } + + vs. Vorwoche: {stats.isPositive ? "+" : ""}{stats.delta}% + +
-
-
- + +
+
+
+ +
+ Diese Woche
-
-

Diese Woche

-

{weekH.toFixed(2)}h

+
+ {stats.currentWeekHours}h +
+ {stats.isPositive ? : } + + vs. Vorwoche: {stats.isPositive ? "+" : ""}{stats.delta}% + +
-
-
- + +
+
+
+ +
+ Aktive Projekte
-
-

Aktive Projekte

-

4

+
+ + {new Set(weekEntries?.map(e => e.projectId)).size} + +
+ Stabil im Vergleich zur Vorwoche +
-
+

Stundenverlauf (7 Tage)

-
+
- - - - + + +
-
+