fix Analyse

This commit is contained in:
nadja
2026-08-26 12:36:07 +02:00
parent 18cf24ff93
commit d4351a1123

View File

@@ -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 <p className="text-gray-400 text-sm">Keine Daten vorhanden</p>;
const max = Math.max(...data.map(d => d.count), 1);
return (
<div className="flex items-end gap-1" style={{ height }}>
{data.map((d, i) => (
<div key={i} className="flex-1 flex flex-col items-center gap-1">
<span className="text-xs text-gray-500">{d.count}</span>
<div
className="w-full bg-blue-500 rounded-t"
style={{ height: `${(d.count / max) * (height - 30)}px`, minHeight: d.count > 0 ? '4px' : '0' }}
title={`${d.date}: ${d.count}`}
/>
<span className="text-xs text-gray-400" style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)', fontSize: '10px' }}>
{d.date.slice(5)}
</span>
</div>
))}
</div>
);
}
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 (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-5">
<p className="text-sm text-gray-500">{label}</p>
<p className={`text-3xl font-bold ${colors[color]}`}>{value}</p>
</div>
);
}
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 <div className="text-center py-12 text-gray-400">Laden...</div>;
if (!overview) return <div className="text-center py-12 text-gray-400">Fehler beim Laden der Analytics-Daten.</div>;
return (
<div className="max-w-6xl mx-auto space-y-8">
<h1 className="text-2xl font-bold">Analytics Dashboard</h1>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-4">
<StatCard label="Pageviews heute" value={overview.pageviews.today} color="blue" />
<StatCard label="Pageviews gestern" value={overview.pageviews.yesterday} color="blue" />
<StatCard label="Pageviews 7 Tage" value={overview.pageviews.week} color="purple" />
<StatCard label="Aktive User heute" value={overview.activeUsers.today} color="green" />
<StatCard label="Rezepte erstellt" value={overview.recipes.created} color="green" />
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<StatCard label="Rezepte bearbeitet" value={overview.recipes.updated} color="orange" />
<StatCard label="Rezepte gelöscht" value={overview.recipes.deleted} color="red" />
<StatCard label="Logins gesamt" value={overview.logins} color="purple" />
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-semibold mb-4">Pageviews (letzte 30 Tage)</h2>
<BarChart data={pageviews} height={220} />
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-semibold mb-4">Aktivste User</h2>
{users.length === 0 ? (
<p className="text-gray-400 text-sm">Keine Daten vorhanden.</p>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-gray-200">
<th className="text-left py-2 font-medium text-gray-500">User</th>
<th className="text-right py-2 font-medium text-gray-500">Logins</th>
<th className="text-right py-2 font-medium text-gray-500">Erstellt</th>
<th className="text-right py-2 font-medium text-gray-500">Bearbeitet</th>
<th className="text-right py-2 font-medium text-gray-500">Gesamt</th>
</tr>
</thead>
<tbody>
{users.map(u => (
<tr key={u.user_id} className="border-b border-gray-100">
<td className="py-2 font-medium">{u.username}</td>
<td className="py-2 text-right">{u.logins}</td>
<td className="py-2 text-right">{u.recipes_created}</td>
<td className="py-2 text-right">{u.recipes_updated}</td>
<td className="py-2 text-right font-semibold">{u.total_events}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<h2 className="text-lg font-semibold mb-4">Letzte Aktivitäten</h2>
{activity.length === 0 ? (
<p className="text-gray-400 text-sm">Keine Aktivitäten vorhanden.</p>
) : (
<div className="space-y-2 max-h-96 overflow-y-auto">
{activity.map(e => (
<div key={e.id} className="flex items-center gap-3 text-sm py-1 border-b border-gray-50">
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${EVENT_COLORS[e.type] || 'bg-gray-100 text-gray-600'}`}>
{EVENT_LABELS[e.type] || e.type}
</span>
<span className="flex-1 text-gray-600">
{e.username || 'Anonym'}
{e.path && <span className="text-gray-400 ml-1">{e.path}</span>}
{e.recipe_name && <span className="text-gray-400 ml-1">"{e.recipe_name}"</span>}
</span>
<span className="text-xs text-gray-400 whitespace-nowrap">
{new Date(e.timestamp).toLocaleString('de-DE', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
</span>
</div>
))}
</div>
)}
</div>
</div>
</div>
);
}