From 2ee1d0aee5d44fe445b9b453ad64b4abef212744 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Mon, 19 Jun 2023 15:59:10 +0200 Subject: [PATCH 1/9] feat(SynthesisPage): Create card to set scenario name --- .../modules/common/components/Flex/Flex.tsx | 15 +++++++ .../modules/common/components/Flex/index.ts | 1 + .../modules/common/components/Icon/Icon.tsx | 2 + .../play/playerActions/PlayerActionsPage.tsx | 24 ++--------- .../playerActions/SynthesisContent.styles.tsx | 10 +++-- .../play/playerActions/SynthesisContent.tsx | 42 +++++++++---------- .../play/playerActions/SynthesisPage.tsx | 34 +++++++++++++++ .../translations/resources/fr/common.json | 2 +- 8 files changed, 82 insertions(+), 48 deletions(-) create mode 100644 packages/client/src/modules/common/components/Flex/Flex.tsx create mode 100644 packages/client/src/modules/common/components/Flex/index.ts create mode 100644 packages/client/src/modules/play/playerActions/SynthesisPage.tsx diff --git a/packages/client/src/modules/common/components/Flex/Flex.tsx b/packages/client/src/modules/common/components/Flex/Flex.tsx new file mode 100644 index 00000000..146ee542 --- /dev/null +++ b/packages/client/src/modules/common/components/Flex/Flex.tsx @@ -0,0 +1,15 @@ +import { styled } from "@mui/material/styles"; +import { getStyledProps } from "../../../../utils/theme"; + +export { FlexRow }; + +const FlexRow = styled( + "div", + getStyledProps("gap") +)<{ gap?: number }>(({ theme, gap = 2 }) => ({ + display: "flex", + gap: theme.spacing(gap), + [theme.breakpoints.down("md")]: { + flexDirection: "column", + }, +})); diff --git a/packages/client/src/modules/common/components/Flex/index.ts b/packages/client/src/modules/common/components/Flex/index.ts new file mode 100644 index 00000000..77e69ed0 --- /dev/null +++ b/packages/client/src/modules/common/components/Flex/index.ts @@ -0,0 +1 @@ +export { FlexRow } from "./Flex"; diff --git a/packages/client/src/modules/common/components/Icon/Icon.tsx b/packages/client/src/modules/common/components/Icon/Icon.tsx index 1aaa4fd1..4d6b7eb3 100644 --- a/packages/client/src/modules/common/components/Icon/Icon.tsx +++ b/packages/client/src/modules/common/components/Icon/Icon.tsx @@ -36,6 +36,7 @@ import { DryCleaning, HistoryEdu, Home, + LocalMovies, Lock, LockOpen, LunchDining, @@ -91,6 +92,7 @@ const ICONS = { "rank-1st": IconImgFactory({ asset: "medal_1.png" }), "rank-2nd": IconImgFactory({ asset: "medal_2.png" }), "rank-3rd": IconImgFactory({ asset: "medal_3.png" }), + scenario: LocalMovies, settings: SettingsSuggestIcon, star: Star, synthesis: AutoGraphRoundedIcon, diff --git a/packages/client/src/modules/play/playerActions/PlayerActionsPage.tsx b/packages/client/src/modules/play/playerActions/PlayerActionsPage.tsx index 84339006..a8b78748 100644 --- a/packages/client/src/modules/play/playerActions/PlayerActionsPage.tsx +++ b/packages/client/src/modules/play/playerActions/PlayerActionsPage.tsx @@ -1,15 +1,14 @@ import { useTheme } from "@mui/material"; import { ValidateActions } from "./Validation"; -import { useCurrentStep, useMyTeam } from "../context/playContext"; +import { useCurrentStep } from "../context/playContext"; import { PlayBox } from "../Components"; import { TeamActionsHeader } from "./TeamActionsHeader"; import { TeamActionsContent } from "./TeamActionsContent"; -import { SynthesisScenarioName } from "./SynthesisContent"; import { PlayerActionsContent } from "./PlayerActionsContent"; import { PlayerActionsHeader } from "./PlayerActionsHeader"; -import { SynthesisBudget, SynthesisCarbon } from "../Components/Synthesis"; import { PlayerHeaderGrid } from "../PlayerPersona"; import { PlayerPageLayout } from "../PlayLayout"; +import { SynthesisPage } from "./SynthesisPage"; export { PlayerActionsPage }; @@ -21,7 +20,7 @@ function PlayerActionsPage() { } if (currentStep.id === "final-situation") { - return ; + return ; } return currentStep.type === "production" ? ( @@ -31,23 +30,6 @@ function PlayerActionsPage() { ); } -function SynthesisLayout() { - const team = useMyTeam(); - - return ( - } - body={ - - - - - - } - /> - ); -} - function PlayerActionsLayout() { return ( ({ +const ScenarioNameTextField = styled(TextField)(() => ({ + flexGrow: 1, + width: "100%", "& label": { color: "white", }, @@ -15,13 +17,13 @@ const ScenarioNameTextField = styled(TextField)(({ theme }) => ({ }, "& .MuiOutlinedInput-root": { "& fieldset": { - borderColor: theme.palette.secondary.main, + borderColor: "white", }, "&:hover fieldset": { - borderColor: theme.palette.secondary.main, + borderColor: "white", }, "&.Mui-focused fieldset": { - borderColor: theme.palette.secondary.main, + borderColor: "white", }, }, "& .MuiInputBase-input": { diff --git a/packages/client/src/modules/play/playerActions/SynthesisContent.tsx b/packages/client/src/modules/play/playerActions/SynthesisContent.tsx index 37da3857..84a58198 100644 --- a/packages/client/src/modules/play/playerActions/SynthesisContent.tsx +++ b/packages/client/src/modules/play/playerActions/SynthesisContent.tsx @@ -1,6 +1,5 @@ import Button from "@mui/material/Button"; import { Box, IconButton } from "@mui/material"; -import { useTheme } from "@mui/material/styles"; import { useEffect, useState } from "react"; import { Typography } from "../../common/components/Typography"; import { useMyTeam, usePlay } from "../context/playContext"; @@ -8,6 +7,7 @@ import { Icon } from "../../common/components/Icon"; import { Dialog } from "../../common/components/Dialog"; import { ScenarioNameTextField } from "./SynthesisContent.styles"; import { useTranslation } from "../../translations/useTranslation"; +import { FlexRow } from "../../common/components/Flex"; export { SynthesisScenarioName }; @@ -18,12 +18,12 @@ function SynthesisScenarioName() { const isTeamEditable = !isGameFinished; return ( - + - {t("synthesis.player.scenario-section.title")} + {t("synthesis.player.scenario-section.title")} {isTeamEditable && } - + {isTeamEditable ? : } @@ -59,7 +59,6 @@ function ScenarioNameEditionHelp() { } function ScenarioNameEditable() { - const theme = useTheme(); const team = useMyTeam(); const { t } = useTranslation(); @@ -68,6 +67,10 @@ function ScenarioNameEditable() { const [localName, setLocalName] = useState(team?.scenarioName); const handleValidateScenarioName = () => { + if (!localName) { + return; + } + updateTeam({ scenarioName: localName }); }; @@ -78,30 +81,25 @@ function ScenarioNameEditable() { useEffect(() => setLocalName(team?.scenarioName), [team?.scenarioName]); return ( - <> - + + + + - + ); } diff --git a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx new file mode 100644 index 00000000..af494746 --- /dev/null +++ b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx @@ -0,0 +1,34 @@ +import { Box } from "@mui/material"; +import { useMyTeam } from "../context/playContext"; +import { PlayBox } from "../Components"; +import { SynthesisScenarioName } from "./SynthesisContent"; +import { SynthesisBudget, SynthesisCarbon } from "../Components/Synthesis"; +import { PlayerHeaderGrid } from "../PlayerPersona"; +import { PlayerPageLayout } from "../PlayLayout"; + +export { SynthesisPage }; + +function SynthesisPage() { + return ; +} + +function SynthesisLayout() { + const team = useMyTeam(); + + return ( + } + body={ + + + + + + + + + + } + /> + ); +} diff --git a/packages/client/src/modules/translations/resources/fr/common.json b/packages/client/src/modules/translations/resources/fr/common.json index 17bc4e94..8611a4a9 100644 --- a/packages/client/src/modules/translations/resources/fr/common.json +++ b/packages/client/src/modules/translations/resources/fr/common.json @@ -264,7 +264,7 @@ "step.production-3.name": "Étape 5", "step.production-3.title": "Choix de production 3", - "synthesis.player.scenario-section.title": "Nom du scénario", + "synthesis.player.scenario-section.title": "Scénario", "synthesis.player.scenario-section.edit-help.description-1": "Donnez un nom à votre scénario pour le futur énergétique de la France.", "synthesis.player.scenario-section.edit-help.description-2": "Veuillez choisir une personne qui éditera le nom pour l’ensemble de l'équipe.", From 1e360733f2de8d6c0e41899797fe05fe6093c07e Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Mon, 19 Jun 2023 16:26:10 +0200 Subject: [PATCH 2/9] feat(SynthesisPage): Setup tab structure --- .../modules/common/components/Icon/Icon.tsx | 2 ++ .../modules/common/components/Tabs/Tabs.tsx | 15 +++++++++--- .../playerActions/SynthesisGeneralTab.tsx | 18 +++++++++++++++ .../play/playerActions/SynthesisPage.tsx | 23 +++++++++++++------ .../translations/resources/fr/common.json | 1 + 5 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx diff --git a/packages/client/src/modules/common/components/Icon/Icon.tsx b/packages/client/src/modules/common/components/Icon/Icon.tsx index 4d6b7eb3..722b0765 100644 --- a/packages/client/src/modules/common/components/Icon/Icon.tsx +++ b/packages/client/src/modules/common/components/Icon/Icon.tsx @@ -41,6 +41,7 @@ import { LockOpen, LunchDining, Microwave, + PieChart, Shield, TipsAndUpdates, Train, @@ -60,6 +61,7 @@ const ICONS = { budget: Paid, car: DirectionsCar, "carbon-footprint": Cloud, + "chart-pie": PieChart, "check-circle": CheckCircle, "check-doubled": DoneAll, clothes: DryCleaning, diff --git a/packages/client/src/modules/common/components/Tabs/Tabs.tsx b/packages/client/src/modules/common/components/Tabs/Tabs.tsx index f83f0d44..21da0e7e 100644 --- a/packages/client/src/modules/common/components/Tabs/Tabs.tsx +++ b/packages/client/src/modules/common/components/Tabs/Tabs.tsx @@ -1,9 +1,14 @@ -import React, { ReactNode, useMemo, useState } from "react"; +import { ReactNode, useMemo, useState } from "react"; import { CustomTab, CustomTabs } from "./Tabs.styles"; +import { Box } from "@mui/material"; export { Tabs }; -function Tabs({ tabs }: { tabs: { label: string; component: ReactNode }[] }) { +function Tabs({ + tabs, +}: { + tabs: { label: string; icon?: ReactNode; component: ReactNode }[]; +}) { const [selectedTabIdx, setSelectedTabIdx] = useState(0); const currentTab = useMemo(() => { @@ -22,7 +27,11 @@ function Tabs({ tabs }: { tabs: { label: string; component: ReactNode }[] }) { + {tab.icon} {tab.label} + + } /> ))} diff --git a/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx b/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx new file mode 100644 index 00000000..4bb2bee5 --- /dev/null +++ b/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx @@ -0,0 +1,18 @@ +import { useMyTeam } from "../context/playContext"; +import { PlayBox } from "../Components"; +import { SynthesisBudget, SynthesisCarbon } from "../Components/Synthesis"; + +export { SynthesisGeneralTab }; + +function SynthesisGeneralTab() { + const team = useMyTeam(); + + return ( + <> + + + + + + ); +} diff --git a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx index af494746..570f85d3 100644 --- a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx +++ b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx @@ -1,10 +1,12 @@ import { Box } from "@mui/material"; -import { useMyTeam } from "../context/playContext"; import { PlayBox } from "../Components"; import { SynthesisScenarioName } from "./SynthesisContent"; -import { SynthesisBudget, SynthesisCarbon } from "../Components/Synthesis"; import { PlayerHeaderGrid } from "../PlayerPersona"; import { PlayerPageLayout } from "../PlayLayout"; +import { Tabs } from "../../common/components/Tabs"; +import { useTranslation } from "../../translations/useTranslation"; +import { Icon } from "../../common/components/Icon"; +import { SynthesisGeneralTab } from "./SynthesisGeneralTab"; export { SynthesisPage }; @@ -13,7 +15,7 @@ function SynthesisPage() { } function SynthesisLayout() { - const team = useMyTeam(); + const { t } = useTranslation(); return ( - - - - + + , + component: , + }, + ]} + /> + } /> diff --git a/packages/client/src/modules/translations/resources/fr/common.json b/packages/client/src/modules/translations/resources/fr/common.json index 8611a4a9..d70c37a8 100644 --- a/packages/client/src/modules/translations/resources/fr/common.json +++ b/packages/client/src/modules/translations/resources/fr/common.json @@ -264,6 +264,7 @@ "step.production-3.name": "Étape 5", "step.production-3.title": "Choix de production 3", + "synthesis.player.general-section.title": "Général", "synthesis.player.scenario-section.title": "Scénario", "synthesis.player.scenario-section.edit-help.description-1": "Donnez un nom à votre scénario pour le futur énergétique de la France.", "synthesis.player.scenario-section.edit-help.description-2": "Veuillez choisir une personne qui éditera le nom pour l’ensemble de l'équipe.", From d635add67829c0fb7f6967e787b179cfb56efde4 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Tue, 20 Jun 2023 10:54:26 +0200 Subject: [PATCH 3/9] refactor(Tabs): Use icon name instead of icon --- .../client/src/modules/common/components/Tabs/Tabs.tsx | 10 +++++++--- .../src/modules/play/playerActions/SynthesisPage.tsx | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/client/src/modules/common/components/Tabs/Tabs.tsx b/packages/client/src/modules/common/components/Tabs/Tabs.tsx index 21da0e7e..5b0ffe27 100644 --- a/packages/client/src/modules/common/components/Tabs/Tabs.tsx +++ b/packages/client/src/modules/common/components/Tabs/Tabs.tsx @@ -1,13 +1,14 @@ import { ReactNode, useMemo, useState } from "react"; import { CustomTab, CustomTabs } from "./Tabs.styles"; import { Box } from "@mui/material"; +import { Icon, IconName } from "../Icon"; export { Tabs }; function Tabs({ tabs, }: { - tabs: { label: string; icon?: ReactNode; component: ReactNode }[]; + tabs: { label: string; iconName?: IconName; component: ReactNode }[]; }) { const [selectedTabIdx, setSelectedTabIdx] = useState(0); @@ -28,8 +29,11 @@ function Tabs({ className={`tabs__tab ${selectedTabIdx === index ? "active" : ""}`} key={tab.label} label={ - - {tab.icon} {tab.label} + + {tab.iconName && ( + + )} + {tab.label} } /> diff --git a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx index 570f85d3..adca5164 100644 --- a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx +++ b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx @@ -5,7 +5,6 @@ import { PlayerHeaderGrid } from "../PlayerPersona"; import { PlayerPageLayout } from "../PlayLayout"; import { Tabs } from "../../common/components/Tabs"; import { useTranslation } from "../../translations/useTranslation"; -import { Icon } from "../../common/components/Icon"; import { SynthesisGeneralTab } from "./SynthesisGeneralTab"; export { SynthesisPage }; @@ -30,7 +29,7 @@ function SynthesisLayout() { tabs={[ { label: t("synthesis.player.general-section.title"), - icon: , + iconName: "chart-pie", component: , }, ]} From a4b52312c152683cfc089776ffb4573d85fa44d8 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Wed, 21 Jun 2023 15:22:12 +0200 Subject: [PATCH 4/9] feat(SynthesisPage): Implement most impactful actions --- packages/client/src/lib/array.ts | 61 +++++- packages/client/src/lib/formatter.ts | 22 +++ .../modules/common/components/Card/Card.tsx | 12 ++ .../modules/common/components/Card/index.ts | 1 + .../modules/common/components/Icon/Icon.tsx | 10 + .../src/modules/common/components/Tag/Tag.tsx | 47 +++++ .../modules/common/components/Tag/index.ts | 1 + .../common/components/TagNumber/TagNumber.tsx | 60 ++++++ .../common/components/TagNumber/index.ts | 1 + .../components/Typography/Typography.tsx | 2 +- .../play/context/hooks/player/index.ts | 2 + .../hooks/player/useMostImpactfulActions.ts | 126 ++++++++++++ .../play/context/hooks/player/useMyProfile.ts | 33 ++++ .../playerActions/SynthesisGeneralTab.tsx | 10 +- .../SynthesisMostImpactfulActionsTab.tsx | 182 ++++++++++++++++++ .../play/playerActions/SynthesisPage.tsx | 8 + .../translations/resources/fr/common.json | 20 +- 17 files changed, 585 insertions(+), 13 deletions(-) create mode 100644 packages/client/src/modules/common/components/Card/Card.tsx create mode 100644 packages/client/src/modules/common/components/Card/index.ts create mode 100644 packages/client/src/modules/common/components/Tag/Tag.tsx create mode 100644 packages/client/src/modules/common/components/Tag/index.ts create mode 100644 packages/client/src/modules/common/components/TagNumber/TagNumber.tsx create mode 100644 packages/client/src/modules/common/components/TagNumber/index.ts create mode 100644 packages/client/src/modules/play/context/hooks/player/index.ts create mode 100644 packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts create mode 100644 packages/client/src/modules/play/context/hooks/player/useMyProfile.ts create mode 100644 packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx diff --git a/packages/client/src/lib/array.ts b/packages/client/src/lib/array.ts index 7fcddb2d..2f705d5f 100644 --- a/packages/client/src/lib/array.ts +++ b/packages/client/src/lib/array.ts @@ -1,4 +1,6 @@ -export { deepFreeze, sortBy, sumReducer, toArray }; +import { get } from "lodash"; + +export { deepFreeze, indexArrayBy, sortBy, sumReducer, toArray }; type OnlyNumberKeys = keyof { [K in keyof T as T[K] extends number ? K : never]: T[K]; @@ -34,3 +36,60 @@ const toArray = (val: T | T[]): T[] => { const isArraySafe = (val: T | T[]): val is T[] => { return val && Array.isArray(val); }; + +/** + * Create an object from an array where items are indexed by the specified key. + * @example + * const arr = [{ id: 1, name: "John"}, { id: 2, name: "Doe"}]; + * indexArrayBy(arr, "name"); + * // => { "John": { id: 1, name: "John"}, "Doe": { id: 2, name: "Doe"} } + */ +const indexArrayBy = < + T extends Record, + U extends DeepPath +>( + arr: T[], + key: U +): Record, T> => { + return Object.fromEntries(arr.map((item) => [get(item, key), item])); +}; + +type DeepPath> = DeepPathRecursive< + T, + keyof T, + "", + [1, 2, 3] +>; + +type DeepPathRecursive< + T extends Record, + TKey extends keyof T, + TPrefix extends string, + TRecursion extends any[] +> = TKey extends TemplatableTypes + ? Head extends never + ? `${TPrefix}${TKey}` + : T[TKey] extends number | string + ? `${TPrefix}${TKey}` + : T[TKey] extends Record + ? DeepPathRecursive< + T[TKey], + keyof T[TKey], + `${TPrefix}${TKey}.`, + Tail + > + : "" + : ""; + +type TemplatableTypes = string | number | bigint | boolean | null | undefined; + +type Head = T extends [infer THead, ...infer _] + ? THead + : never; + +type Tail = T extends [infer _, ...infer TTail] + ? TTail + : never; + +type KeyUnknownGuard = T extends string | number | symbol ? T : any; + diff --git a/packages/client/src/lib/formatter.ts b/packages/client/src/lib/formatter.ts index 559298dd..7d8a9743 100644 --- a/packages/client/src/lib/formatter.ts +++ b/packages/client/src/lib/formatter.ts @@ -3,6 +3,8 @@ import { userLocale } from "../modules/translations"; export { formatBudget, formatCarbonFootprint, + formatConsumption, + formatPercentage, formatPoints, formatProduction, formatProductionGw, @@ -25,6 +27,26 @@ function formatProductionGw(value?: number) { return value?.toFixed(1) || ""; } +function formatConsumption( + value: number, + { fractionDigits = 0 }: { fractionDigits?: number } = {} +) { + return formatNumber(value, { + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }); +} + +function formatPercentage( + value?: number, + { fractionDigits = 1 }: { fractionDigits?: number } = {} +) { + return formatNumber(value, { + minimumFractionDigits: fractionDigits, + maximumFractionDigits: fractionDigits, + }); +} + function formatProduction({ fractionDigits = 2, }: { fractionDigits?: number } = {}) { diff --git a/packages/client/src/modules/common/components/Card/Card.tsx b/packages/client/src/modules/common/components/Card/Card.tsx new file mode 100644 index 00000000..af65910d --- /dev/null +++ b/packages/client/src/modules/common/components/Card/Card.tsx @@ -0,0 +1,12 @@ +import { Box, styled } from "@mui/material"; + +export { Card }; + +const Card = styled(Box)(({ theme }) => ({ + borderRadius: "10px", + border: "2px solid", + borderColor: "#ffffff", + backgroundColor: theme.palette.primary.main, + color: "#ffffff", + overflow: "hidden", +})); diff --git a/packages/client/src/modules/common/components/Card/index.ts b/packages/client/src/modules/common/components/Card/index.ts new file mode 100644 index 00000000..fbaf43c2 --- /dev/null +++ b/packages/client/src/modules/common/components/Card/index.ts @@ -0,0 +1 @@ +export { Card } from "./Card"; diff --git a/packages/client/src/modules/common/components/Icon/Icon.tsx b/packages/client/src/modules/common/components/Icon/Icon.tsx index 722b0765..3e126326 100644 --- a/packages/client/src/modules/common/components/Icon/Icon.tsx +++ b/packages/client/src/modules/common/components/Icon/Icon.tsx @@ -28,11 +28,13 @@ import { SvgIconProps } from "@mui/material"; import PersonPinRounded from "@mui/icons-material/PersonPinRounded"; import { Badge, + Bolt, Close, Computer, ConnectingAirports, ContentCopy, DirectionsCar, + DoNotDisturb, DryCleaning, HistoryEdu, Home, @@ -41,10 +43,13 @@ import { LockOpen, LunchDining, Microwave, + North, PieChart, Shield, + South, TipsAndUpdates, Train, + Whatshot, } from "@mui/icons-material"; export { Icon }; @@ -70,6 +75,7 @@ const ICONS = { consumption: ShoppingCart, copy: ContentCopy, draft: HistoryEdu, + energy: Bolt, "form-draft": HistoryEdu, "form-pending-validation": SettingsSuggestIcon, "form-unfilled": Cancel, @@ -77,12 +83,16 @@ const ICONS = { food: LunchDining, helper: HelpIcon, house: Home, + impactful: Whatshot, information: Info, "info-card": IconImgFactory({ asset: "info_icon.svg" }), lock: Lock, "lock-open": LockOpen, "mark-circle": Cancel, microwave: Microwave, + missed: DoNotDisturb, + "number-increase": North, + "number-decrease": South, "open-in-new-tab": OpenInNew, plane: ConnectingAirports, "player-finished": HowToReg, diff --git a/packages/client/src/modules/common/components/Tag/Tag.tsx b/packages/client/src/modules/common/components/Tag/Tag.tsx new file mode 100644 index 00000000..4f6e7d61 --- /dev/null +++ b/packages/client/src/modules/common/components/Tag/Tag.tsx @@ -0,0 +1,47 @@ +import { styled } from "@mui/material"; +import { ReactNode } from "react"; + +export { Tag }; + +function Tag({ + type, + color, + icon, + children, +}: { + type?: "success" | "error" | "secondary"; + color?: string; + icon?: ReactNode; + children: ReactNode; +}) { + const className = type || ""; + + return ( + + {icon} + {children} + + ); +} + +const TagStyled = styled("span")(({ theme }) => { + return { + display: "flex", + alignItems: "center", + gap: theme.spacing(0.5), + flexGrow: 0, + flexShrink: 0, + padding: theme.spacing(0.5), + borderRadius: "10px", + color: "#ffffff", + "&.success": { + backgroundColor: theme.palette.status.success, + }, + "&.error": { + backgroundColor: theme.palette.status.error, + }, + "&.secondary": { + backgroundColor: "hsl(0, 50%, 100%)", + }, + }; +}); diff --git a/packages/client/src/modules/common/components/Tag/index.ts b/packages/client/src/modules/common/components/Tag/index.ts new file mode 100644 index 00000000..736cac7b --- /dev/null +++ b/packages/client/src/modules/common/components/Tag/index.ts @@ -0,0 +1 @@ +export { Tag } from "./Tag"; diff --git a/packages/client/src/modules/common/components/TagNumber/TagNumber.tsx b/packages/client/src/modules/common/components/TagNumber/TagNumber.tsx new file mode 100644 index 00000000..f5379df7 --- /dev/null +++ b/packages/client/src/modules/common/components/TagNumber/TagNumber.tsx @@ -0,0 +1,60 @@ +import { useMemo } from "react"; +import { Icon } from "../Icon"; +import { Tag } from "../Tag"; +import { Typography } from "../Typography"; + +export { TagNumber }; + +function TagNumber({ + value, + successDirection = "increase", + formatter, +}: { + value: number; + successDirection?: "increase" | "decrease"; + formatter: (nb: number) => string; +}) { + const tagType = useMemo(() => { + const directionFactor = successDirection === "increase" ? 1 : -1; + if (value * directionFactor > 0) { + return "success"; + } + if (value * directionFactor < 0) { + return "error"; + } + return "secondary"; + }, [successDirection, value]); + + const icon = useMemo(() => { + if (value > 0) { + return ( + + ); + } + if (value < 0) { + return ( + + ); + } + return "•"; + }, [value]); + + const sign = useMemo(() => { + if (value > 0) { + return "+"; + } + if (value < 0) { + return "-"; + } + return ""; + }, [value]); + + return ( + + + {sign} + {formatter(Math.abs(value))} + + + ); +} diff --git a/packages/client/src/modules/common/components/TagNumber/index.ts b/packages/client/src/modules/common/components/TagNumber/index.ts new file mode 100644 index 00000000..9042e998 --- /dev/null +++ b/packages/client/src/modules/common/components/TagNumber/index.ts @@ -0,0 +1 @@ +export { TagNumber } from "./TagNumber"; diff --git a/packages/client/src/modules/common/components/Typography/Typography.tsx b/packages/client/src/modules/common/components/Typography/Typography.tsx index 9ca7d914..85df42db 100644 --- a/packages/client/src/modules/common/components/Typography/Typography.tsx +++ b/packages/client/src/modules/common/components/Typography/Typography.tsx @@ -17,7 +17,7 @@ const Typography = styled(TypographyMui)(({ theme }) => ({ "&:is(h6)": { fontSize: 16, }, - "&:is(p)": { + "&:is(p), &:is(span)": { marginRight: 0, fontSize: 14, }, diff --git a/packages/client/src/modules/play/context/hooks/player/index.ts b/packages/client/src/modules/play/context/hooks/player/index.ts new file mode 100644 index 00000000..b4290d00 --- /dev/null +++ b/packages/client/src/modules/play/context/hooks/player/index.ts @@ -0,0 +1,2 @@ +export * from "./useMostImpactfulActions"; +export * from "./useMyProfile"; diff --git a/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts b/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts new file mode 100644 index 00000000..cc08aca1 --- /dev/null +++ b/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts @@ -0,0 +1,126 @@ +import { sumBy } from "lodash"; +import { computeNewConsumptionData } from "../../../utils/consumption"; +import { usePersona, usePlayerActions } from "../../playContext"; +import { useMyProfile } from "./useMyProfile"; +import { sumFor } from "../../../../persona"; +import { + ConsumptionDatum, + ConsumptionType, +} from "../../../../persona/consumption"; +import { indexArrayBy } from "../../../../../lib/array"; +import { fromEntries } from "../../../../../lib/object"; +import { useMemo } from "react"; +import { Action } from "../../../../../utils/types"; + +export { useMostImpactfulActions }; +export type { ImpactfulAction }; + +type ImpactfulAction = { + action: Action; + isPerformed: boolean; + consumptionImpacts: { + type: ConsumptionType; + initial: number; + final: number; + absolute: number; + relative: number; + }[]; +}; + +function useMostImpactfulActions({ limit = 5 }: { limit?: number } = {}) { + const { personalization } = useMyProfile(); + const { playerActions } = usePlayerActions(); + const { personaBySteps } = usePersona(); + + const mostImpactfulActions = useMemo(() => { + const initialPersona = personaBySteps[0]; + const actions = playerActions.map((playerAction) => playerAction.action); + const actionNameToPlayerAction = indexArrayBy(playerActions, "action.name"); + + const initialConsumptionByType = computeConsumptionByType( + initialPersona.consumption + ); + + const mostImpactfulActions: ImpactfulAction[] = actions + .map((action) => { + const consumptionData = computeNewConsumptionData( + [action.name], + personalization + ); + + return { + action, + consumptionData, + totalConsumptionKwh: sumBy(consumptionData, "value"), + }; + }) + .sort( + (consoA, consoB) => + consoA.totalConsumptionKwh - consoB.totalConsumptionKwh + ) + .slice(0, limit) + .map(({ action, consumptionData }) => { + const consumptionByType = computeConsumptionByType(consumptionData); + const consumptionImpacts = computeConsumptionDifference( + consumptionByType, + initialConsumptionByType + ); + + return { + action, + isPerformed: actionNameToPlayerAction[action.name!].isPerformed, + consumptionImpacts, + }; + }); + + return mostImpactfulActions; + }, [limit, personaBySteps, personalization, playerActions]); + + return { + mostImpactfulActions, + }; +} + +function computeConsumptionByType( + consumptionData: readonly ConsumptionDatum[] +): Record { + const consumptionTypes = consumptionData.map((c) => c.type); + + const consumptionByType = fromEntries( + consumptionTypes.map((type) => [type, sumFor(consumptionData, type)]) + ); + + return consumptionByType; +} + +function computeConsumptionDifference( + consumptionByType: Record, + refConsumptionByType: Record +): { + type: ConsumptionType; + initial: number; + final: number; + absolute: number; + relative: number; +}[] { + const consumptionTypes = Object.keys( + refConsumptionByType + ) as ConsumptionType[]; + + const consumptionImpacts = consumptionTypes + .map((type) => { + const absoluteDifference = + consumptionByType[type] - refConsumptionByType[type]; + + return { + type, + initial: refConsumptionByType[type], + final: consumptionByType[type], + absolute: absoluteDifference, + relative: absoluteDifference / refConsumptionByType[type], + }; + }) + .filter((difference) => difference.absolute !== 0); + + return consumptionImpacts; +} diff --git a/packages/client/src/modules/play/context/hooks/player/useMyProfile.ts b/packages/client/src/modules/play/context/hooks/player/useMyProfile.ts new file mode 100644 index 00000000..d81355c5 --- /dev/null +++ b/packages/client/src/modules/play/context/hooks/player/useMyProfile.ts @@ -0,0 +1,33 @@ +import { usePlay } from "../../playContext"; +import { + IEnrichedGame, + ITeamWithPlayers, + Player, +} from "../../../../../utils/types"; +import { useAuth } from "../../../../auth/authProvider"; + +export { useMyProfile }; + +// TODO: get rid of hook once PR refactoring play context store is merged. +function useMyProfile() { + const { game } = usePlay(); + const { user } = useAuth(); + const profile = user && getUserTeamAndPlayer(game, user.id)?.player?.profile; + + return { + profile, + personalization: profile.personalization, + }; +} + +function getUserTeamAndPlayer(game: IEnrichedGame, userId: number) { + const team = game.teams.find((team: ITeamWithPlayers) => + team.players.find((player: Player) => player.userId === userId) + ); + + const player = team?.players.find( + (player: Player) => player.userId === userId + ); + + return { team, player }; +} diff --git a/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx b/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx index 4bb2bee5..3e4e8140 100644 --- a/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx +++ b/packages/client/src/modules/play/playerActions/SynthesisGeneralTab.tsx @@ -8,11 +8,9 @@ function SynthesisGeneralTab() { const team = useMyTeam(); return ( - <> - - - - - + + + + ); } diff --git a/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx b/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx new file mode 100644 index 00000000..90158032 --- /dev/null +++ b/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx @@ -0,0 +1,182 @@ +import { PlayBox } from "../Components"; +import { Typography } from "../../common/components/Typography"; +import { + ImpactfulAction, + useMostImpactfulActions, +} from "../context/hooks/player"; +import { Box, Tooltip, styled, useTheme } from "@mui/material"; +import { useTranslation } from "../../translations"; +import { formatConsumption, formatPercentage } from "../../../lib/formatter"; +import { Icon } from "../../common/components/Icon"; +import { Card } from "../../common/components/Card"; +import { Tag } from "../../common/components/Tag"; +import { TagNumber } from "../../common/components/TagNumber"; + +export { SynthesisMostImpactfulActionsTab }; + +function SynthesisMostImpactfulActionsTab() { + const { t } = useTranslation(); + const { mostImpactfulActions } = useMostImpactfulActions(); + + return ( + + + {t("synthesis.player.most-impactful-actions-section.description")} + + + {mostImpactfulActions.map((impactfulAction, idx) => ( + + ))} + + + ); +} + +function ActionCard({ + impactfulAction, + rank, +}: { + impactfulAction: ImpactfulAction; + rank: number; +}) { + const { t } = useTranslation(); + const theme = useTheme(); + + return ( + + + + #{rank} + {impactfulAction.isPerformed && ( + + + + + + )} + {!impactfulAction.isPerformed && ( + + + + + + )} + + + + {/* TODO: translate using action name. */} + + {impactfulAction.action.description} + + + + + {impactfulAction.consumptionImpacts.map((consoImpact) => ( + + + + } + color={theme.palette.energy[consoImpact.type]} + > + {t(`energy.${consoImpact.type}`)} + + + `${formatConsumption(value)} ${t( + "unit.watthour-per-day.kilo" + )}` + } + successDirection="decrease" + /> + + t("unit.percentage", { + value: formatPercentage(value), + }) + } + successDirection="decrease" + /> + + + + {formatConsumption(consoImpact.initial)}{" "} + {t("unit.watthour-per-day.kilo")} + +   +   + + {formatConsumption(consoImpact.final)}{" "} + {t("unit.watthour-per-day.kilo")} + + + + ))} + + + + + + ); +} + +const ActionCardLayout = styled(Box)(({ theme }) => ({ + display: "flex", + [theme.breakpoints.down("sm")]: { + flexDirection: "column", + }, +})); + +const ActionCardSideBarLayout = styled(Box)(({ theme }) => ({ + display: "flex", + flexDirection: "column", + alignItems: "center", + gap: theme.spacing(1), + padding: theme.spacing(1), + paddingTop: theme.spacing(2), + [theme.breakpoints.down("sm")]: { + flexDirection: "row", + paddingTop: theme.spacing(1), + }, +})); + +const CardContainer = styled("div")(({ theme }) => ({ + display: "grid", + gridTemplateColumns: "1fr", + gap: theme.spacing(2), +})); diff --git a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx index adca5164..c795e351 100644 --- a/packages/client/src/modules/play/playerActions/SynthesisPage.tsx +++ b/packages/client/src/modules/play/playerActions/SynthesisPage.tsx @@ -6,6 +6,7 @@ import { PlayerPageLayout } from "../PlayLayout"; import { Tabs } from "../../common/components/Tabs"; import { useTranslation } from "../../translations/useTranslation"; import { SynthesisGeneralTab } from "./SynthesisGeneralTab"; +import { SynthesisMostImpactfulActionsTab } from "./SynthesisMostImpactfulActionsTab"; export { SynthesisPage }; @@ -32,6 +33,13 @@ function SynthesisLayout() { iconName: "chart-pie", component: , }, + { + label: t( + "synthesis.player.most-impactful-actions-section.title" + ), + iconName: "impactful", + component: , + }, ]} /> diff --git a/packages/client/src/modules/translations/resources/fr/common.json b/packages/client/src/modules/translations/resources/fr/common.json index d70c37a8..2b169b5a 100644 --- a/packages/client/src/modules/translations/resources/fr/common.json +++ b/packages/client/src/modules/translations/resources/fr/common.json @@ -32,6 +32,11 @@ "dialog.step.yes": "Continuer", "dialog.step.no": "Annuler", + "energy.fossil": "Énergie fossile", + "energy.grey": "Énergie grise", + "energy.renewable": "Énergie décarbonée", + "energy.mixte": "Énergie mixte", + "message.error.admin.global.UNEXPECTED": "L'opération n'a pas pu être effectuée", "message.error.signup.USER_ALREADY_EXISTS": "Cette adresse mail est déjà associée à un compte.", "message.error.signup.USER_DOES_NOT_EXIST": "Cette adresse mail n'est associée à aucun compte.", @@ -268,6 +273,10 @@ "synthesis.player.scenario-section.title": "Scénario", "synthesis.player.scenario-section.edit-help.description-1": "Donnez un nom à votre scénario pour le futur énergétique de la France.", "synthesis.player.scenario-section.edit-help.description-2": "Veuillez choisir une personne qui éditera le nom pour l’ensemble de l'équipe.", + "synthesis.player.most-impactful-actions-section.action-performed": "Action effectuée", + "synthesis.player.most-impactful-actions-section.action-missed": "Action ratée", + "synthesis.player.most-impactful-actions-section.description": "Voici les 5 actions qui avaient le plus d’impact en considérant votre situation initiale.", + "synthesis.player.most-impactful-actions-section.title": "Actions à fort impact", "game.status.draft": "Brouillon", "game.status.finished": "Terminé", @@ -293,10 +302,10 @@ "graph.energy.consumption.title": "Évolution des consommations entre équipes", "graph.energy.production.title": "Évolution des productions entre équipes", "graph.energy.total": "Total", - "graph.energy.fossil": "Energie fossile", - "graph.energy.grey": "Energie grise", - "graph.energy.renewable": "Energie décarbonée", - "graph.energy.mixte": "Energie mixte", + "graph.energy.fossil": "Énergie fossile", + "graph.energy.grey": "Énergie grise", + "graph.energy.renewable": "Énergie décarbonée", + "graph.energy.mixte": "Énergie mixte", "graph.energy.offshore": "Production offshore", "graph.energy.terrestrial": "Production terrestre", "graph.energy.nuclear": "Nucléaire", @@ -331,5 +340,6 @@ "unit.budget.year": "Mrd€", "unit.budget.day": "€/j", "unit.carbon.year": "T/an", - "unit.carbon.day": "kg/j" + "unit.carbon.day": "kg/j", + "unit.percentage": "{{value}}%" } From 762bd4b65032c28982c2b21e31311aa7a7cc0d24 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Wed, 21 Jun 2023 15:26:40 +0200 Subject: [PATCH 5/9] chore: Fix linting --- packages/client/src/lib/array.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/client/src/lib/array.ts b/packages/client/src/lib/array.ts index 2f705d5f..75dd4160 100644 --- a/packages/client/src/lib/array.ts +++ b/packages/client/src/lib/array.ts @@ -92,4 +92,3 @@ type Tail = T extends [infer _, ...infer TTail] : never; type KeyUnknownGuard = T extends string | number | symbol ? T : any; - From b95b4902918d1414a343b642144bc3345b4a40dd Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Thu, 22 Jun 2023 19:23:37 +0200 Subject: [PATCH 6/9] refactor(useMostImpactfulActions): Adapt to new play store structure --- .../play/context/hooks/player/index.ts | 1 - .../context/hooks/player/useCurrentPlayer.ts | 1 + .../hooks/player/useMostImpactfulActions.ts | 25 +++++++++----- .../play/context/hooks/player/useMyProfile.ts | 33 ------------------- 4 files changed, 18 insertions(+), 42 deletions(-) delete mode 100644 packages/client/src/modules/play/context/hooks/player/useMyProfile.ts diff --git a/packages/client/src/modules/play/context/hooks/player/index.ts b/packages/client/src/modules/play/context/hooks/player/index.ts index 65b241a8..f9e61f19 100644 --- a/packages/client/src/modules/play/context/hooks/player/index.ts +++ b/packages/client/src/modules/play/context/hooks/player/index.ts @@ -1,4 +1,3 @@ export * from "./useCurrentPlayer"; export * from "./useMostImpactfulActions"; -export * from "./useMyProfile"; export * from "./usePersona"; diff --git a/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts b/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts index 01fd8582..cd34aed4 100644 --- a/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts +++ b/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts @@ -33,6 +33,7 @@ function useCurrentPlayer() { return { player, profile: player.profile, + personalization: player.profile.personalization, playerActions: player.actions, actionPointsAvailableAtCurrentStep: STEPS[game.step].availableActionPoints, teamActions: team.actions, diff --git a/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts b/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts index cc08aca1..b4e0128a 100644 --- a/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts +++ b/packages/client/src/modules/play/context/hooks/player/useMostImpactfulActions.ts @@ -1,7 +1,5 @@ import { sumBy } from "lodash"; import { computeNewConsumptionData } from "../../../utils/consumption"; -import { usePersona, usePlayerActions } from "../../playContext"; -import { useMyProfile } from "./useMyProfile"; import { sumFor } from "../../../../persona"; import { ConsumptionDatum, @@ -11,6 +9,9 @@ import { indexArrayBy } from "../../../../../lib/array"; import { fromEntries } from "../../../../../lib/object"; import { useMemo } from "react"; import { Action } from "../../../../../utils/types"; +import { useCurrentPlayer } from "./useCurrentPlayer"; +import { usePersona } from "./usePersona"; +import { usePlay } from "../../playContext"; export { useMostImpactfulActions }; export type { ImpactfulAction }; @@ -28,14 +29,16 @@ type ImpactfulAction = { }; function useMostImpactfulActions({ limit = 5 }: { limit?: number } = {}) { - const { personalization } = useMyProfile(); - const { playerActions } = usePlayerActions(); + const { consumptionActionById } = usePlay(); + const { personalization, playerActions } = useCurrentPlayer(); const { personaBySteps } = usePersona(); const mostImpactfulActions = useMemo(() => { const initialPersona = personaBySteps[0]; - const actions = playerActions.map((playerAction) => playerAction.action); - const actionNameToPlayerAction = indexArrayBy(playerActions, "action.name"); + const actions = playerActions.map( + (playerAction) => consumptionActionById[playerAction.actionId] + ); + const PlayerActionByActionId = indexArrayBy(playerActions, "actionId"); const initialConsumptionByType = computeConsumptionByType( initialPersona.consumption @@ -68,13 +71,19 @@ function useMostImpactfulActions({ limit = 5 }: { limit?: number } = {}) { return { action, - isPerformed: actionNameToPlayerAction[action.name!].isPerformed, + isPerformed: PlayerActionByActionId[action.id].isPerformed, consumptionImpacts, }; }); return mostImpactfulActions; - }, [limit, personaBySteps, personalization, playerActions]); + }, [ + consumptionActionById, + limit, + personaBySteps, + personalization, + playerActions, + ]); return { mostImpactfulActions, diff --git a/packages/client/src/modules/play/context/hooks/player/useMyProfile.ts b/packages/client/src/modules/play/context/hooks/player/useMyProfile.ts deleted file mode 100644 index d81355c5..00000000 --- a/packages/client/src/modules/play/context/hooks/player/useMyProfile.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { usePlay } from "../../playContext"; -import { - IEnrichedGame, - ITeamWithPlayers, - Player, -} from "../../../../../utils/types"; -import { useAuth } from "../../../../auth/authProvider"; - -export { useMyProfile }; - -// TODO: get rid of hook once PR refactoring play context store is merged. -function useMyProfile() { - const { game } = usePlay(); - const { user } = useAuth(); - const profile = user && getUserTeamAndPlayer(game, user.id)?.player?.profile; - - return { - profile, - personalization: profile.personalization, - }; -} - -function getUserTeamAndPlayer(game: IEnrichedGame, userId: number) { - const team = game.teams.find((team: ITeamWithPlayers) => - team.players.find((player: Player) => player.userId === userId) - ); - - const player = team?.players.find( - (player: Player) => player.userId === userId - ); - - return { team, player }; -} From 84763cb6b4d3767b69d8ceccb400b570e1abea38 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Mon, 26 Jun 2023 14:14:52 +0200 Subject: [PATCH 7/9] fix(SynthesisMostImpactfulActionsTab): Display consumptions with 1 more digit --- .../SynthesisMostImpactfulActionsTab.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx b/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx index 90158032..bae2fae4 100644 --- a/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx +++ b/packages/client/src/modules/play/playerActions/SynthesisMostImpactfulActionsTab.tsx @@ -68,6 +68,7 @@ function ActionCard({ alignItems="center" variant="h4" color={theme.palette.primary.contrastText} + sx={{ cursor: "pointer" }} > @@ -80,7 +81,12 @@ function ActionCard({ "synthesis.player.most-impactful-actions-section.action-missed" )} > - + @@ -117,7 +123,7 @@ function ActionCard({ - `${formatConsumption(value)} ${t( + `${formatConsumption(value, { fractionDigits: 1 })} ${t( "unit.watthour-per-day.kilo" )}` } @@ -135,13 +141,17 @@ function ActionCard({ - {formatConsumption(consoImpact.initial)}{" "} + {formatConsumption(consoImpact.initial, { + fractionDigits: 1, + })}{" "} {t("unit.watthour-per-day.kilo")}     - {formatConsumption(consoImpact.final)}{" "} + {formatConsumption(consoImpact.final, { + fractionDigits: 1, + })}{" "} {t("unit.watthour-per-day.kilo")} From 1574a61ea73e0fb9c7d0dcb6dbfdb88d010ed44b Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Mon, 26 Jun 2023 16:29:25 +0200 Subject: [PATCH 8/9] fix(useCurrentPlayer): Set default value to personalization --- .../modules/play/context/hooks/player/useCurrentPlayer.ts | 6 +++++- packages/client/src/utils/types.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts b/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts index cd34aed4..e45f597e 100644 --- a/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts +++ b/packages/client/src/modules/play/context/hooks/player/useCurrentPlayer.ts @@ -29,11 +29,15 @@ function useCurrentPlayer() { ), [game.step, team.actions, productionActionById] ); + const personalization = useMemo( + () => player?.profile?.personalization || {}, + [player?.profile?.personalization] + ); return { player, profile: player.profile, - personalization: player.profile.personalization, + personalization, playerActions: player.actions, actionPointsAvailableAtCurrentStep: STEPS[game.step].availableActionPoints, teamActions: team.actions, diff --git a/packages/client/src/utils/types.ts b/packages/client/src/utils/types.ts index d89255fe..4cd948cf 100644 --- a/packages/client/src/utils/types.ts +++ b/packages/client/src/utils/types.ts @@ -68,7 +68,7 @@ interface Player { lastName: string; roleId: number; }; - profile: Profile; + profile: Profile | null; actions: PlayerActions[]; hasFinishedStep: boolean; actionPointsLimitExceeded?: boolean; From f2bd23b9a2afdbcd7378456247fc668621b4d139 Mon Sep 17 00:00:00 2001 From: Baptiste Studer Date: Mon, 26 Jun 2023 16:34:55 +0200 Subject: [PATCH 9/9] fix(usePersona): Use personalization --- .../src/modules/play/context/hooks/player/usePersona.ts | 4 +--- packages/client/src/utils/types.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/client/src/modules/play/context/hooks/player/usePersona.ts b/packages/client/src/modules/play/context/hooks/player/usePersona.ts index d66d2f6e..13b95a9d 100644 --- a/packages/client/src/modules/play/context/hooks/player/usePersona.ts +++ b/packages/client/src/modules/play/context/hooks/player/usePersona.ts @@ -7,9 +7,7 @@ export { usePersona }; function usePersona() { const { consumptionActionById, game, productionActionById } = usePlay(); - const { player, playerActions, teamActions } = useCurrentPlayer(); - - const personalization = player.profile.personalization; + const { personalization, playerActions, teamActions } = useCurrentPlayer(); const initialPersona = buildInitialPersona( personalization, diff --git a/packages/client/src/utils/types.ts b/packages/client/src/utils/types.ts index 4cd948cf..d89255fe 100644 --- a/packages/client/src/utils/types.ts +++ b/packages/client/src/utils/types.ts @@ -68,7 +68,7 @@ interface Player { lastName: string; roleId: number; }; - profile: Profile | null; + profile: Profile; actions: PlayerActions[]; hasFinishedStep: boolean; actionPointsLimitExceeded?: boolean;