initial commit
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user