import { useMemo } from 'react'; import { Text, View } from 'react-native'; import Svg, { Path, Circle } from 'react-native-svg'; import { useTranslation } from 'react-i18next'; import { useColors } from '../../lib/theme'; import type { BlockedByConnectionEntry } from '../../hooks/useMailStats'; type Props = { data: BlockedByConnectionEntry[]; /** When true: renders as full-width hero card with integrated title row */ hero?: boolean; totalBlocked?: number; accountCount?: number; isLegend?: boolean; }; const SLICE_COLORS = ['#ef4444', '#3b82f6', '#f59e0b', '#8b5cf6']; const OTHER_COLOR = '#a3a3a3'; // Legend cap: show max 3 named entries + optional "others" row const MAX_LEGEND_ENTRIES = 3; const R_OUTER = 54; const R_INNER = 34; const CX = 64; const CY = 64; // Half-donut: upper semicircle, flat edge at bottom. // Slices sweep from -90° (left) to +90° (right) = 180° total. const HALF_DONUT_START_DEG = -90; function domainFromEmail(email: string): string { return email.split('@')[1] ?? email; } function displayLabel(entry: BlockedByConnectionEntry): string { return entry.title ?? domainFromEmail(entry.email); } function polarToXY(cx: number, cy: number, r: number, angleDeg: number) { const rad = ((angleDeg - 90) * Math.PI) / 180; return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad), }; } function arcPath( cx: number, cy: number, rOuter: number, rInner: number, startDeg: number, endDeg: number, ): string { const outerStart = polarToXY(cx, cy, rOuter, startDeg); const outerEnd = polarToXY(cx, cy, rOuter, endDeg); const innerEnd = polarToXY(cx, cy, rInner, endDeg); const innerStart = polarToXY(cx, cy, rInner, startDeg); const large = endDeg - startDeg > 180 ? 1 : 0; return [ `M ${outerStart.x} ${outerStart.y}`, `A ${rOuter} ${rOuter} 0 ${large} 1 ${outerEnd.x} ${outerEnd.y}`, `L ${innerEnd.x} ${innerEnd.y}`, `A ${rInner} ${rInner} 0 ${large} 0 ${innerStart.x} ${innerStart.y}`, 'Z', ].join(' '); } export function MailDistributionChart({ data, hero, totalBlocked, accountCount, isLegend }: Props) { const { t } = useTranslation(); const colors = useColors(); const total = data.reduce((s, d) => s + d.count, 0); // Build donut slices: always cap at 4 visible segments (Top-3 + Sonstige). // Edge-case: ≤3 → no grouping. Exactly 4 → show all 4 (no grouping). // 5+ → Top-3 + Sonstige. const slices = useMemo(() => { if (data.length === 0 || total === 0) return []; const sorted = [...data].sort((a, b) => b.count - a.count); if (sorted.length <= 4) { return sorted.map((e, i) => ({ label: displayLabel(e), count: e.count, color: SLICE_COLORS[i] ?? OTHER_COLOR, isOther: false, hiddenCount: 0, })); } // 5+ connections: Top-3 + Sonstige bucket const top3 = sorted.slice(0, MAX_LEGEND_ENTRIES); const rest = sorted.slice(MAX_LEGEND_ENTRIES); const restCount = rest.reduce((s, e) => s + e.count, 0); const restConnectionCount = rest.length; const items = top3.map((e, i) => ({ label: displayLabel(e), count: e.count, color: SLICE_COLORS[i], isOther: false, hiddenCount: 0, })); items.push({ label: t('mail.stats.distribution_other_n', { n: restConnectionCount }), count: restCount, color: OTHER_COLOR, isOther: true, hiddenCount: restConnectionCount, }); return items; }, [data, total, t]); if (data.length <= 1 || total === 0) return null; let cursor = HALF_DONUT_START_DEG; const displayTotal = totalBlocked ?? total; if (hero) { return ( {/* Integrated title row */} {displayTotal.toLocaleString()} {t('mail.stats_account_summary', { count: accountCount ?? data.length })} {/* Live / Scheduled pill */} {isLegend ? t('mail.live') : t('mail.scheduled')} {/* Donut + Legend */} {slices.map((slice) => { const sweep = (slice.count / total) * 180; const startDeg = cursor; cursor += sweep; return ( ); })} {slices.map((slice) => ( ))} ); } // Standard (non-hero) card — kept for potential reuse return ( {t('mail.stats.distribution_heading')} {slices.map((slice) => { const sweep = (slice.count / total) * 180; const startDeg = cursor; cursor += sweep; return ( ); })} {slices.map((slice) => ( ))} ); } function LegendRow({ slice, colors, }: { slice: { label: string; count: number; color: string; isOther: boolean }; colors: ReturnType; }) { return ( {slice.label} {slice.count} ); }