diff --git a/packages/client/src/modules/play/Components/Synthesis/SynthesisBudget/SynthesisBudget.tsx b/packages/client/src/modules/play/Components/Synthesis/SynthesisBudget/SynthesisBudget.tsx
index 51fdd084..5c337185 100644
--- a/packages/client/src/modules/play/Components/Synthesis/SynthesisBudget/SynthesisBudget.tsx
+++ b/packages/client/src/modules/play/Components/Synthesis/SynthesisBudget/SynthesisBudget.tsx
@@ -3,13 +3,14 @@ import { formatBudget } from "../../../../../lib/formatter";
import { useTranslation } from "../../../../translations";
import { synthesisConstants } from "../../../playerActions/constants/synthesis";
import { Icon } from "../../../../common/components/Icon";
-import { usePlay, useTeamValues } from "../../../context/playContext";
+import { usePlay } from "../../../context/playContext";
import { ITeam } from "../../../../../utils/types";
import { getDaysToEnergyShiftTargetYear } from "../../../../../lib/time";
import { Typography } from "../../../../common/components/Typography";
import { Tag } from "../../../../common/components/Tag";
import { ENERGY_SHIFT_TARGET_YEAR } from "../../../../common/constants";
import { CardStyled } from "../Synthesis.common.styles";
+import { useTeamValuesForTeam } from "../../../context/hooks/shared";
export default SynthesisBudget;
@@ -17,8 +18,7 @@ function SynthesisBudget({ team }: { team: ITeam | null }) {
const { t } = useTranslation(["common", "countries"]);
const { game } = usePlay();
const daysTo2050 = getDaysToEnergyShiftTargetYear(new Date(game.date));
- const { getTeamById } = useTeamValues();
- const teamValues = getTeamById(team?.id);
+ const { teamValues } = useTeamValuesForTeam({ teamId: team?.id });
const budget = teamValues?.budgetSpent || 0;
diff --git a/packages/client/src/modules/play/Components/Synthesis/SynthesisCarbon/SynthesisCarbon.tsx b/packages/client/src/modules/play/Components/Synthesis/SynthesisCarbon/SynthesisCarbon.tsx
index 8c52a53d..d5e38b54 100644
--- a/packages/client/src/modules/play/Components/Synthesis/SynthesisCarbon/SynthesisCarbon.tsx
+++ b/packages/client/src/modules/play/Components/Synthesis/SynthesisCarbon/SynthesisCarbon.tsx
@@ -6,12 +6,12 @@ import {
import { useTranslation } from "../../../../translations";
import { synthesisConstants } from "../../../playerActions/constants/synthesis";
import { Icon } from "../../../../common/components/Icon";
-import { useTeamValues } from "../../../context/playContext";
import { ITeam } from "../../../../../utils/types";
import { Typography } from "../../../../common/components/Typography";
import { TagNumber } from "../../../../common/components/TagNumber";
import { Tag } from "../../../../common/components/Tag";
import { CardStyled } from "../Synthesis.common.styles";
+import { useTeamValuesForTeam } from "../../../context/hooks/shared";
const CARBON_FOOTPRINT_TONS_THRESHOLD = 2;
@@ -19,8 +19,7 @@ export default SynthesisCarbon;
function SynthesisCarbon({ team }: { team: ITeam | null }) {
const { t } = useTranslation();
- const { getTeamById } = useTeamValues();
- const teamValues = getTeamById(team?.id);
+ const { teamValues } = useTeamValuesForTeam({ teamId: team?.id });
const teamCarbonFootprintInKgPerDay = teamValues?.carbonFootprint || 0;
const carbonFootprintReduction = teamValues?.carbonFootprintReduction || 0;
diff --git a/packages/client/src/modules/play/Components/Synthesis/SynthesisProduction/SynthesisProduction.tsx b/packages/client/src/modules/play/Components/Synthesis/SynthesisProduction/SynthesisProduction.tsx
index 4490c393..77615a46 100644
--- a/packages/client/src/modules/play/Components/Synthesis/SynthesisProduction/SynthesisProduction.tsx
+++ b/packages/client/src/modules/play/Components/Synthesis/SynthesisProduction/SynthesisProduction.tsx
@@ -6,7 +6,7 @@ import {
} from "../../../../../lib/formatter";
import { useTranslation } from "../../../../translations";
import { Icon } from "../../../../common/components/Icon";
-import { usePlay, useTeamValues } from "../../../context/playContext";
+import { usePlay } from "../../../context/playContext";
import { ITeam } from "../../../../../utils/types";
import { Typography } from "../../../../common/components/Typography";
import { TagNumber } from "../../../../common/components/TagNumber";
@@ -15,14 +15,14 @@ import { ProductionDatum } from "../../../../persona/production";
import { isDecarbonatedEnergyProduction } from "../../../utils/production";
import { TagEnergy } from "../../../../common/components/TagEnergy";
import { CardStyled } from "../Synthesis.common.styles";
+import { useTeamValuesForTeam } from "../../../context/hooks/shared";
export default SynthesisProduction;
function SynthesisProduction({ team }: { team: ITeam | null }) {
const { t } = useTranslation();
const { productionOfCountryToday } = usePlay();
- const { getTeamById } = useTeamValues();
- const teamValues = getTeamById(team?.id);
+ const { teamValues } = useTeamValuesForTeam({ teamId: team?.id });
const computeRenewableEnergyProduction = useCallback(
(production: ProductionDatum[] = []) =>
diff --git a/packages/client/src/modules/play/GameConsole/StatsConsole.tsx b/packages/client/src/modules/play/GameConsole/StatsConsole.tsx
index 4cec38ff..23965e1b 100644
--- a/packages/client/src/modules/play/GameConsole/StatsConsole.tsx
+++ b/packages/client/src/modules/play/GameConsole/StatsConsole.tsx
@@ -1,6 +1,6 @@
import { Box, Grid, useTheme } from "@mui/material";
import { PlayBox } from "../Components";
-import { TeamIdToValues, usePlay, useTeamValues } from "../context/playContext";
+import { TeamIdToValues, usePlay } from "../context/playContext";
import { ConsumptionStats, ProductionStats } from "./ProdStats";
import { Typography } from "../../common/components/Typography";
import { Icon } from "../../common/components/Icon";
@@ -13,6 +13,7 @@ import {
import { useTranslation } from "../../translations/useTranslation";
import { I18nTranslateFunction } from "../../translations";
import { IEnrichedGame, ITeam } from "../../../utils/types";
+import { useTeamValues } from "../context/hooks/shared";
export { StatsConsole };
diff --git a/packages/client/src/modules/play/GameConsole/TeamConsoleContent.tsx b/packages/client/src/modules/play/GameConsole/TeamConsoleContent.tsx
index fd3af5b5..9423394a 100644
--- a/packages/client/src/modules/play/GameConsole/TeamConsoleContent.tsx
+++ b/packages/client/src/modules/play/GameConsole/TeamConsoleContent.tsx
@@ -1,4 +1,5 @@
import { Box, Rating, useTheme } from "@mui/material";
+import { ReactNode, useMemo } from "react";
import { PlayerChart } from "./PlayerChart";
import { PlayBox } from "../Components";
import { Icon } from "../../common/components/Icon";
@@ -17,29 +18,142 @@ import {
import { TeamActionsRecap } from "../Components/TeamActionsRecap";
import { getTeamActionsAtCurrentStep } from "../utils/teamActions";
import { sumAllValues } from "../../persona";
-import { useMemo } from "react";
import SynthesisRecapForTeacher from "../Components/Synthesis/SynthesisRecapForTeacher";
export { TeamConsoleContent };
function TeamConsoleContent({ team }: { team: ITeam }) {
- const { game, players, productionActionById } = usePlay();
const currentStep = useCurrentStep();
+
+ const isProductionStep = currentStep?.type === "production";
+ const isSynthesisStep = currentStep?.id === "final-situation";
+
+ if (isSynthesisStep) {
+ return ;
+ } else if (isProductionStep) {
+ return ;
+ } else {
+ return ;
+ }
+}
+
+function TeamConsoleContentConsumption({ team }: { team: ITeam }) {
+ const { players } = usePlay();
const playersInTeam = useMemo(
() => players.filter((p) => p.teamId === team.id),
[players, team]
);
- const isProductionStep = currentStep?.type === "production";
- const isSynthesisStep = currentStep?.id === "final-situation";
+ return (
+
+
+
+ {playersInTeam.map((player) => (
+
+ ))}
+
+
+
+
+
+
+
+ >
+ }
+ />
+ );
+}
+
+function TeamConsoleContentProduction({ team }: { team: ITeam }) {
+ const { game, players, productionActionById } = usePlay();
+ const playersInTeam = useMemo(
+ () => players.filter((p) => p.teamId === team.id),
+ [players, team]
+ );
const teamActionsAtCurrentStep = getTeamActionsAtCurrentStep(
game.step,
team.actions,
productionActionById
);
- const PlayerComponent = getPlayerComponent(isProductionStep, isSynthesisStep);
+ return (
+
+
+
+ {playersInTeam.map((player) => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ }
+ />
+ );
+}
+
+function TeamConsoleContentSynthesis({ team }: { team: ITeam }) {
+ const { players } = usePlay();
+ const playersInTeam = useMemo(
+ () => players.filter((p) => p.teamId === team.id),
+ [players, team]
+ );
+
+ return (
+
+
+
+
+
+
+
+ {playersInTeam.map((player) => (
+
+ ))}
+
+
+
+
+
+
+
+ >
+ }
+ />
+ );
+}
+
+function TeamConsoleContentLayout({
+ team,
+ content,
+}: {
+ team: ITeam;
+ content: ReactNode;
+}) {
return (
- {isSynthesisStep && (
-
-
-
- )}
-
-
-
- {playersInTeam.map((player) => (
-
- ))}
-
-
- {isProductionStep && (
-
-
-
-
-
- )}
-
-
-
-
+
+ {content}
);
}
-function getPlayerComponent(
- isProductionStep: boolean,
- isSynthesisStep: boolean
-) {
- if (isSynthesisStep) {
- return PlayerSynthesis;
- } else if (isProductionStep) {
- return PlayerProduction;
- }
- return PlayerConsumption;
-}
-
function PlayerSynthesis({ player }: { player: Player }) {
const { latestPersona } = usePersonaByUserId(player.userId);
diff --git a/packages/client/src/modules/play/PlayerPersona/PlayerHeader.tsx b/packages/client/src/modules/play/PlayerPersona/PlayerHeader.tsx
index 770afd7d..ce2abc66 100644
--- a/packages/client/src/modules/play/PlayerPersona/PlayerHeader.tsx
+++ b/packages/client/src/modules/play/PlayerPersona/PlayerHeader.tsx
@@ -11,7 +11,7 @@ import { Link } from "react-router-dom";
import { useAuth } from "../../auth/authProvider";
import GameStepper from "../../common/components/Stepper";
import { PlayBox } from "../Components";
-import { useCurrentStep, usePlay, useTeamValues } from "../context/playContext";
+import { useCurrentStep, usePlay } from "../context/playContext";
import { Icon } from "../../common/components/Icon";
import {
formatPoints,
@@ -26,6 +26,7 @@ import { useTranslation } from "../../translations/useTranslation";
import { useCurrentPlayer, usePersona } from "../context/hooks/player";
import { Button } from "../../common/components/Button";
import { useMemo } from "react";
+import { useTeamValues } from "../context/hooks/shared";
export { PlayerHeader };
diff --git a/packages/client/src/modules/play/context/hooks/shared/index.ts b/packages/client/src/modules/play/context/hooks/shared/index.ts
new file mode 100644
index 00000000..268c39d8
--- /dev/null
+++ b/packages/client/src/modules/play/context/hooks/shared/index.ts
@@ -0,0 +1,2 @@
+export { useTeamValues } from "./useTeamValues";
+export { useTeamValuesForTeam } from "./useTeamValuesForTeam";
diff --git a/packages/client/src/modules/play/context/hooks/shared/useTeamValues.ts b/packages/client/src/modules/play/context/hooks/shared/useTeamValues.ts
new file mode 100644
index 00000000..41da3b6a
--- /dev/null
+++ b/packages/client/src/modules/play/context/hooks/shared/useTeamValues.ts
@@ -0,0 +1,39 @@
+import { useMemo } from "react";
+import { TeamValues, usePersonaByUserId, usePlay } from "../../playContext";
+import { buildTeamValues } from "./utils";
+
+export { useTeamValues };
+
+function useTeamValues(): {
+ teamValues: TeamValues[];
+ getTeamById: (id: number | undefined) => TeamValues | undefined;
+} {
+ const { game, players, teams } = usePlay();
+
+ const userIds: number[] = useMemo(
+ () => players.map((p) => p.userId),
+ [players]
+ );
+ const personaByUserId = usePersonaByUserId(userIds);
+
+ const teamValues = useMemo(() => {
+ return teams.map((team) => {
+ return buildTeamValues({
+ game,
+ personaByUserId,
+ players,
+ team,
+ });
+ });
+ // TODO: check `personaByUserId` in deps doesn't trigger infinite renders.
+ }, [game, personaByUserId, players, teams]);
+
+ const getTeamById = (id: number | undefined) => {
+ return teamValues.find((t) => t.id === id);
+ };
+
+ return {
+ teamValues,
+ getTeamById,
+ };
+}
diff --git a/packages/client/src/modules/play/context/hooks/shared/useTeamValuesForTeam.ts b/packages/client/src/modules/play/context/hooks/shared/useTeamValuesForTeam.ts
new file mode 100644
index 00000000..20d752ff
--- /dev/null
+++ b/packages/client/src/modules/play/context/hooks/shared/useTeamValuesForTeam.ts
@@ -0,0 +1,26 @@
+import { useMemo } from "react";
+import { TeamValues, usePersonaByUserId, usePlay } from "../../playContext";
+import { buildTeamValues } from "./utils";
+
+export { useTeamValuesForTeam };
+
+function useTeamValuesForTeam({ teamId }: { teamId?: number }): {
+ teamValues: TeamValues;
+} {
+ const { game, players, teams } = usePlay();
+
+ const userIds: number[] = useMemo(
+ () => players.filter((p) => p.teamId === teamId).map((p) => p.userId),
+ [players, teamId]
+ );
+ const personaByUserId = usePersonaByUserId(userIds);
+
+ const teamValues = useMemo(() => {
+ const team = teams.find((t) => t.id === teamId);
+ return buildTeamValues({ game, personaByUserId, players, team });
+ }, [game, personaByUserId, players, teamId, teams]);
+
+ return {
+ teamValues,
+ };
+}
diff --git a/packages/client/src/modules/play/context/hooks/shared/utils.ts b/packages/client/src/modules/play/context/hooks/shared/utils.ts
new file mode 100644
index 00000000..13a6570f
--- /dev/null
+++ b/packages/client/src/modules/play/context/hooks/shared/utils.ts
@@ -0,0 +1,112 @@
+import range from "lodash/range";
+import { IGame, ITeam, Player } from "../../../../../utils/types";
+import { GameStepType, isStepOfType } from "../../../constants";
+import { TeamValues, usePersonaByUserId } from "../../playContext";
+import { mean } from "../../../../../lib/math";
+import { sumAllValues } from "../../../../persona";
+
+export { buildStepToData, buildTeamValues };
+
+type PersonaByUserId = ReturnType;
+
+function buildStepToData(
+ dataType: GameStepType,
+ game: IGame,
+ players: Player[],
+ personaByUserId: PersonaByUserId
+) {
+ return Object.fromEntries(
+ range(0, game.lastFinishedStep + 1)
+ .filter((step) => isStepOfType(step, dataType))
+ .map((step: number) => [
+ step,
+ buildStepData(dataType, step, players, personaByUserId),
+ ])
+ );
+}
+
+function buildStepData(
+ dataType: GameStepType,
+ step: number,
+ players: Player[],
+ personaByUserId: PersonaByUserId
+) {
+ return mean(
+ players
+ .map((p) => personaByUserId[p.userId].getPersonaAtStep(step)[dataType])
+ .map((data) =>
+ parseInt(sumAllValues(data as { type: string; value: number }[]))
+ )
+ );
+}
+
+function buildTeamValues({
+ game,
+ personaByUserId,
+ players,
+ team,
+}: {
+ game: IGame;
+ personaByUserId: PersonaByUserId;
+ players: Player[];
+ team?: ITeam;
+}): TeamValues {
+ const playersInTeam = players.filter((p) => p.teamId === team?.id);
+ const playerRepresentingTeam = playersInTeam[0] || null;
+ const personaRepresentingTeam =
+ personaByUserId[playerRepresentingTeam?.userId || -1] || null;
+ const currentPersonaRepresentingTeam =
+ personaRepresentingTeam?.getPersonaAtStep?.(game.step) || null;
+
+ return {
+ id: team?.id || 0,
+ playerCount: playersInTeam.length,
+ points: mean(
+ playersInTeam.map(
+ ({ userId }) => personaByUserId[userId].currentPersona.points
+ )
+ ),
+ budget: mean(
+ playersInTeam.map(
+ ({ userId }) => personaByUserId[userId].currentPersona.budget
+ )
+ ),
+ budgetSpent: mean(
+ playersInTeam
+ .map(({ userId }) => personaByUserId[userId])
+ .map(
+ (persona) =>
+ persona.getPersonaAtStep(0).budget - persona.currentPersona.budget
+ )
+ ),
+ carbonFootprint: mean(
+ playersInTeam.map(
+ ({ userId }) => personaByUserId[userId].currentPersona.carbonFootprint
+ )
+ ),
+ carbonFootprintReduction: mean(
+ playersInTeam
+ .map(({ userId }) => personaByUserId[userId])
+ .map(
+ (persona) =>
+ (1 -
+ persona.currentPersona.carbonFootprint /
+ persona.getPersonaAtStep(0).carbonFootprint) *
+ 100
+ )
+ ),
+ stepToConsumption: buildStepToData(
+ "consumption",
+ game,
+ playersInTeam,
+ personaByUserId
+ ),
+ stepToProduction: buildStepToData(
+ "production",
+ game,
+ playersInTeam,
+ personaByUserId
+ ),
+ productionCurrent: currentPersonaRepresentingTeam?.production || [],
+ };
+}
diff --git a/packages/client/src/modules/play/context/playContext.tsx b/packages/client/src/modules/play/context/playContext.tsx
index 5633b894..60933a11 100644
--- a/packages/client/src/modules/play/context/playContext.tsx
+++ b/packages/client/src/modules/play/context/playContext.tsx
@@ -12,11 +12,8 @@ import {
ProductionAction,
} from "../../../utils/types";
import { useAuth } from "../../auth/authProvider";
-import { GameStep, GameStepType, isStepOfType, STEPS } from "../constants";
+import { GameStep, STEPS } from "../constants";
import { buildPersona } from "../utils/persona";
-import { mean } from "../../../lib/math";
-import { range } from "lodash";
-import { sumAllValues } from "../../persona";
import { buildInitialPersona } from "../../persona/persona";
import { WEB_SOCKET_URL } from "../../common/constants";
import { usePlayStore } from "./usePlayStore";
@@ -26,12 +23,11 @@ import { ProductionDatum } from "../../persona/production";
export {
RootPlayProvider,
useCurrentStep,
- useTeamValues,
useLoadedPlay as usePlay,
usePersonaByUserId,
};
-export type { TeamIdToValues };
+export type { TeamIdToValues, TeamValues };
interface TeamIdToValues {
[k: string]: TeamValues;
@@ -239,125 +235,6 @@ function useLoadedPlay(): IPlayContext {
return playValue;
}
-function useTeamValues(): {
- teamValues: TeamValues[];
- getTeamById: (id: number | undefined) => TeamValues | undefined;
-} {
- const { game, players, teams } = useLoadedPlay();
-
- const userIds: number[] = useMemo(
- () => players.map((p) => p.userId),
- [players]
- );
- const personaByUserId = usePersonaByUserId(userIds);
-
- const teamValues = useMemo(() => {
- return teams.map((team) => {
- const playersInTeam = players.filter((p) => p.teamId === team.id);
- const playerRepresentingTeam = playersInTeam[0] || null;
- const personaRepresentingTeam =
- personaByUserId[playerRepresentingTeam?.userId || -1] || null;
- const currentPersonaRepresentingTeam =
- personaRepresentingTeam?.getPersonaAtStep?.(game.step) || null;
-
- return {
- id: team.id,
- playerCount: playersInTeam.length,
- points: mean(
- playersInTeam.map(
- ({ userId }) => personaByUserId[userId].currentPersona.points
- )
- ),
- budget: mean(
- playersInTeam.map(
- ({ userId }) => personaByUserId[userId].currentPersona.budget
- )
- ),
- budgetSpent: mean(
- playersInTeam
- .map(({ userId }) => personaByUserId[userId])
- .map(
- (persona) =>
- persona.getPersonaAtStep(0).budget -
- persona.currentPersona.budget
- )
- ),
- carbonFootprint: mean(
- playersInTeam.map(
- ({ userId }) =>
- personaByUserId[userId].currentPersona.carbonFootprint
- )
- ),
- carbonFootprintReduction: mean(
- playersInTeam
- .map(({ userId }) => personaByUserId[userId])
- .map(
- (persona) =>
- (1 -
- persona.currentPersona.carbonFootprint /
- persona.getPersonaAtStep(0).carbonFootprint) *
- 100
- )
- ),
- stepToConsumption: buildStepToData(
- "consumption",
- game,
- playersInTeam,
- personaByUserId
- ),
- stepToProduction: buildStepToData(
- "production",
- game,
- playersInTeam,
- personaByUserId
- ),
- productionCurrent: currentPersonaRepresentingTeam?.production || [],
- };
- });
- // TODO: check `personaByUserId` in deps doesn't trigger infinite renders.
- }, [game, personaByUserId, players, teams]);
-
- const getTeamById = (id: number | undefined) => {
- return teamValues.find((t) => t.id === id);
- };
-
- return {
- teamValues,
- getTeamById,
- };
-}
-
-function buildStepToData(
- dataType: GameStepType,
- game: IGame,
- players: Player[],
- personaByUserId: ReturnType
-) {
- return Object.fromEntries(
- range(0, game.lastFinishedStep + 1)
- .filter((step) => isStepOfType(step, dataType))
- .map((step: number) => [
- step,
- buildStepData(dataType, step, players, personaByUserId),
- ])
- );
-}
-
-function buildStepData(
- dataType: GameStepType,
- step: number,
- players: Player[],
- personaByUserId: ReturnType
-) {
- return mean(
- players
- .map((p) => personaByUserId[p.userId].getPersonaAtStep(step)[dataType])
- .map((data) =>
- parseInt(sumAllValues(data as { type: string; value: number }[]))
- )
- );
-}
-
function useCurrentStep(): GameStep | null {
const playValue = usePlay();
if (!playValue) {