Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions front/src/components/common/ActionSheetModal.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { View, Text, StyleSheet, Modal, TouchableOpacity } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Colors } from '../../constants/theme';

// ─── ActionSheetModal ─────────────────────────────────────────────────────────
Expand All @@ -16,14 +17,16 @@ import { Colors } from '../../constants/theme';
// onClose () => void — appelé après tout choix (y compris Annuler)

export default function ActionSheetModal({ visible, title, options = [], cancelLabel = 'Annuler', onClose }) {
const insets = useSafeAreaInsets();

const handlePress = (opt) => {
onClose?.();
opt.onPress?.();
};

return (
<Modal visible={visible} transparent animationType="fade" statusBarTranslucent onRequestClose={onClose}>
<View style={styles.backdrop}>
<View style={[styles.backdrop, { paddingBottom: Math.max(12, insets.bottom + 8) }]}>
<View style={styles.card}>
{!!title && <Text style={styles.title} numberOfLines={2}>{title}</Text>}

Expand Down Expand Up @@ -52,7 +55,12 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: 'rgba(0,0,0,0.82)',
justifyContent: 'flex-end',
padding: 12,
paddingHorizontal: 12,
// zIndex/elevation explicites : garantit que la feuille passe au-dessus de
// tout autre contenu positionné (barre d'onglets, headers) — notamment sur
// web où le portail du Modal n'a pas de z-index par défaut.
zIndex: 999,
elevation: 999,
},
card: {
width: '100%',
Expand All @@ -63,7 +71,6 @@ const styles = StyleSheet.create({
paddingTop: 18,
paddingHorizontal: 8,
paddingBottom: 8,
marginBottom: 8,
},
title: {
color: Colors.textMuted,
Expand Down
161 changes: 161 additions & 0 deletions front/src/components/workouts/SaveWorkoutPromptModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React from 'react';
import { View, Text, StyleSheet, Modal, TouchableOpacity } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { Colors } from '../../constants/theme';

// ─── SaveWorkoutPromptModal ───────────────────────────────────────────────────
// Interception du lancement d'une séance sur-mesure (WorkoutBuilderScreen) :
// avant d'ouvrir le chrono, propose de sauvegarder la séance générée comme
// template réutilisable. Deux CTA de poids égal (contrairement à
// ConfirmModal/InfoModal qui n'ont qu'une seule action principale) + une
// icône de fermeture pour annuler sans choisir.
//
// Props :
// visible bool
// onSaveAndLaunch () => void — sauvegarde le template PUIS lance la séance
// onLaunchWithoutSave () => void — lance directement, aucun template créé
// onDismiss () => void — ferme la popup sans lancer la séance
// saving bool — désactive les actions pendant la sauvegarde

export default function SaveWorkoutPromptModal({
visible, onSaveAndLaunch, onLaunchWithoutSave, onDismiss, saving = false,
}) {
return (
<Modal visible={visible} transparent animationType="fade" statusBarTranslucent onRequestClose={onDismiss}>
<View style={styles.backdrop}>
<View style={styles.card}>
<TouchableOpacity
style={styles.closeBtn}
onPress={onDismiss}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
activeOpacity={0.7}
>
<Ionicons name="close" size={18} color={Colors.textMuted} />
</TouchableOpacity>

<View style={styles.iconWrap}>
<Ionicons name="bookmark-outline" size={28} color={Colors.primary} />
</View>

<Text style={styles.title}>Sauvegarder cette séance ?</Text>
<Text style={styles.body}>
Souhaites-tu sauvegarder cette séance sur-mesure pour la retrouver plus tard ?
</Text>

<TouchableOpacity
style={[styles.primaryBtn, saving && styles.btnDisabled]}
onPress={onSaveAndLaunch}
disabled={saving}
activeOpacity={0.85}
>
<Ionicons name="bookmark" size={16} color="#fff" style={{ marginRight: 8 }} />
<Text style={styles.primaryTxt}>
{saving ? 'Sauvegarde...' : 'Sauvegarder et lancer la séance'}
</Text>
</TouchableOpacity>

<TouchableOpacity
style={[styles.secondaryBtn, saving && styles.btnDisabled]}
onPress={onLaunchWithoutSave}
disabled={saving}
activeOpacity={0.85}
>
<Ionicons name="play-outline" size={16} color={Colors.textPrimary} style={{ marginRight: 8 }} />
<Text style={styles.secondaryTxt}>Lancer sans sauvegarder</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
);
}

const styles = StyleSheet.create({
backdrop: {
flex: 1,
backgroundColor: 'rgba(0,0,0,0.82)',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 24,
},
card: {
width: '100%',
backgroundColor: Colors.bgDeep2,
borderRadius: 22,
borderWidth: 1,
borderColor: `${Colors.primary}38`,
padding: 28,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 16 },
shadowOpacity: 0.65,
shadowRadius: 32,
elevation: 20,
},
closeBtn: {
position: 'absolute',
top: 16,
right: 16,
width: 28,
height: 28,
borderRadius: 14,
backgroundColor: Colors.glassBg,
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
},
iconWrap: {
width: 60,
height: 60,
borderRadius: 18,
borderWidth: 1,
borderColor: `${Colors.primary}45`,
backgroundColor: `${Colors.primary}1A`,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 18,
},
title: {
color: Colors.textPrimary,
fontSize: 18,
fontWeight: '800',
letterSpacing: -0.3,
marginBottom: 10,
textAlign: 'center',
},
body: {
color: Colors.textSecondary,
fontSize: 14,
lineHeight: 21,
textAlign: 'center',
marginBottom: 24,
},
primaryBtn: {
flexDirection: 'row',
width: '100%',
height: 50,
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 10,
backgroundColor: Colors.primary,
shadowColor: Colors.primary,
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.40,
shadowRadius: 12,
elevation: 6,
},
primaryTxt: { color: '#fff', fontSize: 15, fontWeight: '700', letterSpacing: 0.2 },
secondaryBtn: {
flexDirection: 'row',
width: '100%',
height: 50,
borderRadius: 13,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: Colors.borderDim,
},
secondaryTxt: { color: Colors.textPrimary, fontSize: 15, fontWeight: '700', letterSpacing: 0.2 },
btnDisabled: { opacity: 0.5 },
});
6 changes: 2 additions & 4 deletions front/src/data/exerciseCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ export const BUILTIN_CATALOG = [
exo('Élévations latérales unilatérales poulie', 'epaules', 'Deltoïde latéral', [], ['Câble'], 'intermediaire'),
exo('Oiseau haltères', 'epaules', 'Deltoïde postérieur', ['Trapèzes', 'Rhomboïdes'], ['Haltères'], 'intermediaire', { videoQuery: 'oiseau haltères deltoïde postérieur' }),
exo('Oiseau buste penché', 'epaules', 'Deltoïde postérieur', ['Trapèzes'], ['Haltères'], 'intermediaire'),
exo('Oiseau machine', 'epaules', 'Deltoïde postérieur', ['Trapèzes'], ['Machine'], 'debutant'),
exo('Reverse pec deck', 'epaules', 'Deltoïde postérieur', ['Trapèzes'], ['Machine'], 'debutant'),
exo('Face pull', 'epaules', 'Deltoïde postérieur', ['Trapèzes', 'Rhomboïdes'], ['Câble'], 'debutant'),
exo('Rowing menton', 'epaules', 'Deltoïde latéral', ['Trapèzes'], ['Barre'], 'intermediaire'),
Expand Down Expand Up @@ -162,6 +161,7 @@ export const BUILTIN_CATALOG = [
exo('Triceps poulie corde', 'bras', 'Triceps', [], ['Câble'], 'debutant'),
exo('Triceps poulie inversé', 'bras', 'Triceps', [], ['Câble'], 'debutant'),
exo('Dips', 'bras', 'Triceps', ['Pectoraux bas', 'Deltoïde antérieur'], ['Poids du corps'], 'intermediaire'),
exo('Dips assistés machine', 'bras', 'Triceps', ['Pectoraux bas', 'Deltoïde antérieur'], ['Machine'], 'debutant'),
exo('Dips bench', 'bras', 'Triceps', ['Deltoïde antérieur'], ['Poids du corps'], 'debutant'),
exo('Pompes diamant', 'bras', 'Triceps', ['Pectoraux milieu'], ['Poids du corps'], 'intermediaire'),
exo('Kickback haltère', 'bras', 'Triceps', [], ['Haltères'], 'debutant'),
Expand All @@ -186,7 +186,7 @@ export const BUILTIN_CATALOG = [
exo('Step-up', 'jambes', 'Quadriceps', ['Fessiers'], ['Haltères'], 'debutant'),
exo('Wall sit', 'jambes', 'Quadriceps', [], ['Poids du corps'], 'debutant'),
exo('Leg extension', 'jambes', 'Quadriceps', [], ['Machine'], 'debutant'),
exo('Presse à cuisses', 'jambes', 'Quadriceps', ['Fessiers'], ['Machine'], 'debutant'),
exo('Leg Press', 'jambes', 'Quadriceps', ['Fessiers'], ['Machine'], 'debutant'),
exo('Presse à cuisses inclinée', 'jambes', 'Quadriceps', ['Fessiers'], ['Machine'], 'debutant'),
exo('Fentes marchées', 'jambes', 'Quadriceps', ['Fessiers', 'Ischios'], ['Haltères'], 'intermediaire'),
exo('Fentes inversées', 'jambes', 'Quadriceps', ['Fessiers'], ['Haltères'], 'debutant'),
Expand Down Expand Up @@ -348,7 +348,6 @@ export const BUILTIN_CATALOG = [
exo('Around the world plank', 'abdos', 'Transverse', ['Obliques'], ['Poids du corps'], 'intermediaire'),

// ─── Variantes complémentaires ────────────────────────────────────────── (+12)
exo('Pec deck inversé', 'epaules', 'Deltoïde postérieur', ['Trapèzes'], ['Machine'], 'debutant'),
exo('Développé poitrine machine convergente', 'pectoraux', 'Pectoraux milieu', ['Triceps'], ['Machine'], 'debutant'),
exo('Pull-down poignée corde', 'dos', 'Grand dorsal', ['Biceps'], ['Câble'], 'debutant'),
exo('Tractions assistées machine', 'dos', 'Grand dorsal', ['Biceps'], ['Machine'], 'debutant'),
Expand Down Expand Up @@ -445,7 +444,6 @@ export const BUILTIN_CATALOG = [
exo('Walking lunge poids du corps', 'jambes', 'Quadriceps', ['Fessiers', 'Ischios'], ['Poids du corps'], 'debutant'),
exo('Step-up latéral', 'jambes', 'Fessiers', ['Quadriceps'], ['Haltères'], 'debutant'),
exo('Romanian deadlift unilatéral haltère', 'jambes', 'Ischios', ['Fessiers'], ['Haltères'], 'avance'),
exo('Single leg leg press', 'jambes', 'Quadriceps', ['Fessiers'], ['Machine'], 'debutant'),
exo('Single leg curl debout', 'jambes', 'Ischios', [], ['Machine'], 'debutant'),
exo('Goblet squat plate', 'jambes', 'Quadriceps', ['Fessiers'], ['Poids du corps'], 'debutant'),
exo('Cable squat', 'jambes', 'Quadriceps', ['Fessiers'], ['Câble'], 'debutant'),
Expand Down
13 changes: 9 additions & 4 deletions front/src/data/trophyCatalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,11 +264,16 @@ export const TROPHY_CATEGORIES = [
{ id: 'special', label: 'Spécial', icon: 'gift', color: '#FE7439' },
];

// Chaque catégorie (locale + backend via BACKEND_CATEGORY_MAP) doit apparaître
// dans exactement un onglet ci-dessous — pas de doublon, pas de trou — pour que
// "Tous" et la somme des onglets filtrés restent toujours cohérents.
export const TROPHY_FILTER_TABS = [
{ id: 'all', label: 'Tous' },
{ id: 'force', label: 'Force', categories: ['force', 'corps'] },
{ id: 'endurance', label: 'Endurance', categories: ['heritage', 'exploration', 'regularite'] },
{ id: 'special', label: 'Spécial', categories: ['secret', 'social', 'special'] },
{ id: 'all', label: 'Tous' },
{ id: 'force', label: 'Force', categories: ['force', 'corps'] },
{ id: 'endurance', label: 'Endurance', categories: ['heritage', 'exploration', 'regularite'] },
{ id: 'social', label: 'Social & Multi', categories: ['social'] },
{ id: 'collection', label: 'Coffres & Raretés', categories: ['collection'] },
{ id: 'special', label: 'Spécial', categories: ['secret', 'special'] },
];

// ─── Helper: évalue le catalogue avec overrides (godMode console) ─────────────
Expand Down
2 changes: 1 addition & 1 deletion front/src/data/tutorialChapters.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const TUTORIAL_CHAPTERS = [
{
key: 'trophies_filters',
title: 'Filtrer par Catégorie',
body: "Les onglets 'Force', 'Endurance' et 'Spécial' filtrent les trophées. Les trophées verrouillés montrent leur condition.",
body: "Fais défiler les onglets 'Force', 'Endurance', 'Social & Multi', 'Coffres & Raretés' et 'Spécial' pour filtrer les trophées. Les trophées verrouillés montrent leur condition.",
targetKey: 'trophies_filters', position: 'bottom', scrollY: 0,
},
{
Expand Down
2 changes: 1 addition & 1 deletion front/src/data/workoutTemplates.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const TEMPLATES = [
videoQuery: 'soulevé de terre roumain',
}),
buildExercise({
name: 'Presse à cuisses',
name: 'Leg Press',
targetMuscle: 'Quadriceps',
secondaryMuscles: ['Fessiers'],
equipment: ['Machine'],
Expand Down
49 changes: 26 additions & 23 deletions front/src/screens/Profile/TrophyRoomScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,12 +226,9 @@ export default function TrophyRoomScreen({ navigation }) {

const allCategories = useMemo(() => [...TROPHY_CATEGORIES, COLLECTION_CATEGORY], []);

// Rattache "Collection" au filtre "Spécial", sans muter les données source.
const filterTabs = useMemo(() => TROPHY_FILTER_TABS.map((tab) => (
tab.id === 'special' && tab.categories
? { ...tab, categories: [...tab.categories, 'collection'] }
: tab
)), []);
// TROPHY_FILTER_TABS couvre déjà nativement "collection" via son propre
// onglet dédié ("Coffres & Raretés") — plus besoin de patch ad-hoc ici.
const filterTabs = TROPHY_FILTER_TABS;

const visibleCategories = useMemo(() => {
if (activeFilter === 'all') return allCategories;
Expand Down Expand Up @@ -294,21 +291,27 @@ export default function TrophyRoomScreen({ navigation }) {
<View style={{ width: 38 }} />
</View>

{/* Filter tabs */}
<View style={styles.filterRow} ref={filtersRef} onLayout={onFiltersLayout} collapsable={false}>
{filterTabs.map((tab) => {
const active = activeFilter === tab.id;
return (
<TouchableOpacity
key={tab.id}
style={[styles.filterTab, active && styles.filterTabActive]}
onPress={() => setFilter(tab.id)}
activeOpacity={0.75}
>
<Text style={[styles.filterLabel, active && styles.filterLabelActive]}>{tab.label}</Text>
</TouchableOpacity>
);
})}
{/* Filter tabs — scroll horizontal (6 onglets ne tiennent plus à plat) */}
<View ref={filtersRef} onLayout={onFiltersLayout} collapsable={false}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{filterTabs.map((tab) => {
const active = activeFilter === tab.id;
return (
<TouchableOpacity
key={tab.id}
style={[styles.filterTab, active && styles.filterTabActive]}
onPress={() => setFilter(tab.id)}
activeOpacity={0.75}
>
<Text style={[styles.filterLabel, active && styles.filterLabelActive]}>{tab.label}</Text>
</TouchableOpacity>
);
})}
</ScrollView>
</View>

<ScrollView
Expand Down Expand Up @@ -511,8 +514,8 @@ const styles = StyleSheet.create({
headerTitle: { color: Colors.textPrimary, fontSize: 17, fontWeight: '800' },
headerSub: { color: Colors.textMuted, fontSize: 11, fontWeight: '600', marginTop: 2 },

filterRow: { flexDirection: 'row', marginHorizontal: 16, paddingBottom: 12, gap: 8 },
filterTab: { flex: 1, paddingVertical: 7, borderRadius: 10, alignItems: 'center', backgroundColor: 'rgba(255,255,255,0.04)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)' },
filterRow: { flexDirection: 'row', paddingHorizontal: 16, paddingBottom: 12, gap: 8 },
filterTab: { paddingVertical: 7, paddingHorizontal: 14, borderRadius: 10, alignItems: 'center', backgroundColor: 'rgba(255,255,255,0.04)', borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)' },
filterTabActive: { backgroundColor: 'rgba(254,116,57,0.14)', borderColor: Colors.primary + '50' },
filterLabel: { fontSize: 11, fontWeight: '700', color: Colors.textMuted, letterSpacing: 0.5 },
filterLabelActive: { color: Colors.primary },
Expand Down
Loading
Loading