204 lines
5.9 KiB
JavaScript
204 lines
5.9 KiB
JavaScript
import React, { useCallback, useRef, useState } from 'react';
|
||
import {
|
||
View,
|
||
Text,
|
||
FlatList,
|
||
TouchableOpacity,
|
||
StyleSheet,
|
||
ActivityIndicator,
|
||
} from 'react-native';
|
||
import { useFocusEffect } from '@react-navigation/native';
|
||
import { obtenirClasse, effectuerTirage, reTirer } from '../storage';
|
||
import { couleurs } from '../theme';
|
||
|
||
function formaterDate(iso) {
|
||
const d = new Date(iso);
|
||
const pad = (n) => String(n).padStart(2, '0');
|
||
return `${pad(d.getDate())}/${pad(d.getMonth() + 1)}/${d.getFullYear()} ${pad(
|
||
d.getHours()
|
||
)}:${pad(d.getMinutes())}`;
|
||
}
|
||
|
||
export default function DetailClasseScreen({ route, navigation }) {
|
||
const { classeId } = route.params;
|
||
const [classe, setClasse] = useState(null);
|
||
const [enCours, setEnCours] = useState(false);
|
||
// Ref en plus du state : le state ne se répercute qu'au prochain rendu,
|
||
// ce qui laisse une fenêtre où un second tap très rapide (avant que le
|
||
// bouton soit visuellement désactivé) pourrait lancer un 2e tirage pendant
|
||
// que le premier écrit encore dans le stockage. La ref, elle, est lue et
|
||
// mise à jour de façon synchrone, donc elle bloque le double-tap immédiatement.
|
||
const enCoursRef = useRef(false);
|
||
|
||
const recharger = useCallback(async () => {
|
||
const c = await obtenirClasse(classeId);
|
||
setClasse(c);
|
||
if (c) navigation.setOptions({ title: c.nom });
|
||
}, [classeId]);
|
||
|
||
useFocusEffect(
|
||
useCallback(() => {
|
||
recharger();
|
||
}, [recharger])
|
||
);
|
||
|
||
const tirer = async () => {
|
||
if (enCoursRef.current) return;
|
||
enCoursRef.current = true;
|
||
setEnCours(true);
|
||
try {
|
||
const c = await effectuerTirage(classeId);
|
||
setClasse(c);
|
||
} finally {
|
||
enCoursRef.current = false;
|
||
setEnCours(false);
|
||
}
|
||
};
|
||
|
||
const reTirerAction = async () => {
|
||
if (enCoursRef.current) return;
|
||
enCoursRef.current = true;
|
||
setEnCours(true);
|
||
try {
|
||
const c = await reTirer(classeId);
|
||
setClasse(c);
|
||
} finally {
|
||
enCoursRef.current = false;
|
||
setEnCours(false);
|
||
}
|
||
};
|
||
|
||
if (!classe) {
|
||
return (
|
||
<View style={[styles.container, styles.centre]}>
|
||
<ActivityIndicator />
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const dernierTirage = classe.tirages[0];
|
||
|
||
return (
|
||
<View style={styles.container}>
|
||
<Text style={styles.sousTitre}>
|
||
{classe.nombreEleves} élèves · pondération max ×{classe.ponderationMax}
|
||
</Text>
|
||
|
||
<View style={styles.cardTirage}>
|
||
<TouchableOpacity
|
||
style={[styles.bouton, enCours && styles.boutonDesactive]}
|
||
onPress={tirer}
|
||
disabled={enCours}
|
||
>
|
||
<Text style={styles.boutonTexte}>🎲 Tirer un élève au sort</Text>
|
||
</TouchableOpacity>
|
||
|
||
{dernierTirage && (
|
||
<>
|
||
<Text style={styles.numeroTire}>n°{dernierTirage.numeroEleve}</Text>
|
||
<TouchableOpacity
|
||
style={[styles.boutonSecondaire, enCours && styles.boutonDesactive]}
|
||
onPress={reTirerAction}
|
||
disabled={enCours}
|
||
>
|
||
<Text style={styles.boutonSecondaireTexte}>
|
||
🔁 Re-tirer (annule et retire)
|
||
</Text>
|
||
</TouchableOpacity>
|
||
</>
|
||
)}
|
||
</View>
|
||
|
||
<Text style={styles.titreHistorique}>Historique des tirages</Text>
|
||
<FlatList
|
||
data={classe.tirages}
|
||
keyExtractor={(item) => item.id}
|
||
contentContainerStyle={{ paddingBottom: 24 }}
|
||
ListEmptyComponent={
|
||
<Text style={styles.texteVide}>Aucun tirage pour le moment.</Text>
|
||
}
|
||
renderItem={({ item, index }) => (
|
||
<View style={styles.ligneHistorique}>
|
||
<View style={styles.badge}>
|
||
<Text style={styles.badgeTexte}>{item.numeroEleve}</Text>
|
||
</View>
|
||
<Text style={styles.dateHistorique}>{formaterDate(item.dateTirage)}</Text>
|
||
{index === 0 && <Text style={styles.etiquetteRecent}>dernier</Text>}
|
||
</View>
|
||
)}
|
||
/>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: { flex: 1, backgroundColor: couleurs.fond, padding: 16 },
|
||
centre: { justifyContent: 'center', alignItems: 'center' },
|
||
sousTitre: { color: couleurs.texteSecondaire, marginBottom: 14, fontSize: 14 },
|
||
cardTirage: {
|
||
backgroundColor: couleurs.carte,
|
||
borderRadius: 14,
|
||
padding: 20,
|
||
alignItems: 'center',
|
||
marginBottom: 22,
|
||
shadowColor: '#000',
|
||
shadowOpacity: 0.06,
|
||
shadowRadius: 4,
|
||
shadowOffset: { width: 0, height: 1 },
|
||
elevation: 1,
|
||
},
|
||
bouton: {
|
||
backgroundColor: couleurs.primaire,
|
||
borderRadius: 10,
|
||
paddingVertical: 14,
|
||
paddingHorizontal: 20,
|
||
alignItems: 'center',
|
||
width: '100%',
|
||
},
|
||
boutonDesactive: { opacity: 0.6 },
|
||
boutonTexte: { color: 'white', fontSize: 16, fontWeight: '600' },
|
||
numeroTire: {
|
||
fontSize: 52,
|
||
fontWeight: '800',
|
||
color: couleurs.primaire,
|
||
marginVertical: 14,
|
||
},
|
||
boutonSecondaire: {
|
||
borderRadius: 10,
|
||
paddingVertical: 12,
|
||
paddingHorizontal: 20,
|
||
alignItems: 'center',
|
||
width: '100%',
|
||
backgroundColor: couleurs.fond,
|
||
borderWidth: 1,
|
||
borderColor: couleurs.bordure,
|
||
},
|
||
boutonSecondaireTexte: { color: couleurs.texte, fontSize: 15, fontWeight: '600' },
|
||
titreHistorique: { fontSize: 16, fontWeight: '700', marginBottom: 10, color: couleurs.texte },
|
||
texteVide: { color: couleurs.texteSecondaire },
|
||
ligneHistorique: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
paddingVertical: 10,
|
||
borderBottomWidth: 1,
|
||
borderBottomColor: couleurs.bordure,
|
||
},
|
||
badge: {
|
||
backgroundColor: couleurs.primaire,
|
||
borderRadius: 14,
|
||
width: 28,
|
||
height: 28,
|
||
justifyContent: 'center',
|
||
alignItems: 'center',
|
||
marginRight: 12,
|
||
},
|
||
badgeTexte: { color: 'white', fontWeight: '700', fontSize: 13 },
|
||
dateHistorique: { color: couleurs.texteSecondaire, flex: 1 },
|
||
etiquetteRecent: {
|
||
fontSize: 11,
|
||
color: couleurs.succes,
|
||
fontWeight: '700',
|
||
textTransform: 'uppercase',
|
||
},
|
||
});
|