PS_ProserveReport/PS_Report/src/hooks/useComputedSuccess.ts
j.foucher 2b9d532627 V1 : session detail redesign, computed success logic, UI improvements
- Redesign session header: title on top, badge + date/map/scenario below, participants aligned
- Add 3-column layout for standard sessions: KPIs | Global+Personal Stats | BarChart
- FireRange/LongRange: per-variant target sizing (human=320px, longRange=480px)
- Challenge: hide target and objectives, show reaction time chart
- Add TargetVisualization component with SVG hit markers
- Compute success/failed from debrief data (civilKilled, policeKilled, hitsReceived)
- Apply computed success across Dashboard, Sessions list, UserDetail, SessionDetail
- Add useComputedSuccess hook for batch debrief loading
- Unified participant combo box in session header with auto-select for single player
- Dark theme: lighter dropdown arrows, brighter muted text, larger ScoreBadge
- Add i18n keys for new stats labels (FR/EN)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 10:52:43 +01:00

61 lines
2.4 KiB
TypeScript

import { useState, useEffect, useRef } from 'react';
import { getDebrief } from '../api/client';
import type { Session, DebriefRow } from '../types';
/**
* Computes success/failure for a list of sessions based on debrief data.
* A session is failed if any: civilKilled > 0, policeKilled > 0, or hitsReceived > 0.
* Only fetches debriefs for the given sessions (use with paginated/visible sessions).
* Returns a Map<sessionId, computedSuccess>.
*/
export function useComputedSuccess(sessions: Session[]): Map<number, boolean> {
const [successMap, setSuccessMap] = useState<Map<number, boolean>>(new Map());
const fetchedRef = useRef<Set<number>>(new Set());
useEffect(() => {
if (sessions.length === 0) return;
// Only fetch sessions we haven't already fetched
const toFetch = sessions.filter(s => !fetchedRef.current.has(s.id));
if (toFetch.length === 0) return;
Promise.all(
toFetch.map(async (session) => {
try {
const debrief = await getDebrief(session.id);
return { id: session.id, success: computeSuccess(session.success, debrief) };
} catch {
return { id: session.id, success: session.success };
}
}),
).then((results) => {
setSuccessMap(prev => {
const next = new Map(prev);
for (const r of results) {
next.set(r.id, r.success);
fetchedRef.current.add(r.id);
}
return next;
});
});
}, [sessions]);
return successMap;
}
/** Compute success from debrief rows */
export function computeSuccess(originalSuccess: boolean, debrief: DebriefRow[]): boolean {
const totalCivilKilled = debrief.reduce((s, r) => s + (Number(r.totalCivilKilled) || 0), 0);
const totalPoliceKilled = debrief.reduce((s, r) => s + (Number(r.totalPoliceKilled) || 0), 0);
const totalHitsReceived = debrief.reduce(
(s, r) => s + (Number(r.nbReceivedHitsFromEnemyIA) || 0) + (Number(r.nbReceivedHitsFromEnemyUser) || 0) + (Number(r.nbReceivedHitsFromPoliceUser) || 0),
0,
);
return originalSuccess && totalCivilKilled === 0 && totalPoliceKilled === 0 && totalHitsReceived === 0;
}
/** Helper: get computed success for a single session from the map, fallback to session.success */
export function getComputedSuccess(successMap: Map<number, boolean>, session: Session): boolean {
return successMap.has(session.id) ? successMap.get(session.id)! : session.success;
}