diff --git a/backend/time_tracking/views.py b/backend/time_tracking/views.py index dba5125..21fb573 100644 --- a/backend/time_tracking/views.py +++ b/backend/time_tracking/views.py @@ -25,16 +25,25 @@ User = get_user_model() def planned_minutes_for_period(user_id, start, end): - """Total planifié sur la période. + """Total planifié déjà dû sur la période. - Les objectifs hebdomadaires n'imposent aucun jour précis : pour les - statistiques, leur total est réparti uniformément sur les sept jours afin - qu'un jour, un mois et une année restent comparables et additionnables. + Les statistiques ne comptent que les journées strictement antérieures à + aujourd'hui : la journée en cours et les journées futures ne doivent pas + apparaître comme des heures manquantes. + + Les objectifs hebdomadaires n'imposent aucun jour précis : leur total reste + réparti uniformément sur les sept jours, mais uniquement pour les journées + déjà passées de la période demandée. """ + yesterday = timezone.localdate() - timedelta(days=1) + counted_end = min(end, yesterday) + if start > counted_end: + return 0 + plan = load_plan(user_id) total = 0.0 day = start - while day <= end: + while day <= counted_end: planned = effective_day(user_id, day, plan) if planned.get('schedule_mode') == 'weekly_hours': total += float(planned.get('week_target_minutes') or 0) / 7 diff --git a/frontend/src/components/statistics/MonthlyHoursRace.jsx b/frontend/src/components/statistics/MonthlyHoursRace.jsx index 32fef7e..3639a2b 100644 --- a/frontend/src/components/statistics/MonthlyHoursRace.jsx +++ b/frontend/src/components/statistics/MonthlyHoursRace.jsx @@ -9,9 +9,8 @@ import { } from '@/components/ui/dialog'; import { timeApi } from '@/api/time'; import { formatMinutes, personLabel } from '@/components/planning/utils'; -import { useAuth } from '@/context/useAuth'; -const RACE_DURATION_MS = 20000; +const RACE_DURATION_MS = 8000; const monthLabel = (value) => { if (!value) return ''; @@ -32,7 +31,6 @@ const dateLabel = (value) => { const clamp = (value, min, max) => Math.min(max, Math.max(min, value)); export default function MonthlyHoursRace() { - const { user } = useAuth(); const [open, setOpen] = useState(false); const [race, setRace] = useState(null); const [loading, setLoading] = useState(false); @@ -40,6 +38,7 @@ export default function MonthlyHoursRace() { const [progress, setProgress] = useState(0); const [runId, setRunId] = useState(0); const frameRef = useRef(null); + const replayFrameRef = useRef(null); const loadRace = useCallback(async () => { setLoading(true); @@ -60,7 +59,9 @@ export default function MonthlyHoursRace() { setOpen(nextOpen); if (!nextOpen) { if (frameRef.current) cancelAnimationFrame(frameRef.current); + if (replayFrameRef.current) cancelAnimationFrame(replayFrameRef.current); frameRef.current = null; + replayFrameRef.current = null; setProgress(0); } }; @@ -72,9 +73,20 @@ export default function MonthlyHoursRace() { const replay = () => { if (frameRef.current) cancelAnimationFrame(frameRef.current); + if (replayFrameRef.current) cancelAnimationFrame(replayFrameRef.current); frameRef.current = null; + replayFrameRef.current = null; + + // On force d'abord un rendu complet à 0 avant de relancer la course. + // Le double requestAnimationFrame garantit qu'au moins une frame vide + // est peinte, ce qui remet aussi correctement la jauge à zéro. setProgress(0); - setRunId((value) => value + 1); + replayFrameRef.current = requestAnimationFrame(() => { + replayFrameRef.current = requestAnimationFrame(() => { + replayFrameRef.current = null; + setRunId((value) => value + 1); + }); + }); }; useEffect(() => { @@ -187,14 +199,13 @@ export default function MonthlyHoursRace() { {Math.round(progress * 100)} %
-
+
{animatedRows.map((participant) => { - const isMe = String(participant.id) === String(user?.id); const barHeight = maxFinalMinutes > 0 ? clamp((participant.animatedMinutes / maxFinalMinutes) * 100, 0, 100) : 0; @@ -211,7 +222,7 @@ export default function MonthlyHoursRace() {
0 ? 1.5 : 0)}%`, backgroundColor: participant.color || 'var(--primary)', @@ -224,7 +235,6 @@ export default function MonthlyHoursRace() {

{personLabel(participant)}

- {isMe &&

toi

}
);