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[]; }; const SLICE_COLORS = ['#ef4444', '#3b82f6', '#f59e0b', '#8b5cf6', '#10b981']; const OTHER_COLOR = '#a3a3a3'; const MAX_SLICES = 5; const R_OUTER = 54; const R_INNER = 34; const CX = 64; const CY = 64; // Half-donut renders the UPPER semicircle (flat edge at bottom). // CY=64 places the center at the bottom of the 68px-tall viewBox. // angleDeg=0 → top (12 o'clock), angleDeg=-90 → left, angleDeg=90 → right. // 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 }: Props) { const { t } = useTranslation(); const colors = useColors(); const total = data.reduce((s, d) => s + d.count, 0); const slices = useMemo(() => { if (data.length === 0 || total === 0) return []; const sorted = [...data].sort((a, b) => b.count - a.count); const top = sorted.slice(0, MAX_SLICES); const rest = sorted.slice(MAX_SLICES); const items: { label: string; count: number; color: string }[] = top.map((e, i) => ({ label: displayLabel(e), count: e.count, color: SLICE_COLORS[i], })); if (rest.length > 0) { items.push({ label: t('mail.stats.distribution_other'), count: rest.reduce((s, e) => s + e.count, 0), color: OTHER_COLOR, }); } return items; }, [data, total, t]); if (data.length <= 1 || total === 0) return null; let cursor = HALF_DONUT_START_DEG; return ( {t('mail.stats.distribution_heading')} {/* Half-donut — upper semicircle, center pinned at bottom of viewBox */} {slices.map((slice) => { const sweep = (slice.count / total) * 180; const startDeg = cursor; cursor += sweep; return ( ); })} {/* Inner fill to enforce donut shape */} {/* Legend */} {slices.map((slice) => ( {slice.label} {slice.count} ))} ); }