Checkpoint: Ajout d'un indicateur visuel de rafraîchissement (badge "Mis à jour il y a X secondes" avec icône animée) et de statistiques en temps réel du taux de présence par période (matin/après-midi) sur la page d'émargement numérique
This commit is contained in:
@@ -26,6 +26,8 @@ export default function FormateurEmargement() {
|
||||
const { user } = useAuth();
|
||||
const [selectedSequenceId, setSelectedSequenceId] = useState<number | null>(null);
|
||||
const [showQRDialog, setShowQRDialog] = useState(false);
|
||||
const [lastRefreshTime, setLastRefreshTime] = useState<Date>(new Date());
|
||||
const [secondsSinceRefresh, setSecondsSinceRefresh] = useState(0);
|
||||
|
||||
// Récupérer toutes les séquences (filtrées côté serveur pour les formateurs)
|
||||
const { data: sequences, isLoading: loadingSequences } = trpc.sequences.list.useQuery();
|
||||
@@ -61,11 +63,42 @@ export default function FormateurEmargement() {
|
||||
const interval = setInterval(() => {
|
||||
refetchInscrits();
|
||||
refetchPresences();
|
||||
setLastRefreshTime(new Date());
|
||||
setSecondsSinceRefresh(0);
|
||||
}, 30000); // 30 secondes
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [selectedSequenceId, refetchInscrits, refetchPresences]);
|
||||
|
||||
// Compteur pour "Mis à jour il y a X secondes"
|
||||
useEffect(() => {
|
||||
if (!selectedSequenceId) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setSecondsSinceRefresh(Math.floor((new Date().getTime() - lastRefreshTime.getTime()) / 1000));
|
||||
}, 1000); // Mise à jour chaque seconde
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [selectedSequenceId, lastRefreshTime]);
|
||||
|
||||
// Calculer les statistiques de présence par date et période
|
||||
const getPresenceStats = (dateFormationId: number, periode: "matin" | "apres_midi") => {
|
||||
if (!inscrits || !presences) return { present: 0, total: 0, percentage: 0 };
|
||||
|
||||
const total = inscrits.length;
|
||||
const present = inscrits.filter(inscrit =>
|
||||
presences.some(p =>
|
||||
p.inscriptionId === inscrit.inscriptionId &&
|
||||
p.dateFormationId === dateFormationId &&
|
||||
p.periode === periode
|
||||
)
|
||||
).length;
|
||||
|
||||
const percentage = total > 0 ? Math.round((present / total) * 100) : 0;
|
||||
|
||||
return { present, total, percentage };
|
||||
};
|
||||
|
||||
// Valider une présence manuellement
|
||||
const { mutate: validerPresence, isPending: validating } = trpc.presences.valider.useMutation({
|
||||
onSuccess: () => {
|
||||
@@ -175,30 +208,45 @@ export default function FormateurEmargement() {
|
||||
</Select>
|
||||
|
||||
{selectedSequenceId && (
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleGenerateQR} disabled={generatingQR}>
|
||||
{generatingQR ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Génération...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<QrCode className="mr-2 h-4 w-4" />
|
||||
Afficher le QR code
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
refetchInscrits();
|
||||
refetchPresences();
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Actualiser
|
||||
</Button>
|
||||
<div className="space-y-3">
|
||||
<div className="flex gap-2">
|
||||
<Button onClick={handleGenerateQR} disabled={generatingQR}>
|
||||
{generatingQR ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Génération...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<QrCode className="mr-2 h-4 w-4" />
|
||||
Afficher le QR code
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
refetchInscrits();
|
||||
refetchPresences();
|
||||
setLastRefreshTime(new Date());
|
||||
setSecondsSinceRefresh(0);
|
||||
}}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Actualiser
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Indicateur de rafraîchissement */}
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<RefreshCw className={`h-3 w-3 ${secondsSinceRefresh < 30 ? 'animate-spin' : ''}`} />
|
||||
<span>
|
||||
Mis à jour il y a {secondsSinceRefresh} seconde{secondsSinceRefresh > 1 ? 's' : ''}
|
||||
</span>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Rafraîchissement auto: 30s
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -268,6 +316,17 @@ export default function FormateurEmargement() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Statistiques Matin */}
|
||||
<div className="flex items-center justify-between bg-blue-50 dark:bg-blue-950/20 px-3 py-2 rounded">
|
||||
<span className="text-xs font-medium">🌅 Matin</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{(() => {
|
||||
const stats = getPresenceStats(date.id, "matin");
|
||||
return `${stats.present}/${stats.total} présents (${stats.percentage}%)`;
|
||||
})()}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Matin */}
|
||||
<div className="flex items-center justify-between pl-4">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
@@ -328,6 +387,17 @@ export default function FormateurEmargement() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Statistiques Après-midi */}
|
||||
<div className="flex items-center justify-between bg-orange-50 dark:bg-orange-950/20 px-3 py-2 rounded mt-2">
|
||||
<span className="text-xs font-medium">🌆 Après-midi</span>
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{(() => {
|
||||
const stats = getPresenceStats(date.id, "apres_midi");
|
||||
return `${stats.present}/${stats.total} présents (${stats.percentage}%)`;
|
||||
})()}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Après-midi */}
|
||||
<div className="flex items-center justify-between pl-4">
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
|
||||
3
todo.md
3
todo.md
@@ -1493,3 +1493,6 @@
|
||||
- [x] Créer l'interface de gestion des attestations pour formateurs et super-formateurs (charger, supprimer, modifier, envoyer)
|
||||
|
||||
- [x] Ajouter un rafraîchissement automatique toutes les 30 secondes sur la page d'émargement numérique
|
||||
|
||||
- [x] Ajouter un indicateur visuel de rafraîchissement avec badge "Mis à jour il y a X secondes"
|
||||
- [x] Ajouter des statistiques en temps réel du taux de présence par période
|
||||
|
||||
Reference in New Issue
Block a user