chahinebrini 14452b2a46 refactor(native): Pressable → TouchableOpacity sweep (style-fn swallows Android styles)
Alle <Pressable style={({pressed}) => ({...})}> ersetzt — style-Funktion
droppt auf Android (New Arch) intermittierend width/height, führt zu 0×0
unsichtbaren Elementen. TouchableOpacity mit activeOpacity ist stabil.

Außerdem übrige Pressables (plain style) aus components/ und app/
migriert sowie zwei überschüssige </View>-Tags in chat.tsx + RoomCard.tsx
entfernt die TS-Fehler verursacht haben.

64 Dateien, typecheck sauber.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:43:10 +02:00

161 lines
4.4 KiB
TypeScript

import { useState } from 'react';
import { TouchableOpacity, Text, View } from 'react-native';
import Svg, { Rect, Text as SvgText } from 'react-native-svg';
import { useTranslation } from 'react-i18next';
import { useColors } from '../../lib/theme';
import type { DailyStat } from '../../hooks/useMailStatus';
type Props = {
dailyStats: DailyStat[];
totalBlocked: number;
};
const CHART_HEIGHT = 72;
const BAR_RADIUS = 4;
const LABEL_HEIGHT = 16;
const SVG_HEIGHT = CHART_HEIGHT + LABEL_HEIGHT;
export function MailWeeklyChart({ dailyStats, totalBlocked }: Props) {
const { t } = useTranslation();
const colors = useColors();
const [activeIdx, setActiveIdx] = useState<number | null>(null);
const chartMax = Math.max(...dailyStats.map((d) => d.count), 1);
const weekTotal = dailyStats.reduce((s, d) => s + d.count, 0);
return (
<View
style={{
backgroundColor: colors.surface,
borderRadius: 16,
borderWidth: 1,
borderColor: colors.border,
paddingHorizontal: 16,
paddingTop: 14,
paddingBottom: 14,
}}
>
{/* Header */}
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 12,
}}
>
<Text
style={{
fontSize: 13,
fontFamily: 'Nunito_700Bold',
color: colors.text,
}}
>
{t('mail.chart_title')}
</Text>
<Text
style={{
fontSize: 12,
fontFamily: 'Nunito_600SemiBold',
color: colors.error,
}}
>
{t('mail.chart_week_total', { count: weekTotal })}
</Text>
</View>
{/* Tooltip */}
{activeIdx !== null && dailyStats[activeIdx] !== undefined && (
<View
style={{
alignSelf: 'center',
backgroundColor: colors.surfaceElevated,
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 4,
marginBottom: 8,
}}
>
<Text
style={{
fontSize: 12,
fontFamily: 'Nunito_700Bold',
color: colors.text,
textAlign: 'center',
}}
>
{dailyStats[activeIdx].label}: {dailyStats[activeIdx].count}
</Text>
</View>
)}
{/* SVG Bar Chart */}
<View style={{ width: '100%' }}>
<Svg width="100%" height={SVG_HEIGHT} viewBox={`0 0 ${7 * 40} ${SVG_HEIGHT}`} preserveAspectRatio="none">
{dailyStats.map((day, i) => {
const barH = day.count > 0
? Math.max(6, Math.round((day.count / chartMax) * CHART_HEIGHT))
: 4;
const x = i * 40 + 4;
const barW = 32;
const y = CHART_HEIGHT - barH;
const isActive = activeIdx === i;
const fill = day.count > 0
? isActive ? '#b91c1c' : '#ef4444'
: colors.border;
return (
<Rect
key={day.date}
x={x}
y={y}
width={barW}
height={barH}
rx={BAR_RADIUS}
ry={BAR_RADIUS}
fill={fill}
/>
);
})}
{dailyStats.map((day, i) => (
<SvgText
key={`label-${day.date}`}
x={i * 40 + 20}
y={SVG_HEIGHT - 2}
textAnchor="middle"
fontSize={10}
fill={colors.textMuted}
fontFamily="Nunito_400Regular"
>
{day.label}
</SvgText>
))}
</Svg>
{/* Invisible tap targets per bar */}
<View
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
flexDirection: 'row',
}}
>
{dailyStats.map((day, i) => (
<TouchableOpacity
key={`tap-${day.date}`}
style={{ flex: 1, height: '100%' }}
onPress={() => setActiveIdx((prev) => (prev === i ? null : i))}
activeOpacity={0.7}
accessibilityLabel={`${day.label}: ${day.count}`}
/>
))}
</View>
</View>
</View>
);
}