polish(mobile): audit design DSFR multi-agents — tokens, a11y, états, finitions
- tokens centralisés (Tint, IconSize, LineHeight, LetterSpacing), zéro hex en dur - accessibilité (rôles, labels, live regions, afficher/masquer mot de passe) - états vides/erreur complétés (ex. saisie introuvable), cibles tactiles >=44px - suppression du composant mort RepubliqueHeader Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Tabs } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Colors } from '@/lib/colors';
|
||||
import { FontWeight, Spacing } from '@/lib/theme';
|
||||
|
||||
export default function TabLayout() {
|
||||
return (
|
||||
@@ -11,12 +12,13 @@ export default function TabLayout() {
|
||||
tabBarStyle: {
|
||||
backgroundColor: Colors.white,
|
||||
borderTopColor: Colors.gray[200],
|
||||
paddingBottom: 8,
|
||||
paddingTop: 8,
|
||||
height: 65,
|
||||
paddingBottom: Spacing.sm,
|
||||
paddingTop: Spacing.sm,
|
||||
height: Spacing.xxxl + Spacing.xl + 1,
|
||||
},
|
||||
tabBarLabelStyle: { fontWeight: FontWeight.medium },
|
||||
headerStyle: { backgroundColor: Colors.white },
|
||||
headerTitleStyle: { fontWeight: '700', color: Colors.black },
|
||||
headerTitleStyle: { fontWeight: FontWeight.bold, color: Colors.black },
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
|
||||
@@ -8,6 +8,12 @@ import { Colors } from '@/lib/colors';
|
||||
import { Spacing, Radius, FontSize, FontWeight, MIN_TOUCH } from '@/lib/theme';
|
||||
import { AppBar, Card, Badge, SectionTitle, EmptyState, Loader } from '@/components/ui';
|
||||
|
||||
/** Tailles d'icônes et de pastilles (multiples de l'échelle 4px du DSFR). */
|
||||
const ICON_LG = 28;
|
||||
const ACTION_ICON = 56;
|
||||
const EMOTION_ICON = 44;
|
||||
const EMOJI_SIZE = 22;
|
||||
|
||||
export default function DashboardScreen() {
|
||||
const router = useRouter();
|
||||
const { user } = useAuthStore();
|
||||
@@ -27,13 +33,13 @@ export default function DashboardScreen() {
|
||||
<View style={styles.actionsRow}>
|
||||
<Card onPress={() => router.push('/(tabs)/journal/new')} padding={Spacing.lg} style={styles.actionPrimary}>
|
||||
<View style={[styles.actionIcon, styles.actionIconPrimary]}>
|
||||
<Ionicons name="add-circle" size={28} color={Colors.white} />
|
||||
<Ionicons name="add-circle" size={ICON_LG} color={Colors.white} />
|
||||
</View>
|
||||
<Text style={styles.actionTextPrimary}>Nouvelle saisie</Text>
|
||||
</Card>
|
||||
<Card onPress={() => router.push('/(tabs)/journal/reports')} padding={Spacing.lg} style={styles.actionCard}>
|
||||
<View style={[styles.actionIcon, styles.actionIconSecondary]}>
|
||||
<Ionicons name="bar-chart" size={28} color={Colors.primary} />
|
||||
<Ionicons name="bar-chart" size={ICON_LG} color={Colors.primary} />
|
||||
</View>
|
||||
<Text style={styles.actionText}>Mes rapports</Text>
|
||||
</Card>
|
||||
@@ -73,7 +79,11 @@ export default function DashboardScreen() {
|
||||
<View style={styles.sectionHeader}>
|
||||
<SectionTitle>Dernières saisies</SectionTitle>
|
||||
{recentSaisies.length > 0 && (
|
||||
<Text style={styles.seeAll} onPress={() => router.push('/(tabs)/journal')}>
|
||||
<Text
|
||||
style={styles.seeAll}
|
||||
onPress={() => router.push('/(tabs)/journal')}
|
||||
accessibilityRole="link"
|
||||
>
|
||||
Tout voir
|
||||
</Text>
|
||||
)}
|
||||
@@ -97,7 +107,7 @@ export default function DashboardScreen() {
|
||||
padding={Spacing.md}
|
||||
style={styles.saisieCard}
|
||||
>
|
||||
<View style={[styles.emotionIcon, { backgroundColor: `${saisie.emotion?.couleur}20` }]}>
|
||||
<View style={[styles.emotionIcon, { backgroundColor: `${saisie.emotion?.couleur || Colors.primary}20` }]}>
|
||||
<Text style={styles.emotionEmoji}>{saisie.emotion?.icone || '🔵'}</Text>
|
||||
</View>
|
||||
<View style={styles.saisieContent}>
|
||||
@@ -132,9 +142,9 @@ const styles = StyleSheet.create({
|
||||
backgroundColor: Colors.primary,
|
||||
borderColor: Colors.primary,
|
||||
},
|
||||
actionIcon: { width: 56, height: 56, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' },
|
||||
actionIcon: { width: ACTION_ICON, height: ACTION_ICON, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' },
|
||||
actionIconPrimary: { backgroundColor: Colors.primaryDark },
|
||||
actionIconSecondary: { backgroundColor: '#E3E3FD' },
|
||||
actionIconSecondary: { backgroundColor: Colors.gray[100] },
|
||||
actionText: { fontSize: FontSize.sm, fontWeight: FontWeight.semibold, color: Colors.black },
|
||||
actionTextPrimary: { fontSize: FontSize.sm, fontWeight: FontWeight.bold, color: Colors.white },
|
||||
block: { marginBottom: Spacing.xl },
|
||||
@@ -153,8 +163,8 @@ const styles = StyleSheet.create({
|
||||
paddingTop: Spacing.xs,
|
||||
},
|
||||
saisieCard: { flexDirection: 'row', alignItems: 'center', gap: Spacing.md, marginBottom: Spacing.sm },
|
||||
emotionIcon: { width: 44, height: 44, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' },
|
||||
emotionEmoji: { fontSize: 22 },
|
||||
emotionIcon: { width: EMOTION_ICON, height: EMOTION_ICON, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' },
|
||||
emotionEmoji: { fontSize: EMOJI_SIZE },
|
||||
saisieContent: { flex: 1, gap: Spacing.xs, alignItems: 'flex-start' },
|
||||
saisieDate: { fontSize: FontSize.xs, color: Colors.gray[500] },
|
||||
});
|
||||
|
||||
@@ -31,7 +31,16 @@ export default function FeedDetailScreen() {
|
||||
);
|
||||
}
|
||||
|
||||
const texteNettoye = (feed.contenu ?? '').replace(/<[^>]*>/g, '');
|
||||
const texteNettoye = (feed.contenu ?? '')
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/'|'/g, "'")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/\n{3,}/g, '\n\n')
|
||||
.trim();
|
||||
|
||||
return (
|
||||
<View style={styles.fill}>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { useFeeds } from '@/hooks/useFeeds';
|
||||
import { Colors } from '@/lib/colors';
|
||||
import { FontSize, FontWeight, Radius, Spacing } from '@/lib/theme';
|
||||
import { FontSize, FontWeight, Radius, Spacing, Tint } from '@/lib/theme';
|
||||
import { AppBar, Card, EmptyState, Loader } from '@/components/ui';
|
||||
import type { Feed } from '@/types';
|
||||
|
||||
@@ -12,7 +12,14 @@ export default function InfoListScreen() {
|
||||
const router = useRouter();
|
||||
|
||||
const renderItem = ({ item }: { item: Feed }) => {
|
||||
const extrait = (item.contenu ?? '').replace(/<[^>]*>/g, '').trim();
|
||||
const extrait = (item.contenu ?? '')
|
||||
.replace(/<[^>]*>/g, '')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/'|'/g, "'")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
return (
|
||||
<Card onPress={() => router.push(`/(tabs)/info/${item.slug}`)} style={styles.card}>
|
||||
<View style={styles.cardRow}>
|
||||
@@ -73,7 +80,7 @@ const styles = StyleSheet.create({
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: Radius.sm,
|
||||
backgroundColor: '#E3E3FD',
|
||||
backgroundColor: Tint.primary,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
@@ -6,12 +6,15 @@ import EmotionPicker from '@/components/EmotionPicker';
|
||||
import { IntensityPicker } from './new';
|
||||
import { Colors } from '@/lib/colors';
|
||||
import { Spacing, FontSize, FontWeight } from '@/lib/theme';
|
||||
import { Button, TextField, Loader } from '@/components/ui';
|
||||
import { Button, TextField, Loader, EmptyState } from '@/components/ui';
|
||||
|
||||
/** Hauteur de ligne du grand chiffre d'intensité. */
|
||||
const INTENSITY_LINE_HEIGHT = 56;
|
||||
|
||||
export default function EditEntryScreen() {
|
||||
const router = useRouter();
|
||||
const { id } = useLocalSearchParams<{ id: string }>();
|
||||
const { data: saisies } = useSaisies();
|
||||
const { data: saisies, isLoading } = useSaisies();
|
||||
const updateSaisie = useUpdateSaisie();
|
||||
|
||||
const saisie = saisies?.find((s) => s.id === Number(id));
|
||||
@@ -46,10 +49,24 @@ export default function EditEntryScreen() {
|
||||
}
|
||||
};
|
||||
|
||||
if (!saisie) {
|
||||
if (isLoading) {
|
||||
return <Loader label="Chargement…" />;
|
||||
}
|
||||
|
||||
if (!saisie) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<EmptyState
|
||||
icon="alert-circle-outline"
|
||||
title="Saisie introuvable"
|
||||
message="Cette saisie n'existe plus ou a été supprimée."
|
||||
actionLabel="Retour"
|
||||
onAction={() => router.back()}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
{/* Émotion */}
|
||||
@@ -106,7 +123,7 @@ const styles = StyleSheet.create({
|
||||
section: { marginBottom: Spacing.xl },
|
||||
label: { fontSize: FontSize.body, fontWeight: FontWeight.bold, color: Colors.black, marginBottom: Spacing.md },
|
||||
intensityContainer: { flexDirection: 'row', alignItems: 'baseline', justifyContent: 'center', marginBottom: Spacing.lg },
|
||||
intensityValue: { fontSize: FontSize.display, fontWeight: FontWeight.heavy, color: Colors.primary, lineHeight: 56 },
|
||||
intensityValue: { fontSize: FontSize.display, fontWeight: FontWeight.heavy, color: Colors.primary, lineHeight: INTENSITY_LINE_HEIGHT },
|
||||
intensityUnit: { fontSize: FontSize.xl, color: Colors.gray[400], marginLeft: Spacing.xs },
|
||||
scaleLabels: { flexDirection: 'row', justifyContent: 'space-between', marginTop: Spacing.md },
|
||||
scaleLabel: { fontSize: FontSize.xs, color: Colors.gray[500] },
|
||||
|
||||
@@ -8,6 +8,16 @@ import { Spacing, Radius, FontSize, FontWeight, Shadow, MIN_TOUCH, HIT_SLOP } fr
|
||||
import { AppBar, Card, Badge, EmptyState, Loader } from '@/components/ui';
|
||||
import type { SaisieTracker } from '@/types';
|
||||
|
||||
/** Dimensions locales (multiples de l'échelle 4px du DSFR). */
|
||||
const EMOTION_ICON = 48;
|
||||
const EMOJI_SIZE = 24;
|
||||
const ACTION_ICON_SIZE = 20;
|
||||
const FAB_SIZE = 56;
|
||||
const FAB_ICON = 30;
|
||||
const BAR_HEIGHT = 6;
|
||||
/** Marge basse de la liste : laisse passer le FAB sans qu'il chevauche le contenu. */
|
||||
const LIST_BOTTOM_GAP = FAB_SIZE + Spacing.xl + Spacing.lg;
|
||||
|
||||
export default function JournalScreen() {
|
||||
const router = useRouter();
|
||||
const { data: saisies, isLoading, refetch } = useSaisies();
|
||||
@@ -39,7 +49,7 @@ export default function JournalScreen() {
|
||||
|
||||
const renderItem = ({ item }: { item: SaisieTracker }) => (
|
||||
<Card padding={Spacing.lg} style={styles.saisieCard}>
|
||||
<View style={[styles.emotionIcon, { backgroundColor: `${item.emotion?.couleur}20` }]}>
|
||||
<View style={[styles.emotionIcon, { backgroundColor: `${item.emotion?.couleur || Colors.primary}20` }]}>
|
||||
<Text style={styles.emotionEmoji}>{item.emotion?.icone || '🔵'}</Text>
|
||||
</View>
|
||||
<View style={styles.saisieContent}>
|
||||
@@ -79,7 +89,7 @@ export default function JournalScreen() {
|
||||
accessibilityLabel="Modifier la saisie"
|
||||
onPress={() => router.push({ pathname: '/(tabs)/journal/edit', params: { id: String(item.id) } })}
|
||||
>
|
||||
<Ionicons name="create-outline" size={20} color={Colors.primary} />
|
||||
<Ionicons name="create-outline" size={ACTION_ICON_SIZE} color={Colors.primary} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.actionBtn}
|
||||
@@ -87,7 +97,7 @@ export default function JournalScreen() {
|
||||
accessibilityLabel="Supprimer la saisie"
|
||||
onPress={() => handleDelete(item.id)}
|
||||
>
|
||||
<Ionicons name="trash-outline" size={20} color={Colors.error} />
|
||||
<Ionicons name="trash-outline" size={ACTION_ICON_SIZE} color={Colors.error} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</Card>
|
||||
@@ -120,10 +130,12 @@ export default function JournalScreen() {
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={styles.fab}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="Nouvelle saisie"
|
||||
hitSlop={HIT_SLOP}
|
||||
onPress={() => router.push('/(tabs)/journal/new')}
|
||||
>
|
||||
<Ionicons name="add" size={30} color={Colors.white} />
|
||||
<Ionicons name="add" size={FAB_ICON} color={Colors.white} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
@@ -131,15 +143,15 @@ export default function JournalScreen() {
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: { flex: 1, backgroundColor: Colors.gray[50] },
|
||||
listContent: { padding: Spacing.lg, paddingBottom: 96 },
|
||||
listContent: { padding: Spacing.lg, paddingBottom: LIST_BOTTOM_GAP },
|
||||
saisieCard: { flexDirection: 'row', alignItems: 'flex-start', gap: Spacing.md, marginBottom: Spacing.md },
|
||||
emotionIcon: { width: 48, height: 48, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' },
|
||||
emotionEmoji: { fontSize: 24 },
|
||||
emotionIcon: { width: EMOTION_ICON, height: EMOTION_ICON, borderRadius: Radius.sm, justifyContent: 'center', alignItems: 'center' },
|
||||
emotionEmoji: { fontSize: EMOJI_SIZE },
|
||||
saisieContent: { flex: 1 },
|
||||
saisieHeader: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: Spacing.sm, flexWrap: 'wrap' },
|
||||
intensityText: { fontSize: FontSize.xs, color: Colors.gray[500] },
|
||||
intensityBar: { height: 6, backgroundColor: Colors.gray[200], borderRadius: Radius.sm, marginBottom: Spacing.sm },
|
||||
intensityFill: { height: 6, borderRadius: Radius.sm },
|
||||
intensityBar: { height: BAR_HEIGHT, backgroundColor: Colors.gray[200], borderRadius: Radius.sm, marginBottom: Spacing.sm },
|
||||
intensityFill: { height: BAR_HEIGHT, borderRadius: Radius.sm },
|
||||
note: { fontSize: FontSize.xs, color: Colors.gray[600], marginBottom: Spacing.xs, lineHeight: 19 },
|
||||
date: { fontSize: FontSize.xs, color: Colors.gray[400], textTransform: 'capitalize' },
|
||||
actions: { gap: Spacing.sm },
|
||||
@@ -153,9 +165,9 @@ const styles = StyleSheet.create({
|
||||
fab: {
|
||||
position: 'absolute',
|
||||
right: Spacing.lg,
|
||||
bottom: Spacing.lg,
|
||||
width: 56,
|
||||
height: 56,
|
||||
bottom: Spacing.xl,
|
||||
width: FAB_SIZE,
|
||||
height: FAB_SIZE,
|
||||
borderRadius: Radius.sm,
|
||||
backgroundColor: Colors.primary,
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -8,6 +8,14 @@ import { Colors } from '@/lib/colors';
|
||||
import { Spacing, Radius, FontSize, FontWeight } from '@/lib/theme';
|
||||
import { Button, TextField } from '@/components/ui';
|
||||
|
||||
/** Dimensions locales (multiples de l'échelle 4px du DSFR). */
|
||||
const STEP_CIRCLE = 36;
|
||||
const STEP_LINE_WIDTH = 40;
|
||||
const STEP_LINE_HEIGHT = 3;
|
||||
const STEP_CHECK_ICON = 18;
|
||||
const INTENSITY_LINE_HEIGHT = 60;
|
||||
const DOT_SIZE = 44;
|
||||
|
||||
export default function NewEntryScreen() {
|
||||
const router = useRouter();
|
||||
const createSaisie = useCreateSaisie();
|
||||
@@ -34,12 +42,16 @@ export default function NewEntryScreen() {
|
||||
return (
|
||||
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
|
||||
{/* Stepper */}
|
||||
<View style={styles.stepper}>
|
||||
<View
|
||||
style={styles.stepper}
|
||||
accessibilityRole="progressbar"
|
||||
accessibilityLabel={`Étape ${step} sur 3`}
|
||||
>
|
||||
{[1, 2, 3].map((s) => (
|
||||
<View key={s} style={styles.stepRow}>
|
||||
<View style={[styles.stepCircle, step >= s && styles.stepActive]}>
|
||||
{step > s ? (
|
||||
<Ionicons name="checkmark" size={18} color={Colors.white} />
|
||||
<Ionicons name="checkmark" size={STEP_CHECK_ICON} color={Colors.white} />
|
||||
) : (
|
||||
<Text style={[styles.stepNumber, step >= s && styles.stepNumberActive]}>{s}</Text>
|
||||
)}
|
||||
@@ -123,6 +135,8 @@ export function IntensityPicker({ value, onChange }: { value: number; onChange:
|
||||
key={v}
|
||||
style={[pickerStyles.dot, active && pickerStyles.dotActive]}
|
||||
onPress={() => onChange(v)}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: active }}
|
||||
accessibilityLabel={`Intensité ${v}`}
|
||||
>
|
||||
<Text style={[pickerStyles.dotText, active && pickerStyles.dotTextActive]}>{v}</Text>
|
||||
@@ -136,8 +150,8 @@ export function IntensityPicker({ value, onChange }: { value: number; onChange:
|
||||
const pickerStyles = StyleSheet.create({
|
||||
grid: { flexDirection: 'row', flexWrap: 'wrap', gap: Spacing.sm, justifyContent: 'center' },
|
||||
dot: {
|
||||
width: 44,
|
||||
height: 44,
|
||||
width: DOT_SIZE,
|
||||
height: DOT_SIZE,
|
||||
borderRadius: Radius.sm,
|
||||
backgroundColor: Colors.gray[100],
|
||||
borderWidth: 1,
|
||||
@@ -156,8 +170,8 @@ const styles = StyleSheet.create({
|
||||
stepper: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', marginBottom: Spacing.xxl },
|
||||
stepRow: { flexDirection: 'row', alignItems: 'center' },
|
||||
stepCircle: {
|
||||
width: 36,
|
||||
height: 36,
|
||||
width: STEP_CIRCLE,
|
||||
height: STEP_CIRCLE,
|
||||
borderRadius: Radius.pill,
|
||||
backgroundColor: Colors.gray[200],
|
||||
justifyContent: 'center',
|
||||
@@ -166,11 +180,11 @@ const styles = StyleSheet.create({
|
||||
stepActive: { backgroundColor: Colors.primary },
|
||||
stepNumber: { fontSize: FontSize.md, fontWeight: FontWeight.bold, color: Colors.gray[500] },
|
||||
stepNumberActive: { color: Colors.white },
|
||||
stepLine: { width: 40, height: 3, backgroundColor: Colors.gray[200], marginHorizontal: Spacing.xs },
|
||||
stepLine: { width: STEP_LINE_WIDTH, height: STEP_LINE_HEIGHT, backgroundColor: Colors.gray[200], marginHorizontal: Spacing.xs },
|
||||
stepLineActive: { backgroundColor: Colors.primary },
|
||||
stepTitle: { fontSize: FontSize.xl, fontWeight: FontWeight.bold, color: Colors.black, marginBottom: Spacing.xl },
|
||||
intensityContainer: { flexDirection: 'row', alignItems: 'baseline', justifyContent: 'center', marginBottom: Spacing.xl },
|
||||
intensityValue: { fontSize: FontSize.display, fontWeight: FontWeight.heavy, color: Colors.primary, lineHeight: 60 },
|
||||
intensityValue: { fontSize: FontSize.display, fontWeight: FontWeight.heavy, color: Colors.primary, lineHeight: INTENSITY_LINE_HEIGHT },
|
||||
intensityLabel: { fontSize: FontSize.xxl, color: Colors.gray[400], marginLeft: Spacing.xs },
|
||||
scaleLabels: { flexDirection: 'row', justifyContent: 'space-between', marginTop: Spacing.md, marginBottom: Spacing.xxl },
|
||||
scaleLabel: { fontSize: FontSize.xs, color: Colors.gray[500] },
|
||||
|
||||
@@ -3,10 +3,15 @@ import { View, Text, StyleSheet, ScrollView, TouchableOpacity } from 'react-nati
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useRapport } from '@/hooks/useRapport';
|
||||
import { Colors } from '@/lib/colors';
|
||||
import { Spacing, Radius, FontSize, FontWeight } from '@/lib/theme';
|
||||
import { Spacing, Radius, FontSize, FontWeight, MIN_TOUCH } from '@/lib/theme';
|
||||
import { AppBar, Card, Badge, SectionTitle, EmptyState, Loader } from '@/components/ui';
|
||||
import EmotionPieChart from '@/components/EmotionPieChart';
|
||||
|
||||
/** Dimensions locales (multiples de l'échelle 4px du DSFR). */
|
||||
const STAT_ICON = 20;
|
||||
const SEGMENT_MIN_HEIGHT = MIN_TOUCH;
|
||||
const BAR_HEIGHT = 6;
|
||||
|
||||
type Period = 'week' | 'month' | 'quarter' | 'year';
|
||||
|
||||
const PERIODS: { value: Period; label: string }[] = [
|
||||
@@ -33,6 +38,8 @@ export default function ReportsScreen() {
|
||||
key={p.value}
|
||||
style={[styles.segmentBtn, active && styles.segmentBtnActive]}
|
||||
onPress={() => setPeriod(p.value)}
|
||||
accessibilityRole="button"
|
||||
accessibilityState={{ selected: active }}
|
||||
accessibilityLabel={p.label}
|
||||
>
|
||||
<Text style={[styles.segmentText, active && styles.segmentTextActive]}>{p.label}</Text>
|
||||
@@ -49,13 +56,13 @@ export default function ReportsScreen() {
|
||||
<Card padding={Spacing.lg} style={styles.statsCard}>
|
||||
<View style={styles.statsRow}>
|
||||
<View style={styles.statCell}>
|
||||
<Ionicons name="bar-chart" size={20} color={Colors.primary} />
|
||||
<Ionicons name="bar-chart" size={STAT_ICON} color={Colors.primary} />
|
||||
<Text style={styles.statValue}>{rapport.stats.total_saisies}</Text>
|
||||
<Text style={styles.statLabel}>Saisies</Text>
|
||||
</View>
|
||||
<View style={styles.statDivider} />
|
||||
<View style={styles.statCell}>
|
||||
<Ionicons name="trending-up" size={20} color={Colors.primary} />
|
||||
<Ionicons name="trending-up" size={STAT_ICON} color={Colors.primary} />
|
||||
<Text style={styles.statValue}>{rapport.stats.intensite_moyenne?.toFixed(1) || '—'}</Text>
|
||||
<Text style={styles.statLabel}>Intensité moy.</Text>
|
||||
</View>
|
||||
@@ -134,7 +141,7 @@ const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
content: { padding: Spacing.lg, paddingBottom: Spacing.xxxl },
|
||||
segment: { flexDirection: 'row', backgroundColor: Colors.gray[100], borderRadius: Radius.sm, padding: Spacing.xs, marginBottom: Spacing.lg },
|
||||
segmentBtn: { flex: 1, paddingVertical: Spacing.sm, alignItems: 'center', borderRadius: Radius.sm, minHeight: 40, justifyContent: 'center' },
|
||||
segmentBtn: { flex: 1, paddingVertical: Spacing.sm, alignItems: 'center', borderRadius: Radius.sm, minHeight: SEGMENT_MIN_HEIGHT, justifyContent: 'center' },
|
||||
segmentBtnActive: { backgroundColor: Colors.primary },
|
||||
segmentText: { fontSize: FontSize.xs, fontWeight: FontWeight.semibold, color: Colors.gray[500] },
|
||||
segmentTextActive: { color: Colors.white },
|
||||
@@ -152,8 +159,8 @@ const styles = StyleSheet.create({
|
||||
repItemBorder: { borderTopWidth: 1, borderTopColor: Colors.gray[200] },
|
||||
repHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: Spacing.sm },
|
||||
repPct: { fontSize: FontSize.sm, fontWeight: FontWeight.bold, color: Colors.black },
|
||||
progressBar: { height: 6, backgroundColor: Colors.gray[200], borderRadius: Radius.sm, marginBottom: Spacing.xs },
|
||||
progressFill: { height: 6, borderRadius: Radius.sm },
|
||||
progressBar: { height: BAR_HEIGHT, backgroundColor: Colors.gray[200], borderRadius: Radius.sm, marginBottom: Spacing.xs },
|
||||
progressFill: { height: BAR_HEIGHT, borderRadius: Radius.sm },
|
||||
repFooter: { flexDirection: 'row', justifyContent: 'space-between' },
|
||||
repDetail: { fontSize: FontSize.xs, color: Colors.gray[500] },
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useRouter } from 'expo-router';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { useAuthStore } from '@/lib/auth-store';
|
||||
import { Colors } from '@/lib/colors';
|
||||
import { FontSize, FontWeight, MIN_TOUCH, Radius, Spacing } from '@/lib/theme';
|
||||
import { FontSize, FontWeight, HIT_SLOP, MIN_TOUCH, Radius, Spacing, Tint } from '@/lib/theme';
|
||||
import { AppBar, Badge, Button, Card, SectionTitle } from '@/components/ui';
|
||||
|
||||
export default function ProfileScreen() {
|
||||
@@ -101,7 +101,12 @@ function MenuItem({
|
||||
last?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<TouchableOpacity style={[styles.menuItem, last && styles.menuItemLast]} accessibilityRole="button">
|
||||
<TouchableOpacity
|
||||
style={[styles.menuItem, last && styles.menuItemLast]}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={label}
|
||||
hitSlop={HIT_SLOP}
|
||||
>
|
||||
<Ionicons name={icon} size={20} color={Colors.gray[600]} />
|
||||
<Text style={styles.menuLabel}>{label}</Text>
|
||||
<Ionicons name="chevron-forward" size={18} color={Colors.gray[400]} />
|
||||
@@ -133,7 +138,7 @@ const styles = StyleSheet.create({
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: Radius.sm,
|
||||
backgroundColor: '#E3E3FD',
|
||||
backgroundColor: Tint.primary,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user