diff --git a/app/(app)/analytics/report/ReportClient.tsx b/app/(app)/analytics/report/ReportClient.tsx index a18ba9a..a581240 100644 --- a/app/(app)/analytics/report/ReportClient.tsx +++ b/app/(app)/analytics/report/ReportClient.tsx @@ -33,200 +33,532 @@ type ReportData = { generatedAt: number; }; -export function ReportClient({ data, logo }: { data: ReportData; logo: React.ReactNode }) { +const NR_LOGO = ( + + + + + + + + + + n + r + + + +); + +export function ReportClient({ data }: { data: ReportData; logo?: React.ReactNode }) { useEffect(() => { const url = new URL(window.location.href); if (url.searchParams.get("print") === "1") { - setTimeout(() => window.print(), 800); + setTimeout(() => window.print(), 1200); } }, []); const s = data.sections; + const generatedDate = new Date(data.generatedAt).toLocaleDateString("de-DE", { day: "2-digit", month: "long", year: "numeric" }); + const periodFrom = new Date(Date.now() - data.days * 24 * 60 * 60 * 1000).toLocaleDateString("de-DE"); + const periodTo = new Date().toLocaleDateString("de-DE"); return ( -
+ <> - {/* Toolbar (only visible on screen) */} -
- - Zurück - - -
+
+
+ + Zurück + + +
-
- {/* Cover */} -
-
{logo}
-
-

{s.title}

-

- Zeitraum: letzte {data.days} Tage • Erstellt: {new Date(data.generatedAt).toLocaleString("de-DE")} -

+ {/* COVER */} +
+
+ {NR_LOGO} +
{s.title}
-
+
+
Domain-Redirect-Report
+
{s.title}
+
Statistik-Report · CoreX NexRedirect
+
+

+ Übersicht über Domain-Aktivität und Nutzung im Zeitraum vom {periodFrom} bis {periodTo}. + Die Daten basieren auf der lokalen Hit-Datenbank des NexRedirect-Servers. +

+ +

+ Bot-Traffic und automatisierte Anfragen sind herausgefiltert; eindeutige Besucher werden über + täglich rotierende IP-Hashes ermittelt (DSGVO-konform). +

+ +
+ + + + + + + + + +
Berichtszeitraumletzte {data.days} Tage ({periodFrom} – {periodTo})
Domains gesamt{data.totalDomains.toLocaleString("de-DE")} ({data.activeDomains} aktiv)
Hits im Zeitraum{data.totalHits.toLocaleString("de-DE")}
Eindeutige Besucher{data.uniqueIps.toLocaleString("de-DE")}
Erstellt am{generatedDate}
+ +

Inhalt

+

+ {[ + s.summary && "1. Zusammenfassung", + s.daily && "2. Hits pro Tag", + s.top && "3. Top Domains", + s.country && "4. Geografische Verteilung", + s.dead && "5. Tote Domains", + s.perDomain && "6. Detail pro Domain", + s.hits && "7. Letzte Aufrufe", + ].filter(Boolean).map((line, i) => ( + {line}
+ ))} +

+
+ + {/* Summary */} {s.summary && ( -
-

Zusammenfassung

-
- - - - +
+
{NR_LOGO}
{s.title}
+

Zusammenfassung

+ +
+
+
Domains
+
{data.totalDomains.toLocaleString("de-DE")}
+
{data.activeDomains} aktiv
+
+
+
Hits
+
{data.totalHits.toLocaleString("de-DE")}
+
in {data.days} Tagen
+
+
+
Besucher
+
{data.uniqueIps.toLocaleString("de-DE")}
+
eindeutig
+
+
+
Ø Hits/Tag
+
{Math.round(data.totalHits / data.days).toLocaleString("de-DE")}
+
über Zeitraum
+
-
- )} - - {s.daily && ( -
-

Hits pro Tag

-
- -
-
+ + {data.daily.length > 0 && ( + <> +

Verlauf

+
+ +
+ + )} + +
+
Hinweis zur Erfassung
+
+ Bots, Crawler und automatisierte Monitoring-Anfragen werden serverseitig herausgefiltert + (User-Agent-Match, HEAD/OPTIONS-Requests, Standard-Pfade wie /favicon.ico). Eindeutige + Besucher basieren auf täglich rotierenden IP-Hashes — kein Klartext gespeichert. +
+
+
)} + {/* Top Domains */} {s.top && data.top.length > 0 && ( -
-

Top Domains

-
- +
+
{NR_LOGO}
{s.title}
+

Top Domains

+ +

Die meistgenutzten Domains im Berichtszeitraum, sortiert nach Hit-Count.

+ +
+
- - - + +
DomainHits% gesamt
+ + - + {data.top.slice(0, 30).map((r) => ( - - - + + + ))}
DomainHitsAnteil
{r.domain}{r.hits.toLocaleString("de-DE")}{data.totalHits ? ((r.hits / data.totalHits) * 100).toFixed(1) : "0"}%{r.domain}{r.hits.toLocaleString("de-DE")}{data.totalHits ? ((r.hits / data.totalHits) * 100).toFixed(1) : "0"}%
-
+
)} + {/* Country */} {s.country && data.country.length > 0 && ( -
-

Geografische Verteilung

-
-
- -
- - - - {data.country.map((c) => ( - - ))} - -
LandHits
{c.country}{c.hits.toLocaleString("de-DE")}
+
+
{NR_LOGO}
{s.title}
+

Geografische Verteilung

+ +

Aufschlüsselung der Hits nach Herkunftsland (über lokale GeoLite2-Datenbank).

+ +
+
-
+ + + + + {data.country.map((c) => ( + + + + + + ))} + +
LandHitsAnteil
{c.country}{c.hits.toLocaleString("de-DE")}{data.totalHits ? ((c.hits / data.totalHits) * 100).toFixed(1) : "0"}%
+
)} + {/* Dead */} {s.dead && data.dead.length > 0 && ( -
-

Tote Domains

-

Aktive Domains ohne Hits in den letzten 90 Tagen — Kandidaten zum Kündigen.

- - - +
+
{NR_LOGO}
{s.title}
+

Tote Domains

+ +

+ Aktive Domains ohne einen einzigen Hit in den letzten 90 Tagen. Kandidaten zur + Auflösung oder Kündigung — bevor erneut Verlängerungsgebühren anfallen. +

+ +
DomainZielAngelegt
+ + {data.dead.map((d) => ( - - - + + + ))}
DomainZielAngelegt
{d.domain}{d.target_url || "—"}{new Date(d.created_at).toLocaleDateString("de-DE")}{d.domain}{d.target_url || "—"}{new Date(d.created_at).toLocaleDateString("de-DE")}
-
+ +
+
Empfehlung
+
+ Domains ohne Aufrufe über 90 Tage werden mit hoher Wahrscheinlichkeit nicht mehr genutzt. + Vor Kündigung empfiehlt sich, kurz die Suchindex-Position und die ausgehende Verlinkung + zu prüfen, um SEO-Wert nicht versehentlich aufzugeben. +
+
+
)} + {/* Per-Domain Detail */} {s.perDomain && data.perDomain.length > 0 && ( -
-

Detailbericht pro Domain

- - +
+
{NR_LOGO}
{s.title}
+

Detail pro Domain

+ +

Vollständige Aufstellung aller Domains mit Hit-Zahlen für den Berichtszeitraum und insgesamt.

+ +
+ - - - - - - - + + + + + + - + {data.perDomain.map((d) => ( - - - - - - - + + + + + + ))}
DomainStatusCodeZielHits ({data.days}d)Hits gesamtLetzter HitDomainStatusCodeHits ({data.days}d)Hits gesamtLetzter Hit
{d.domain}{d.status}{d.redirect_code}{d.target_url || "—"}{d.hits_period.toLocaleString("de-DE")}{d.hits_total.toLocaleString("de-DE")}{d.last_hit ? new Date(d.last_hit).toLocaleString("de-DE") : "—"}{d.domain}{d.status}{d.redirect_code}{d.hits_period.toLocaleString("de-DE")}{d.hits_total.toLocaleString("de-DE")}{d.last_hit ? new Date(d.last_hit).toLocaleString("de-DE") : "—"}
-
+
)} + {/* Recent hits */} {s.hits && data.recentHits.length > 0 && ( -
-

Letzte Aufrufe

-

Bis zu 200 jüngste Hits im Berichts-Zeitraum.

- - - +
+
{NR_LOGO}
{s.title}
+

Letzte Aufrufe

+ +

Die jüngsten {data.recentHits.length} Hits im Berichtszeitraum (chronologisch absteigend).

+ +
ZeitDomainLandPfad
+ + + + + + + + + {data.recentHits.map((h, i) => ( - - - - + + + + ))}
ZeitDomainLandPfad
{new Date(h.ts).toLocaleString("de-DE")}{h.domain}{h.country || "—"}{(h.path || "/").slice(0, 60)}{new Date(h.ts).toLocaleString("de-DE")}{h.domain}{h.country || "—"}{(h.path || "/").slice(0, 50)}
-
+ )} - - - - ); -} - -function Stat({ label, value, sub }: { label: string; value: number; sub?: string }) { - return ( -
-

{label}

-

{value.toLocaleString("de-DE")}

- {sub &&

{sub}

} -
+ ); } diff --git a/app/(app)/analytics/report/page.tsx b/app/(app)/analytics/report/page.tsx index 41d7a25..ae04940 100644 --- a/app/(app)/analytics/report/page.tsx +++ b/app/(app)/analytics/report/page.tsx @@ -1,6 +1,5 @@ import { notFound } from "next/navigation"; import { getDb } from "@/lib/db"; -import { Logo } from "@/components/Logo"; import { ReportClient } from "./ReportClient"; export const dynamic = "force-dynamic"; @@ -79,7 +78,6 @@ export default async function ReportPage({ searchParams }: { searchParams: Promi recentHits, generatedAt: Date.now(), }} - logo={} /> ); } diff --git a/package.json b/package.json index d5212d3..5687b58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "corex-nexredirect", - "version": "0.1.12", + "version": "0.1.13", "license": "MIT", "overrides": { "postcss": "^8.5.13",