diff --git a/.gitignore b/.gitignore index b1a16b0..ac20ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,10 @@ # pro Umgebung unterschiedlich und darf nicht öffentlich einsehbar sein /hifi/api/config/setup.php +# Google-Service-Account-Schlüssel für die Analytics-Data-API-Anbindung im Dashboard +# (wird vom Admin-Panel automatisch neu geschrieben, enthält einen privaten Schlüssel) +/hifi/api/config/ga-service-account.json + # Claude-Code-Tooling (Skills/lokale Einstellungen) - gehört nicht zum Website-Projekt /hifi/.claude/ /hifi/.agents/ diff --git a/hifi-src/src/pages/admin/Dashboard.jsx b/hifi-src/src/pages/admin/Dashboard.jsx index 20a06d6..4a10c71 100644 --- a/hifi-src/src/pages/admin/Dashboard.jsx +++ b/hifi-src/src/pages/admin/Dashboard.jsx @@ -16,6 +16,94 @@ const Icon = ({ path, className = 'h-6 w-6' }) => ( ); +function AnalyticsWidget() { + const [report, setReport] = useState(null); + const [error, setError] = useState(''); + + useEffect(() => { + api.get('/analytics/report').then(setReport).catch((e) => setError(e.message)); + }, []); + + if (error) { + return ( +
+

Google Analytics

+

{error}

+
+ ); + } + + if (!report) return null; + + if (!report.configured) { + return ( +
+

Google Analytics

+

+ Noch nicht eingerichtet. Unter{' '} + + Einstellungen → Website + {' '} + kannst du die Dashboard-Anbindung konfigurieren, um hier Besucherzahlen zu sehen. +

+
+ ); + } + + const maxPageviews = Math.max(1, ...report.daily.map((d) => d.pageviews)); + + return ( +
+

Google Analytics

+

Letzte 7 Tage.

+ +
+
+

{report.totals.users}

+

Nutzer

+
+
+

{report.totals.sessions}

+

Sitzungen

+
+
+

{report.totals.pageviews}

+

Seitenaufrufe

+
+
+ + {report.daily.length > 0 && ( +
+ {report.daily.map((d) => ( +
+
+ {d.date.slice(6, 8)}.{d.date.slice(4, 6)}. +
+ ))} +
+ )} + + {report.top_pages.length > 0 && ( +
+

Meistbesuchte Seiten

+
    + {report.top_pages.map((p) => ( +
  • + {p.path} + {p.pageviews} +
  • + ))} +
+
+ )} +
+ ); +} + export default function Dashboard() { const { user, hasPermission } = useAuth(); const [stats, setStats] = useState({}); @@ -70,6 +158,8 @@ export default function Dashboard() { ))} )} + + {hasPermission('settings.manage') && } ); } diff --git a/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx b/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx index d0cb6ea..cf41c9c 100644 --- a/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx +++ b/hifi-src/src/pages/admin/settings/WebsiteSettings.jsx @@ -11,6 +11,13 @@ export default function WebsiteSettings() { const [saved, setSaved] = useState(false); const [error, setError] = useState(''); + const [gaPropertyId, setGaPropertyId] = useState(''); + const [gaServiceAccountJson, setGaServiceAccountJson] = useState(''); + const [gaHasCredentials, setGaHasCredentials] = useState(false); + const [gaBusy, setGaBusy] = useState(false); + const [gaError, setGaError] = useState(''); + const [gaSaved, setGaSaved] = useState(false); + useEffect(() => { api .get('/site-settings') @@ -24,8 +31,31 @@ export default function WebsiteSettings() { }) ) .finally(() => setLoading(false)); + api.get('/settings/analytics').then((res) => { + setGaPropertyId(res.ga_property_id || ''); + setGaHasCredentials(res.has_credentials); + }); }, []); + const handleGaDashboardSave = async () => { + setGaBusy(true); + setGaError(''); + setGaSaved(false); + try { + const res = await api.post('/settings/analytics', { + ga_property_id: gaPropertyId, + service_account_json: gaServiceAccountJson, + }); + setGaHasCredentials(res.has_credentials); + setGaServiceAccountJson(''); + setGaSaved(true); + } catch (err) { + setGaError(err.message); + } finally { + setGaBusy(false); + } + }; + const update = (field, value) => { setForm((f) => ({ ...f, [field]: value })); setSaved(false); @@ -119,6 +149,48 @@ export default function WebsiteSettings() { className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900" /> + +
+

Dashboard-Anbindung (optional)

+

+ Zeigt Besucherzahlen direkt in deinem Admin-Dashboard an. Braucht ein Google-Cloud-Service-Account mit + Lesezugriff auf diese GA4-Property. +

+
+
+ + { setGaPropertyId(e.target.value); setGaSaved(false); }} + placeholder="z. B. 123456789" + className="w-full rounded-md border border-neutral-300 bg-white px-3 py-2 text-sm dark:border-neutral-700 dark:bg-neutral-900" + /> +

Nicht die Measurement-ID (G-...) - zu finden unter GA4 → Verwaltung → Property-Details.

+
+
+ +