Polish social milestone chart curves

This commit is contained in:
2026-08-17 11:19:41 +02:00
parent eb932ebdaa
commit b278d275bb
3 changed files with 66 additions and 12 deletions

View File

@@ -7,6 +7,7 @@ import { Button } from '@/components/ui/Button';
import { useCsrf } from '@/hooks/useCsrf';
import { useTranslation } from '@/hooks/useTranslation';
import { showToast } from '@/components/ui/Toast';
import { roundedChartPath } from '@/lib/rounded-chart-path';
type Channel = 'x' | 'instagram';
type Card = { language: 'en' | 'de'; qrTitle: string; label: string; title: string; totalScans?: number; totalUniqueScans: number; milestoneThreshold: number; trend: { points: Array<{ at: string; total: number }>; startLabel: string; endLabel: string; target: number; ceiling: number } | null };
@@ -24,20 +25,21 @@ function Trend({ trend, locale }: { trend: NonNullable<Card['trend']>; locale: '
: Array.from({ length: 5 }, (_, index) => trend.target * (index + 1) / 4);
const first = new Date(trend.points[0].at).getTime();
const last = Math.max(new Date(trend.points[trend.points.length - 1].at).getTime(), first + 1);
const points = trend.points.map(point => {
const chartPoints = trend.points.map(point => {
const x = 60 + ((new Date(point.at).getTime() - first) / (last - first)) * 410;
const y = 142 - (point.total / ceiling) * 126;
return `${x},${y}`;
}).join(' ');
const targetY = 142 - (trend.target / ceiling) * 126;
return { x, y };
});
const path = roundedChartPath(chartPoints, 18);
const endPoint = chartPoints[chartPoints.length - 1] || { x: 470, y: 142 };
const number = new Intl.NumberFormat(locale === 'de' ? 'de-DE' : 'en-US', { maximumFractionDigits: 2 });
return <svg viewBox="0 0 480 184" className="w-full" role="img" aria-label="Cumulative unique scan trend">
{ticks.map(tick => {
const y = 142 - (tick / ceiling) * 126;
return <g key={tick}><text x="48" y={y + 4} textAnchor="end" fontSize="11" fontWeight={tick === trend.target ? 700 : 500} fill={tick === trend.target ? '#0256ff' : '#64748b'}>{number.format(tick)}</text><line x1="60" x2="470" y1={y} y2={y} stroke={tick === trend.target ? '#bfdbfe' : '#e2e8f0'} strokeWidth={tick === trend.target ? 1.6 : 1} /></g>;
})}
<polyline points={points} fill="none" stroke="#0256ff" strokeWidth="3.5" strokeLinejoin="round" strokeLinecap="round" />
<circle cx="470" cy={targetY} r="4.5" fill="#ffffff" stroke="#0256ff" strokeWidth="3" />
<path d={path} fill="none" stroke="#0256ff" strokeWidth="3.5" strokeLinejoin="round" strokeLinecap="round" />
<circle cx={endPoint.x} cy={endPoint.y} r="4.5" fill="#ffffff" stroke="#0256ff" strokeWidth="3" />
<text x="60" y="176" fontSize="11" fontWeight="600" fill="#45617f">{trend.startLabel}</text>
<text x="470" y="176" textAnchor="end" fontSize="11" fontWeight="600" fill="#45617f">{trend.endLabel}</text>
</svg>;