diff --git a/frontend/src/pages/Analytics.jsx b/frontend/src/pages/Analytics.jsx new file mode 100644 index 0000000..bf70523 --- /dev/null +++ b/frontend/src/pages/Analytics.jsx @@ -0,0 +1,181 @@ +import { useState, useEffect } from 'react'; +import { useAuth } from '../context/AuthContext'; +import { + fetchAnalyticsOverview, fetchAnalyticsPageviews, + fetchAnalyticsUsers, fetchAnalyticsActivity +} from '../api'; + +function BarChart({ data, height = 200 }) { + if (!data.length) return

Keine Daten vorhanden

; + const max = Math.max(...data.map(d => d.count), 1); + return ( +
+ {data.map((d, i) => ( +
+ {d.count} +
0 ? '4px' : '0' }} + title={`${d.date}: ${d.count}`} + /> + + {d.date.slice(5)} + +
+ ))} +
+ ); +} + +function StatCard({ label, value, color = 'blue' }) { + const colors = { + blue: 'text-blue-600', + green: 'text-green-600', + orange: 'text-orange-500', + red: 'text-red-600', + purple: 'text-purple-600', + }; + return ( +
+

{label}

+

{value}

+
+ ); +} + +const EVENT_LABELS = { + page_view: 'Seitenaufruf', + recipe_created: 'Rezept erstellt', + recipe_updated: 'Rezept bearbeitet', + recipe_deleted: 'Rezept gelöscht', + user_login: 'Login', + user_register: 'Registrierung', +}; + +const EVENT_COLORS = { + page_view: 'bg-blue-100 text-blue-700', + recipe_created: 'bg-green-100 text-green-700', + recipe_updated: 'bg-yellow-100 text-yellow-700', + recipe_deleted: 'bg-red-100 text-red-700', + user_login: 'bg-purple-100 text-purple-700', + user_register: 'bg-indigo-100 text-indigo-700', +}; + +export default function Analytics() { + const { token } = useAuth(); + const [overview, setOverview] = useState(null); + const [pageviews, setPageviews] = useState([]); + const [users, setUsers] = useState([]); + const [activity, setActivity] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + load(); + }, []); + + const load = async () => { + try { + const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10); + const [ov, pv, us, act] = await Promise.all([ + fetchAnalyticsOverview(token), + fetchAnalyticsPageviews(token, thirtyDaysAgo), + fetchAnalyticsUsers(token), + fetchAnalyticsActivity(token), + ]); + setOverview(ov); + setPageviews(pv); + setUsers(us); + setActivity(act); + } catch (e) { + console.error('Analytics Fehler:', e); + } + setLoading(false); + }; + + if (loading) return
Laden...
; + if (!overview) return
Fehler beim Laden der Analytics-Daten.
; + + return ( +
+

Analytics Dashboard

+ +
+ + + + + +
+ +
+ + + +
+ +
+

Pageviews (letzte 30 Tage)

+ +
+ +
+
+

Aktivste User

+ {users.length === 0 ? ( +

Keine Daten vorhanden.

+ ) : ( +
+ + + + + + + + + + + + {users.map(u => ( + + + + + + + + ))} + +
UserLoginsErstelltBearbeitetGesamt
{u.username}{u.logins}{u.recipes_created}{u.recipes_updated}{u.total_events}
+
+ )} +
+ +
+

Letzte Aktivitäten

+ {activity.length === 0 ? ( +

Keine Aktivitäten vorhanden.

+ ) : ( +
+ {activity.map(e => ( +
+ + {EVENT_LABELS[e.type] || e.type} + + + {e.username || 'Anonym'} + {e.path && {e.path}} + {e.recipe_name && "{e.recipe_name}"} + + + {new Date(e.timestamp).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })} + +
+ ))} +
+ )} +
+
+
+ ); +}