feat: add example report preview with stats and chart to FamilyGuard page

Zeigt beispielhaft, wie die monatliche FamilyGuard-Report-E-Mail aussieht
(Stat-Kacheln + Balkendiagramm der letzten 6 Monate), direkt nach dem
Report-Angebot platziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhY1QhDXGWfyhrxaJvfpNt
This commit is contained in:
MBO-Tech-IT 2026-07-21 20:09:20 +02:00
parent a26cc3f8f2
commit 0183430725
2 changed files with 149 additions and 0 deletions

View File

@ -2,6 +2,7 @@ import type { Metadata } from "next";
import Link from "next/link"; import Link from "next/link";
import Logo from "@/components/Logo"; import Logo from "@/components/Logo";
import FlyerDownloadForm from "@/components/FlyerDownloadForm"; import FlyerDownloadForm from "@/components/FlyerDownloadForm";
import FlyerReportPreview from "@/components/FlyerReportPreview";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "MBO FamilyGuard Familienschutz fürs Heimnetzwerk | MBO-Tech-IT", title: "MBO FamilyGuard Familienschutz fürs Heimnetzwerk | MBO-Tech-IT",
@ -180,6 +181,8 @@ export default function FamilyGuardPage() {
</p> </p>
</div> </div>
<FlyerReportPreview />
{/* Preis */} {/* Preis */}
<div className="mb-16 rounded-2xl overflow-hidden border border-blue-500/30 bg-[#111925]"> <div className="mb-16 rounded-2xl overflow-hidden border border-blue-500/30 bg-[#111925]">
<div className="p-8 flex flex-col sm:flex-row items-center justify-between gap-6"> <div className="p-8 flex flex-col sm:flex-row items-center justify-between gap-6">

View File

@ -0,0 +1,146 @@
"use client";
import { useEffect, useRef, useState } from "react";
const monthlyData = [
{ month: "Okt", value: 189 },
{ month: "Nov", value: 203 },
{ month: "Dez", value: 231 },
{ month: "Jan", value: 198 },
{ month: "Feb", value: 215 },
{ month: "Mär", value: 247 },
];
const maxValue = Math.max(...monthlyData.map((d) => d.value));
const stats = [
{ value: 247, suffix: "", label: "Blockierte Gefahren (Mär)", accent: "orange" as const },
{ value: 6, suffix: "", label: "Geschützte Geräte", accent: "blue" as const },
{ value: 100, suffix: "%", label: "Automatisch aktuell", accent: "orange" as const },
];
function Counter({
value,
suffix,
accent,
}: {
value: number;
suffix: string;
accent: "orange" | "blue";
}) {
const [count, setCount] = useState(0);
const ref = useRef<HTMLSpanElement>(null);
const started = useRef(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting && !started.current) {
started.current = true;
const duration = 1200;
const start = performance.now();
const tick = (now: number) => {
const progress = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
setCount(Math.floor(eased * value));
if (progress < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
observer.disconnect();
}
},
{ threshold: 0.3 }
);
observer.observe(el);
return () => observer.disconnect();
}, [value]);
const color = accent === "orange" ? "text-orange-400" : "text-blue-400";
return (
<span ref={ref} className={`text-3xl sm:text-4xl font-black ${color}`}>
{count}
{suffix}
</span>
);
}
export default function FlyerReportPreview() {
return (
<div className="mb-16 p-6 sm:p-8 rounded-2xl bg-white dark:bg-gray-900 border border-slate-200 dark:border-gray-800">
<div className="text-center mb-8">
<span className="text-orange-400 font-mono text-xs font-bold tracking-[0.25em] uppercase">
Beispiel-Report
</span>
<h3 className="text-2xl font-black text-slate-900 dark:text-white mt-1 mb-2">
So sieht Ihre monatliche Übersicht aus
</h3>
<p className="text-slate-600 dark:text-slate-400 text-sm max-w-xl mx-auto">
Beispielhafte Darstellung des MBO FamilyGuard Reports Ihre echte
monatliche E-Mail zeigt die tatsächlichen Werte für Ihr Zuhause.
</p>
</div>
{/* Stat-Kacheln */}
<div className="grid grid-cols-3 gap-3 sm:gap-4 mb-10">
{stats.map((s) => (
<div
key={s.label}
className="text-center p-4 rounded-xl bg-slate-50 dark:bg-[#111925] border border-slate-200 dark:border-gray-800"
>
<Counter value={s.value} suffix={s.suffix} accent={s.accent} />
<p className="text-slate-600 dark:text-slate-400 text-[11px] sm:text-xs mt-2 leading-snug">
{s.label}
</p>
</div>
))}
</div>
{/* Balkendiagramm */}
<div>
<p className="text-slate-700 dark:text-slate-300 text-sm font-semibold mb-4 text-center">
Blockierte Gefahren letzte 6 Monate
</p>
<div className="flex items-end justify-between gap-3 h-28 px-2">
{monthlyData.map((d, i) => {
const isCurrent = i === monthlyData.length - 1;
const heightPct = (d.value / maxValue) * 100;
return (
<div key={d.month} className="flex-1 h-full flex flex-col justify-end items-center">
<span
className={`text-[11px] font-semibold mb-1 ${
isCurrent ? "text-orange-500 dark:text-orange-400" : "text-slate-400 dark:text-slate-500"
}`}
>
{d.value}
</span>
<div
className={`w-full max-w-[32px] rounded-t-md ${
isCurrent ? "bg-orange-500" : "bg-orange-500/25"
}`}
style={{ height: `${heightPct}%` }}
/>
</div>
);
})}
</div>
<div className="flex justify-between gap-3 px-2 mt-2">
{monthlyData.map((d) => (
<span
key={d.month}
className="flex-1 text-center text-[11px] text-slate-500 dark:text-slate-500"
>
{d.month}
</span>
))}
</div>
</div>
</div>
);
}