diff --git a/README.md b/README.md index 341a35a..e313213 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - Business logic: `TaskService` + models in `backend/tasks.py` - Frontend: React 18, Vite, FluentUI components, feature-first structure under `frontend/src/features` - Tests: Playwright E2E (`tests/e2e/app.spec.js`) - +test ## Documentation All deep-dive guides now live under `docs/` for easier discovery: diff --git a/frontend/package.json b/frontend/package.json index f5ef78e..08c02c4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,8 @@ "@fluentui/react-icons": "^2.0.239", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^7.9.6" + "react-router-dom": "^7.9.6", + "recharts": "^3.5.0" }, "devDependencies": { "@playwright/test": "^1.42.1", @@ -24,4 +25,4 @@ "@vitejs/plugin-react": "^4.2.1", "vite": "^5.1.0" } -} \ No newline at end of file +} diff --git a/frontend/src/features/dashboard/Dashboard.jsx b/frontend/src/features/dashboard/Dashboard.jsx index 427567d..bc2eceb 100644 --- a/frontend/src/features/dashboard/Dashboard.jsx +++ b/frontend/src/features/dashboard/Dashboard.jsx @@ -2,7 +2,7 @@ * Dashboard Component * * Displays real-time server information using Server-Sent Events - * Demonstrates FluentUI Card and Text components + * Demonstrates FluentUI Card and Text components with colorful metrics and charts */ import { useEffect, useState } from 'react' @@ -13,9 +13,37 @@ import { makeStyles, tokens, Spinner, + Badge, } from '@fluentui/react-components' -import { Clock24Regular, CalendarLtr24Regular } from '@fluentui/react-icons' +import { + Clock24Regular, + CalendarLtr24Regular, + ChartMultiple24Regular, + DataUsage24Regular, + People24Regular, + Checkmark24Regular, + ArrowUp24Regular, + Server24Regular, +} from '@fluentui/react-icons' import { connectToTimeStream, getCurrentDate } from '../../services/api' +import { + LineChart, + Line, + AreaChart, + Area, + BarChart, + Bar, + PieChart, + Pie, + Cell, + ResponsiveContainer, + XAxis, + YAxis, + Tooltip, + Legend, + RadialBarChart, + RadialBar, +} from 'recharts' const useStyles = makeStyles({ dashboard: { @@ -27,6 +55,31 @@ const useStyles = makeStyles({ card: { padding: tokens.spacingVerticalL, }, + cardSuccess: { + padding: tokens.spacingVerticalL, + backgroundColor: tokens.colorPaletteGreenBackground2, + borderLeft: `4px solid ${tokens.colorPaletteGreenBorder2}`, + }, + cardWarning: { + padding: tokens.spacingVerticalL, + backgroundColor: tokens.colorPaletteYellowBackground2, + borderLeft: `4px solid ${tokens.colorPaletteYellowBorder2}`, + }, + cardInfo: { + padding: tokens.spacingVerticalL, + backgroundColor: tokens.colorPaletteBlueBorder1, + borderLeft: `4px solid ${tokens.colorPaletteBlueBorder2}`, + }, + cardPurple: { + padding: tokens.spacingVerticalL, + backgroundColor: tokens.colorPalettePurpleBackground2, + borderLeft: `4px solid ${tokens.colorPalettePurpleBorder2}`, + }, + cardRed: { + padding: tokens.spacingVerticalL, + backgroundColor: tokens.colorPaletteRedBackground2, + borderLeft: `4px solid ${tokens.colorPaletteRedBorder2}`, + }, timeDisplay: { fontSize: '48px', fontWeight: 'bold', @@ -37,6 +90,15 @@ const useStyles = makeStyles({ fontSize: '24px', color: tokens.colorNeutralForeground2, }, + metricValue: { + fontSize: '36px', + fontWeight: 'bold', + color: tokens.colorBrandForeground1, + }, + metricChange: { + fontSize: '14px', + fontWeight: 'semibold', + }, label: { fontSize: '14px', color: tokens.colorNeutralForeground3, @@ -47,8 +109,48 @@ const useStyles = makeStyles({ flexDirection: 'column', gap: tokens.spacingVerticalM, }, + chartContainer: { + height: '200px', + marginTop: tokens.spacingVerticalM, + }, + metricRow: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, }) +// Sample data generators +const generateWeeklyData = () => { + const days = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'] + return days.map((day) => ({ + name: day, + wert: Math.floor(Math.random() * 100) + 50, + ziel: 80, + })) +} + +const generateMonthlyData = () => { + const months = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun'] + return months.map((month) => ({ + name: month, + umsatz: Math.floor(Math.random() * 5000) + 3000, + kosten: Math.floor(Math.random() * 3000) + 1500, + })) +} + +const generateCategoryData = () => [ + { name: 'Frontend', value: 35, color: '#0078d4' }, + { name: 'Backend', value: 28, color: '#50e6ff' }, + { name: 'DevOps', value: 20, color: '#00b7c3' }, + { name: 'Design', value: 17, color: '#8764b8' }, +] + +const generatePerformanceData = () => [ + { name: 'Performance', value: 85, fill: '#10b981' }, + { name: 'Verbleibend', value: 15, fill: '#e5e7eb' }, +] + export default function Dashboard() { const styles = useStyles() const [liveTime, setLiveTime] = useState(null) @@ -56,6 +158,22 @@ export default function Dashboard() { const [isConnected, setIsConnected] = useState(false) const [error, setError] = useState(null) + // Fictional metrics state + const [weeklyData] = useState(generateWeeklyData()) + const [monthlyData] = useState(generateMonthlyData()) + const [categoryData] = useState(generateCategoryData()) + const [performanceData] = useState(generatePerformanceData()) + const [systemMetrics] = useState({ + activeUsers: Math.floor(Math.random() * 500) + 1200, + userChange: '+12%', + completedTasks: Math.floor(Math.random() * 200) + 450, + taskChange: '+8%', + serverUptime: '99.9%', + uptimeChange: '+0.1%', + avgResponseTime: Math.floor(Math.random() * 50) + 120, + responseChange: '-15ms', + }) + // Fetch initial server date useEffect(() => { getCurrentDate() @@ -82,72 +200,222 @@ export default function Dashboard() { return (
- + {/* Active Users Metric */} + - - Live Server Time + + Aktive Benutzer
} - description={ - isConnected ? ( - Connected via Server-Sent Events - ) : ( - Connecting... - ) + description={Live} + /> +
+
+
{systemMetrics.activeUsers.toLocaleString()}
+ + {systemMetrics.userChange} + +
+
+ + + + + + + + + +
+
+ + + {/* Task Completion Metric */} + + + + Erledigte Aufgaben + } + description={Heute} />
- {liveTime ? ( - <> -
- {liveTime.time} -
-
{liveTime.date}
- - ) : ( - - )} +
+
{systemMetrics.completedTasks}
+ + {systemMetrics.taskChange} + +
+
+ + + + + + + + +
+
+
+ + {/* Server Uptime */} + + + + Server Verfügbarkeit + + } + description={30 Tage} + /> +
+
+
{systemMetrics.serverUptime}
+ + {systemMetrics.uptimeChange} + +
+
+ + + + + +
+
+
+ + {/* Response Time */} + + + + Ø Antwortzeit + + } + description={Millisekunden} + /> +
+
+
{systemMetrics.avgResponseTime}ms
+ + {systemMetrics.responseChange} + +
+
+ + + + + + + + +
+
+
+ + {/* Monthly Revenue/Cost Chart */} + + + + Umsatz & Kosten Übersicht + + } + description={Letzte 6 Monate} + /> +
+
+ + + + + + + + + + +
+
+
+ + {/* Category Distribution */} + + + + Projektverteilung + + } + description={Nach Kategorie} + /> +
+
+ + + `${name}: ${value}%`} + > + {categoryData.map((entry, index) => ( + + ))} + + + + +
+ {/* Live Server Time */} - - Server Date + + Live Server Zeit } - description={Current date from API} + description={ + isConnected ? ( + Verbunden + ) : ( + Verbinde... + ) + } />
- {serverDate ? ( + {liveTime ? ( <> -
-
Date
-
- {serverDate.date} -
-
-
-
Time
-
- {serverDate.time} -
-
-
-
ISO 8601
- - {serverDate.datetime} - +
+ {liveTime.time}
+
{liveTime.date}
- ) : error ? ( - Error: {error} ) : ( - + )}