diff --git a/packages/client/src/lib/time.ts b/packages/client/src/lib/time.ts index 8883d3c1..bfdec718 100644 --- a/packages/client/src/lib/time.ts +++ b/packages/client/src/lib/time.ts @@ -2,7 +2,7 @@ import { differenceInDays } from "date-fns"; import { ENERGY_SHIFT_TARGET_YEAR } from "../modules/common/constants"; import { userLocale } from "../modules/translations"; -export { formatDate, getDaysTo2050, getDaysToEnergyShiftTargetYear }; +export { formatDate, getDaysToEnergyShiftTargetYear }; type DateFormat = "date-at-time" | "full-date-at-time"; @@ -37,14 +37,6 @@ function formatDate( ).format(new Date(date)); } -// TODO: Replace use with getDaysToEnergyShiftTargetYear. -/** - * @deprecated Use getDaysToEnergyShiftTargetYear instead. - */ -function getDaysTo2050() { - return Math.round(differenceInDays(new Date("01/01/2050"), new Date())); -} - function getDaysToEnergyShiftTargetYear(refDate: Date = new Date()) { return Math.round( differenceInDays(new Date(`01/01/${ENERGY_SHIFT_TARGET_YEAR}`), refDate) diff --git a/packages/client/src/modules/charts/DetailsEnergyBars.tsx b/packages/client/src/modules/charts/DetailsEnergyBars.tsx index 2622c621..23facf8d 100644 --- a/packages/client/src/modules/charts/DetailsEnergyBars.tsx +++ b/packages/client/src/modules/charts/DetailsEnergyBars.tsx @@ -9,12 +9,10 @@ import { Cell, } from "recharts"; import { EnergyPalette, ProductionPalette } from "../../utils/theme"; -import { hasNuclear, roundValue } from "../common/utils"; +import { roundValue } from "../common/utils"; import { ConsumptionDatum } from "../persona/consumption"; import { Persona } from "../persona/persona"; import { ProductionDatum } from "../persona/production"; -import { productionConstants } from "../play"; -import { usePlay } from "../play/context/playContext"; import { translateName, useTranslation } from "../translations"; import { Typography } from "../common/components/Typography"; @@ -44,20 +42,10 @@ function DetailsEnergyProductionBars({ persona: Persona; }) { const theme = useTheme(); - const { game } = usePlay(); - - const personaValues = persona.production.filter( - ({ type }: ProductionDatum) => { - if (!hasNuclear(game) && type === productionConstants.NUCLEAR.name) { - return false; - } - return true; - } - ); return DetailsEnergyBars( "production", - personaValues, + persona.productionDisplayed, theme.palette.production, title ); diff --git a/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceDetailsForPlayerGraph.tsx b/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceDetailsForPlayerGraph.tsx new file mode 100644 index 00000000..3e2ef394 --- /dev/null +++ b/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceDetailsForPlayerGraph.tsx @@ -0,0 +1,52 @@ +import React, { useMemo } from "react"; +import { Persona } from "../../persona/persona"; +import { useTranslation } from "../../translations/useTranslation"; +import { + DetailsEnergyConsumptionBars, + DetailsEnergyProductionBars, +} from "../DetailsEnergyBars"; +import { + EnergyConsumptionButtons, + EnergyProductionButtons, +} from "../../common/components/EnergyButtons"; +import { StepDatum } from "./types"; +import { getStackName } from "./utils"; + +export { EnergyBalanceDetailsForPlayerGraph }; + +function EnergyBalanceDetailsForPlayerGraph({ + stepDatum, + getPersonaAtStep, +}: { + stepDatum: StepDatum; + getPersonaAtStep: (step: number) => Persona; +}) { + const { t } = useTranslation(); + + const DetailsContent = useMemo(() => { + const persona = getPersonaAtStep(stepDatum.step); + + const graphTitle = t( + "page.player.statistics.tabs.energy-balance.graphs.details.title", + { stackName: getStackName({ stepDatum, t }) } + ); + + if (stepDatum.type === "consumption") { + return ( + <> + + + + ); + } else if (stepDatum.type === "production") { + return ( + <> + + + + ); + } + }, [stepDatum, getPersonaAtStep, t]); + + return <>{DetailsContent}; +} diff --git a/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceForPlayerChart.tsx b/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceForPlayerChart.tsx new file mode 100644 index 00000000..a636fd49 --- /dev/null +++ b/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceForPlayerChart.tsx @@ -0,0 +1,99 @@ +import { Box } from "@mui/material"; +import _ from "lodash"; +import React, { useMemo, useState } from "react"; +import { + StackedBars, + StackedBarsStackData, + StackedBarsStacks, +} from "../StackedBars"; +import { Persona } from "../../persona/persona"; +import { STEPS, getStepTypes } from "../../play"; +import { formatProduction } from "../../../lib/formatter"; +import { usePlay } from "../../play/context/playContext"; +import { useTranslation } from "../../translations/useTranslation"; +import { StepDatum } from "./types"; +import { + computeConsumptionBarsForPersona, + computeProductionBarsForPersona, + getStackName, +} from "./utils"; +import { EnergyBalanceDetailsForPlayerGraph } from "./EnergyBalanceDetailsForPlayerGraph"; +import { buildStack } from "../utils"; + +export { EnergyBalanceForPlayerChart }; + +function EnergyBalanceForPlayerChart({ + getPersonaAtStep, +}: { + getPersonaAtStep: (step: number) => Persona; +}) { + const { t } = useTranslation(); + const { game } = usePlay(); + const [selectedStepDatum, setSelectedStepDatum] = useState( + null + ); + + const stepDataToDisplay = useMemo((): StepDatum[] => { + const maxStep = STEPS.findIndex((s) => s.id === "final-situation"); + const endStep = Math.min(game.lastFinishedStep + 1, maxStep); + + return _.range(0, endStep) + .map(getStepTypes) + .map((stepTypes, idx) => + stepTypes.map((stepType) => ({ step: idx, type: stepType })) + ) + .flat(); + }, [game.lastFinishedStep]); + + const MainGraph = useMemo(() => { + const data = stepDataToDisplay.map((stepDatum): StackedBarsStackData => { + const computeBars = + stepDatum.type === "consumption" + ? computeConsumptionBarsForPersona + : computeProductionBarsForPersona; + + return buildStack({ + bars: computeBars({ + persona: getPersonaAtStep(stepDatum.step), + t, + }), + label: getStackName({ stepDatum, t }), + }); + }); + + const stacks: StackedBarsStacks = { + data, + yAxisUnitLabel: t("unit.watthour-per-day-bare.kilo"), + palettes: ["energy", "production"], + yAxisValueFormatter: (value) => + formatProduction(value, { fractionDigits: 2 }), + yAxisTicksValueFormatter: (value) => + formatProduction(value, { fractionDigits: 0 }), + }; + + return ( + { + if (chartState?.activeTooltipIndex != null) { + const stepDatum = stepDataToDisplay[chartState.activeTooltipIndex]; + setSelectedStepDatum(stepDatum); + } + }} + /> + ); + }, [stepDataToDisplay, getPersonaAtStep, t]); + + return ( + + {MainGraph} + {selectedStepDatum && ( + + )} + + ); +} diff --git a/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceForTeamChart.tsx b/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceForTeamChart.tsx new file mode 100644 index 00000000..44814522 --- /dev/null +++ b/packages/client/src/modules/charts/EnergyBalanceCharts/EnergyBalanceForTeamChart.tsx @@ -0,0 +1,75 @@ +import { Box } from "@mui/material"; +import React, { useMemo } from "react"; +import { + StackedBars, + StackedBarsStackData, + StackedBarsStacks, +} from "../StackedBars"; +import { formatProduction, formatUserName } from "../../../lib/formatter"; +import { usePersonaByUserId, usePlay } from "../../play/context/playContext"; +import { useTranslation } from "../../translations/useTranslation"; +import { + computeConsumptionBarsForPersona, + computeProductionBarsForPersona, +} from "./utils"; +import { ITeam } from "../../../utils/types"; +import { buildStack } from "../utils"; + +export { EnergyBalanceForTeamChart }; + +function EnergyBalanceForTeamChart({ team }: { team: ITeam }) { + const { t } = useTranslation(); + const { players } = usePlay(); + + const playersInTeam = useMemo( + () => players.filter((p) => p.teamId === team.id), + [players, team] + ); + const userIds = useMemo( + () => playersInTeam.map((p) => p.userId), + [playersInTeam] + ); + const personaByUserId = usePersonaByUserId(userIds); + + const Graph = useMemo(() => { + const consumptionStacks: StackedBarsStackData[] = playersInTeam.map( + (player) => + buildStack({ + bars: computeConsumptionBarsForPersona({ + persona: personaByUserId[player.userId].currentPersona, + t, + }), + label: formatUserName(player.user), + }) + ); + + const productionStack: StackedBarsStackData = buildStack({ + bars: computeProductionBarsForPersona({ + persona: personaByUserId[playersInTeam[0].userId].currentPersona, + t, + }), + label: t("graph.common.production"), + }); + + const data = [...consumptionStacks, productionStack]; + + const stacks: StackedBarsStacks = { + data, + yAxisUnitLabel: t("unit.watthour-per-day-bare.kilo"), + palettes: ["energy", "production"], + yAxisValueFormatter: (value) => + formatProduction(value, { fractionDigits: 2 }), + yAxisTicksValueFormatter: (value) => + formatProduction(value, { fractionDigits: 0 }), + }; + + return ( + + ); + }, [personaByUserId, playersInTeam, t]); + + return {Graph}; +} diff --git a/packages/client/src/modules/charts/EnergyBalanceCharts/index.ts b/packages/client/src/modules/charts/EnergyBalanceCharts/index.ts new file mode 100644 index 00000000..c093fdbc --- /dev/null +++ b/packages/client/src/modules/charts/EnergyBalanceCharts/index.ts @@ -0,0 +1,2 @@ +export { EnergyBalanceForPlayerChart } from "./EnergyBalanceForPlayerChart"; +export { EnergyBalanceForTeamChart } from "./EnergyBalanceForTeamChart"; diff --git a/packages/client/src/modules/charts/EnergyBalanceCharts/types.ts b/packages/client/src/modules/charts/EnergyBalanceCharts/types.ts new file mode 100644 index 00000000..a0c73c76 --- /dev/null +++ b/packages/client/src/modules/charts/EnergyBalanceCharts/types.ts @@ -0,0 +1,5 @@ +import { GameStepType } from "../../play"; + +export type { StepDatum }; + +type StepDatum = { step: number; type: GameStepType }; diff --git a/packages/client/src/modules/charts/EnergyBalanceCharts/utils.ts b/packages/client/src/modules/charts/EnergyBalanceCharts/utils.ts new file mode 100644 index 00000000..cc253a37 --- /dev/null +++ b/packages/client/src/modules/charts/EnergyBalanceCharts/utils.ts @@ -0,0 +1,76 @@ +import { StackedBarsBar } from "../StackedBars"; +import { Persona } from "../../persona/persona"; +import { sumReducer } from "../../../lib/array"; +import { filterOutDuplicates } from "../../common/utils"; +import { I18nTranslateFunction } from "../../translations"; +import { StepDatum } from "./types"; + +export { + computeConsumptionBarsForPersona, + computeProductionBarsForPersona, + getStackName, +}; + +function computeConsumptionBarsForPersona({ + persona, + t, +}: { + persona: Persona; + t: I18nTranslateFunction; +}): StackedBarsBar[] { + const consumptionTypes = persona.consumption + .map((consumption) => consumption.type) + .filter(filterOutDuplicates) + .sort() + .reverse(); + + const bars: StackedBarsBar[] = consumptionTypes.map((type) => ({ + key: type, + label: t(`energy.${type}`), + total: persona.consumption + .filter((datum) => datum.type === type) + .map((datum) => datum.value) + .reduce(sumReducer, 0), + })); + + return bars; +} + +function computeProductionBarsForPersona({ + persona, + t, +}: { + persona: Persona; + t: I18nTranslateFunction; +}): StackedBarsBar[] { + const productionTypes = persona.productionDisplayed + .map((production) => production.type) + .filter(filterOutDuplicates) + .sort() + .reverse(); + + const bars: StackedBarsBar[] = productionTypes.map((type) => ({ + key: type, + label: t(`graph.energy.${type}`), + total: persona.productionDisplayed + .filter((datum) => datum.type === type) + .map((datum) => datum.value) + .reduce(sumReducer, 0), + })); + + return bars; +} + +function getStackName({ + stepDatum, + t, +}: { + stepDatum: StepDatum; + t: I18nTranslateFunction; +}) { + return stepDatum.step === 0 + ? stepDatum.type === "consumption" + ? t("graph.step.first.consumption.name") + : t("graph.step.first.production.name") + : t("graph.step.other.name", { stepNumber: stepDatum.step }); +} diff --git a/packages/client/src/modules/charts/ResourcesPerProductionTypeChart.tsx b/packages/client/src/modules/charts/ResourcesPerProductionTypeChart.tsx index 7064ec2d..984997ff 100644 --- a/packages/client/src/modules/charts/ResourcesPerProductionTypeChart.tsx +++ b/packages/client/src/modules/charts/ResourcesPerProductionTypeChart.tsx @@ -26,7 +26,7 @@ function ResourcesPerProductionTypeChart({ const graphStacks: StackedBarsStacks = useMemo(() => { const data: StackedBarsStackData[] = pipe( - persona[resourceType], + persona[`${resourceType}Displayed`], (resources: PhysicalResourceNeedDatum[]) => resources.reduce( ( diff --git a/packages/client/src/modules/charts/ResourcesPerStepChart.tsx b/packages/client/src/modules/charts/ResourcesPerStepChart.tsx index 11691f8f..1ec71441 100644 --- a/packages/client/src/modules/charts/ResourcesPerStepChart.tsx +++ b/packages/client/src/modules/charts/ResourcesPerStepChart.tsx @@ -48,7 +48,7 @@ function ResourcesPerStepChart({ const computeBarsForPersona = useCallback( (persona: Persona): StackedBarsBar[] => { const indexBarByResourceName = (persona: Persona) => - persona[resourceType].reduce( + persona[`${resourceType}Displayed`].reduce( (barIndexedByResourceName, resourceDatum) => { if ( !barIndexedByResourceName[ diff --git a/packages/client/src/modules/charts/StackedBars.tsx b/packages/client/src/modules/charts/StackedBars.tsx index 37bed82c..81e86390 100644 --- a/packages/client/src/modules/charts/StackedBars.tsx +++ b/packages/client/src/modules/charts/StackedBars.tsx @@ -511,7 +511,11 @@ function StackedBars({ } /> {chartConfig.barsProps.map((props, idx) => ( - + ))} {chartConfig.linesProps.map((props, idx) => ( diff --git a/packages/client/src/modules/charts/StackedEnergyBars.tsx b/packages/client/src/modules/charts/StackedEnergyBars.tsx deleted file mode 100644 index a355cd98..00000000 --- a/packages/client/src/modules/charts/StackedEnergyBars.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { Card, Grid, useTheme } from "@mui/material"; -import { - BarChart, - Bar, - XAxis, - YAxis, - Tooltip, - Legend, - ResponsiveContainer, - TooltipProps, -} from "recharts"; -import { CategoricalChartFunc } from "recharts/types/chart/generateCategoricalChart"; -import { EnergyPalette, ProductionPalette } from "../../utils/theme"; -import { hasNuclear, filterOutDuplicates } from "../common/utils"; -import { usePlay } from "../play/context/playContext"; -import { productionConstants } from "../play"; -import { t, useTranslation } from "../translations"; -import { ConsumptionType } from "../persona/consumption"; -import { ProductionActionType } from "../../utils/types"; -import { Typography } from "../common/components/Typography"; - -export { StackedEnergyBars }; - -function StackedEnergyBars({ - title, - data, - tick = true, - onClick, -}: { - title?: string; - data: any[]; - tick?: boolean; - onClick?: CategoricalChartFunc; -}) { - const theme = useTheme(); - const { t } = useTranslation(); - const { game } = usePlay(); - - const maximumTotal = Math.max( - ...data.map((pileData: any) => pileData.total), - 0 - ); - - const CustomTooltip = ({ - active, - payload, - label, - }: TooltipProps): JSX.Element => { - if (active && payload && payload.length) { - return ( - - - {label} - - {Object.entries(payload[0].payload) - .filter(([key]) => key !== "name" && key !== "type") - .filter( - ([key]) => - key !== productionConstants.NUCLEAR.name || hasNuclear(game) - ) - .map(([key, value]) => ( - - {translateLabel(key)}:{" "} - {t("unit.watthour-per-day.kilo", { - value, - })} - - ))} - - ); - } - return <>; - }; - - const uniqueBars = data - .flatMap((d) => - Object.keys(d) - .filter((key) => !["name", "total", "type"].includes(key)) - .filter( - (key) => key !== productionConstants.NUCLEAR.name || hasNuclear(game) - ) - ) - .filter(filterOutDuplicates) - .reverse(); - - return ( - - {!!title && ( - - {title} - - )} - - - - - } /> - - {uniqueBars.map((key, idx) => ( - - ))} - - - - ); -} - -function translateLabel(value: string): string { - return ( - t(`graph.energy.${value as ConsumptionType | ProductionActionType}`) ?? - "Unknown" - ); -} diff --git a/packages/client/src/modules/charts/index.ts b/packages/client/src/modules/charts/index.ts index a055ce79..7d66850e 100644 --- a/packages/client/src/modules/charts/index.ts +++ b/packages/client/src/modules/charts/index.ts @@ -1,17 +1,21 @@ -import { StackedEnergyBars } from "./StackedEnergyBars"; import { LineEvolution } from "./LineEvolution"; import { DetailsEnergyConsumptionBars, DetailsEnergyProductionBars, } from "./DetailsEnergyBars"; +import { + EnergyBalanceForPlayerChart, + EnergyBalanceForTeamChart, +} from "./EnergyBalanceCharts"; import { ResourcesPerProductionTypeChart } from "./ResourcesPerProductionTypeChart"; import { ResourcesPerStepChart } from "./ResourcesPerStepChart"; export { - StackedEnergyBars, LineEvolution, DetailsEnergyConsumptionBars, DetailsEnergyProductionBars, + EnergyBalanceForPlayerChart, + EnergyBalanceForTeamChart, ResourcesPerProductionTypeChart, ResourcesPerStepChart, }; diff --git a/packages/client/src/modules/charts/utils/index.ts b/packages/client/src/modules/charts/utils/index.ts new file mode 100644 index 00000000..bb9d4615 --- /dev/null +++ b/packages/client/src/modules/charts/utils/index.ts @@ -0,0 +1,18 @@ +import sumBy from "lodash/sumBy"; +import { StackedBarsBar, StackedBarsStackData } from "../StackedBars"; + +export { buildStack }; + +function buildStack({ + bars, + label, +}: { + bars: StackedBarsBar[]; + label: string; +}): StackedBarsStackData { + return { + label, + total: sumBy(bars, "total"), + bars, + }; +} diff --git a/packages/client/src/modules/common/components/EnergyButtons/index.tsx b/packages/client/src/modules/common/components/EnergyButtons/index.tsx index 48546f1e..8d5f272c 100644 --- a/packages/client/src/modules/common/components/EnergyButtons/index.tsx +++ b/packages/client/src/modules/common/components/EnergyButtons/index.tsx @@ -1,90 +1,132 @@ import { Box, Button, useTheme, Tooltip } from "@mui/material"; -import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; +import { useMemo } from "react"; import { Persona } from "../../../persona/persona"; -import { - I18nTranslateFunction, - translateName, - useTranslation, -} from "../../../translations"; -import { hasNuclear, roundValue } from "../../utils"; -import { usePlay } from "../../../play/context/playContext"; +import { translateName, useTranslation } from "../../../translations"; +import { roundValue } from "../../utils"; +import { Icon } from "../Icon"; export { EnergyConsumptionButtons, EnergyProductionButtons }; -interface EnergyType { - color: string; - name: string; - type: string; -} - function EnergyConsumptionButtons({ persona }: { persona: Persona }) { const theme = useTheme(); const { t } = useTranslation(); - const energies: EnergyType[] = [ - { - color: theme.palette.energy.grey, - name: t("energy.grey"), - type: "grey", - }, - { - color: theme.palette.energy.fossil, - name: t("energy.fossil"), - type: "fossil", - }, - { - color: theme.palette.energy.renewable, - name: t("energy.renewable"), - type: "renewable", - }, - { - color: theme.palette.energy.mixte, - name: t("energy.mixte"), - type: "mixte", - }, - ]; + const items: EnergyItem[] = useMemo((): EnergyItem[] => { + const energyTypesDisplayed = new Set( + persona.consumption.map((datum) => datum.type) + ); + + const energies = [ + { + color: theme.palette.energy.grey, + name: t("energy.grey"), + type: "grey", + }, + { + color: theme.palette.energy.fossil, + name: t("energy.fossil"), + type: "fossil", + }, + { + color: theme.palette.energy.renewable, + name: t("energy.renewable"), + type: "renewable", + }, + { + color: theme.palette.energy.mixte, + name: t("energy.mixte"), + type: "mixte", + }, + ] as const; + + return energies + .filter((energy) => energyTypesDisplayed.has(energy.type)) + .map((energy) => { + const details = persona.consumption + .filter(({ type }) => type === energy.type) + .map( + ({ name, value }) => + `${translateName("consumption", name)} : ${t( + "unit.watthour-per-day.kilo", + { + value: roundValue(value), + } + )}` + ) + .join("\n"); - return buildEnergyButtons("consumption", energies, persona, t); + return { + name: energy.name, + color: energy.color, + details, + }; + }); + }, [persona.consumption, theme, t]); + + return ; } function EnergyProductionButtons({ persona }: { persona: Persona }) { const theme = useTheme(); const { t } = useTranslation(); - const { game } = usePlay(); - const energies: EnergyType[] = [ - { - color: theme.palette.production.offshore, - name: t("graph.energy.offshore"), - type: "offshore", - }, - { - color: theme.palette.production.terrestrial, - name: t("graph.energy.terrestrial"), - type: "terrestrial", - }, - ]; + const items: EnergyItem[] = useMemo((): EnergyItem[] => { + const energyTypesDisplayed = new Set( + persona.productionDisplayed.map((datum) => datum.type) + ); + + const energies = [ + { + color: theme.palette.production.offshore, + name: t("graph.energy.offshore"), + type: "offshore", + }, + { + color: theme.palette.production.terrestrial, + name: t("graph.energy.terrestrial"), + type: "terrestrial", + }, + { + color: theme.palette.production.nuclear, + name: t("graph.energy.nuclear"), + type: "nuclear", + }, + ] as const; + + return energies + .filter((energy) => energyTypesDisplayed.has(energy.type)) + .map((energy) => { + const details = persona.productionDisplayed + .filter(({ type }) => type === energy.type) + .map( + ({ name, value }) => + `${translateName("production", name)} : ${t( + "unit.watthour-per-day.kilo", + { + value: roundValue(value), + } + )}` + ) + .join("\n"); - if (hasNuclear(game)) { - energies.push({ - color: theme.palette.production.nuclear, - name: t("graph.energy.nuclear"), - type: "nuclear", - }); - } + return { + name: energy.name, + color: energy.color, + details, + }; + }); + }, [persona.productionDisplayed, theme, t]); - return buildEnergyButtons("production", energies, persona, t); + return ; } -function buildEnergyButtons( - stepType: string, - energies: EnergyType[], - persona: Persona, - t: I18nTranslateFunction -) { - const personaValues: any = - stepType === "consumption" ? persona.consumption : persona.production; +interface EnergyItem { + name: string; + details: string; + color: string; +} +function EnergyButtons({ items }: { items: EnergyItem[] }) { return ( - {energies.map((energyTypes) => ( + {items.map((item) => ( - {personaValues - .filter( - ({ type }: { type: string }) => type === energyTypes.type - ) - .map(({ name, value }: { name: string; value: number }) => { - return `${translateName(stepType, name)} : ${t( - "unit.watthour-per-day.kilo", - { - value: roundValue(value), - } - )}`; - }) - .join("\n")} - - } + key={item.name} + title={
{item.details}
} arrow >
))} diff --git a/packages/client/src/modules/common/constants/constants.ts b/packages/client/src/modules/common/constants/constants.ts index 4fed5ebf..bb0c070f 100644 --- a/packages/client/src/modules/common/constants/constants.ts +++ b/packages/client/src/modules/common/constants/constants.ts @@ -6,7 +6,6 @@ export { WEB_SOCKET_URL, }; -// TODO: use this constant where `2050` is hardcoded. const ENERGY_SHIFT_TARGET_YEAR = 2050; const TERMS_OF_USE_URL = "https://ogre-public.s3.eu-west-3.amazonaws.com/cgu-latest.pdf"; diff --git a/packages/client/src/modules/common/utils.tsx b/packages/client/src/modules/common/utils.tsx index 2d1f9e3e..81d941cb 100644 --- a/packages/client/src/modules/common/utils.tsx +++ b/packages/client/src/modules/common/utils.tsx @@ -1,14 +1,9 @@ -import { IGame } from "../../utils/types"; -import { getStepIndexById } from "../play"; +export { filterOutDuplicates, roundValue }; -export function roundValue(value: number) { +function roundValue(value: number) { return Math.round((value + Number.EPSILON) * 100) / 100; } -export function hasNuclear(game: IGame) { - return game.step >= getStepIndexById("production-3"); -} - -export function filterOutDuplicates(value: any, index: number, array: any[]) { +function filterOutDuplicates(value: any, index: number, array: any[]) { return array.indexOf(value) === index; } diff --git a/packages/client/src/modules/persona/persona.ts b/packages/client/src/modules/persona/persona.ts index d4c39f5d..2e1f187e 100644 --- a/packages/client/src/modules/persona/persona.ts +++ b/packages/client/src/modules/persona/persona.ts @@ -5,7 +5,7 @@ import { computeCarbonFootprint, } from "../play/utils/carbonFootprint"; import { ConsumptionDatum, getConsumptionFromProfile } from "./consumption"; -import { PRODUCTION, ProductionDatum } from "./production"; +import { ProductionDatum } from "./production"; import { computeIntermediateValues } from "./consumption/computing"; import { computeMaterials, @@ -13,6 +13,7 @@ import { PhysicalResourceNeedDatum, } from "../play/gameEngines/resourcesEngine"; import { ProductionAction, TeamAction } from "../../utils/types"; +import { computeEnergyProduction } from "../play/utils/production"; export { buildInitialPersona }; export type { Persona }; @@ -24,8 +25,11 @@ interface Persona { points: number; consumption: readonly ConsumptionDatum[]; production: ProductionDatum[]; + productionDisplayed: ProductionDatum[]; materials: PhysicalResourceNeedDatum[]; + materialsDisplayed: PhysicalResourceNeedDatum[]; metals: PhysicalResourceNeedDatum[]; + metalsDisplayed: PhysicalResourceNeedDatum[]; } const buildInitialPersona = ( @@ -42,19 +46,46 @@ const buildInitialPersona = ( intermediateValues ); + const production: ProductionDatum[] = Object.values(productionActionById).map( + (productionAction) => { + return { + name: productionAction.name, + type: productionAction.type, + carbonType: productionAction.carbonType, + revealOnStep: productionAction.revealOnStep, + value: computeEnergyProduction( + productionAction, + productionAction.defaultTeamValue + ), + }; + } + ); + + const productionDisplayed = production.filter((p) => !p.revealOnStep); + const carbonProductionElectricMix = - computeCarbonProductionElectricMix(PRODUCTION); + computeCarbonProductionElectricMix(production); const carbonFootprint = computeCarbonFootprint( carbonProductionElectricMix, consumption as ConsumptionDatum[] ); const materials = computeMaterials( - PRODUCTION, + production, + teamActions, + productionActionById + ); + const materialsDisplayed = computeMaterials( + productionDisplayed, + teamActions, + productionActionById + ); + const metals = computeMetals(production, teamActions, productionActionById); + const metalsDisplayed = computeMetals( + productionDisplayed, teamActions, productionActionById ); - const metals = computeMetals(PRODUCTION, teamActions, productionActionById); const persona: Persona = { budget: 13.7, @@ -62,9 +93,12 @@ const buildInitialPersona = ( points: 0, carbonFootprint, consumption, - production: PRODUCTION, + production, + productionDisplayed, materials, + materialsDisplayed, metals, + metalsDisplayed, }; return persona; diff --git a/packages/client/src/modules/persona/production.ts b/packages/client/src/modules/persona/production.ts index 3753cd23..935930c5 100644 --- a/packages/client/src/modules/persona/production.ts +++ b/packages/client/src/modules/persona/production.ts @@ -3,9 +3,7 @@ import { ProductionActionType, ProductionCarbonType, } from "../../utils/types"; -import { productionConstants } from "../play/constants"; -export { PRODUCTION }; export type { ProductionDatum }; interface ProductionDatum { @@ -16,74 +14,3 @@ interface ProductionDatum { value: number; revealOnStep?: number; } - -// TODO: should be moved to ProductionAction model in database -const PRODUCTION: ProductionDatum[] = [ - { - name: productionConstants.GEOTHERMAL.name, - value: 0.0054, - type: "offshore", - carbonType: "decarbonated", - }, - { - name: productionConstants.HYDRAULIC.name, - value: 2.67, - type: "offshore", - carbonType: "decarbonated", - }, - { - name: productionConstants.OFF_SHORE_WIND_TURBINE.name, - value: 0.4, - type: "offshore", - carbonType: "decarbonated", - }, - { - name: productionConstants.TIDAL.name, - value: 0.02226, - type: "offshore", - carbonType: "decarbonated", - }, - { - name: productionConstants.WAVE.name, - value: 0.000167, - type: "offshore", - carbonType: "decarbonated", - }, - { - name: productionConstants.NUCLEAR.name, - value: 0, - type: "nuclear", - carbonType: "decarbonated", - revealOnStep: 5, - }, - { - name: productionConstants.BIOMASS.name, - value: 7.14, - type: "terrestrial", - carbonType: "decarbonated", - }, - { - name: productionConstants.ON_SHORE_WIND_TURBINE.name, - value: 2.08, - type: "terrestrial", - carbonType: "decarbonated", - }, - { - name: productionConstants.PHOTOVOLTAIC_FARM.name, - value: 0.31, - type: "terrestrial", - carbonType: "decarbonated", - }, - { - name: productionConstants.PHOTOVOLTAIC_ROOF.name, - value: 0.25, - type: "terrestrial", - carbonType: "decarbonated", - }, - { - name: productionConstants.THERMAL_SOLAR.name, - value: 0.11, - type: "terrestrial", - carbonType: "decarbonated", - }, -]; diff --git a/packages/client/src/modules/play/GameConsole/PlayerChart.tsx b/packages/client/src/modules/play/GameConsole/PlayerChart.tsx index 3d735dfd..db87c212 100644 --- a/packages/client/src/modules/play/GameConsole/PlayerChart.tsx +++ b/packages/client/src/modules/play/GameConsole/PlayerChart.tsx @@ -1,15 +1,13 @@ import { ITeam } from "../../../utils/types"; -import { StackedEnergyBars } from "../../charts/StackedEnergyBars"; -import { sumAllValues, sumForAndFormat } from "../../persona"; import { usePersonaByUserId, usePlay } from "../context/playContext"; import { Tabs } from "../../common/components/Tabs"; import { useMemo } from "react"; import { + EnergyBalanceForTeamChart, ResourcesPerProductionTypeChart, ResourcesPerStepChart, } from "../../charts"; import { useTranslation } from "../../translations/useTranslation"; -import { formatUserName } from "../../../lib/formatter"; export { PlayerChart }; @@ -24,16 +22,11 @@ function PlayerChart({ team }: { team: ITeam }) { const personaByUserId = usePersonaByUserId(userIds); const [firstPersona] = Object.values(personaByUserId); - const data = useBuildData({ team }); - const consProdData = data.filter((data) => - ["consumption", "production"].includes(data.type) - ); - const tabs = useMemo(() => { return [ { label: t("page.teacher.statistics.tabs.energy-balance.label"), - component: , + component: , }, { label: t("page.teacher.statistics.tabs.materials.label"), @@ -66,67 +59,7 @@ function PlayerChart({ team }: { team: ITeam }) { ), }, ]; - }, [consProdData, firstPersona, t]); + }, [firstPersona, team, t]); return ; } - -function useBuildData({ team }: { team: ITeam }) { - const { game, players } = usePlay(); - - const playersInTeam = useMemo( - () => players.filter((p) => p.teamId === team.id), - [players, team] - ); - const userIds = useMemo( - () => playersInTeam.map((p) => p.userId), - [playersInTeam] - ); - const personaByUserId = usePersonaByUserId(userIds); - const [firstPersona] = Object.values(personaByUserId); - - return [ - ...playersInTeam.map((player) => { - const userId = player.userId; - const personaBySteps = personaByUserId[userId]!.personaBySteps; - const playerConsumption = - personaBySteps[game.lastFinishedStep].consumption; - - return { - name: formatUserName(player.user), - type: "consumption", - total: sumAllValues(playerConsumption) || 0, - grey: sumForAndFormat(playerConsumption, "grey"), - mixte: sumForAndFormat(playerConsumption, "mixte"), - fossil: sumForAndFormat(playerConsumption, "fossil"), - renewable: sumForAndFormat(playerConsumption, "renewable"), - }; - }), - firstPersona - ? { - name: "Production", - type: "production", - total: sumAllValues(firstPersona.currentPersona.production) || 0, - offshore: sumForAndFormat( - firstPersona.currentPersona.production, - "offshore" - ), - nuclear: sumForAndFormat( - firstPersona.currentPersona.production, - "nuclear" - ), - terrestrial: sumForAndFormat( - firstPersona.currentPersona.production, - "terrestrial" - ), - } - : { - name: "Production", - type: "production", - total: 0, - offshore: 0, - nuclear: 0, - terrestrial: 0, - }, - ]; -} diff --git a/packages/client/src/modules/play/GameConsole/utils/statsConsoleValues.tsx b/packages/client/src/modules/play/GameConsole/utils/statsConsoleValues.tsx index 8bd85b89..1292d312 100644 --- a/packages/client/src/modules/play/GameConsole/utils/statsConsoleValues.tsx +++ b/packages/client/src/modules/play/GameConsole/utils/statsConsoleValues.tsx @@ -4,7 +4,7 @@ import { formatCarbonFootprint, formatPoints, } from "../../../../lib/formatter"; -import { getDaysTo2050 } from "../../../../lib/time"; +import { getDaysToEnergyShiftTargetYear } from "../../../../lib/time"; import { IEnrichedGame, ITeam } from "../../../../utils/types"; import { MAX_TEAMS_POINTS } from "../../constants"; import { synthesisConstants } from "../../playerActions/constants/synthesis"; @@ -20,7 +20,9 @@ function computeBudget( ) { if (isSynthesisStep) { const budgetSpentTotalFrance = - (budgetSpent * getDaysTo2050() * synthesisConstants.FRANCE_POPULATION) / + (budgetSpent * + getDaysToEnergyShiftTargetYear() * + synthesisConstants.FRANCE_POPULATION) / synthesisConstants.MILLIARD; return formatBudget(budgetSpentTotalFrance); diff --git a/packages/client/src/modules/play/Stats/StatsGraphs.tsx b/packages/client/src/modules/play/Stats/StatsGraphs.tsx index 38f771ee..0890b229 100644 --- a/packages/client/src/modules/play/Stats/StatsGraphs.tsx +++ b/packages/client/src/modules/play/Stats/StatsGraphs.tsx @@ -1,22 +1,11 @@ import { Box } from "@mui/material"; -import React, { useMemo, useState } from "react"; +import React, { useMemo } from "react"; import { - StackedEnergyBars, - DetailsEnergyConsumptionBars, - DetailsEnergyProductionBars, ResourcesPerProductionTypeChart, ResourcesPerStepChart, + EnergyBalanceForPlayerChart, } from "../../charts"; import { PlayBox } from "../Components"; -import { - EnergyConsumptionButtons, - EnergyProductionButtons, -} from "../../common/components/EnergyButtons"; -import { STEPS } from "../constants"; -import _ from "lodash"; -import { usePlay } from "../context/playContext"; -import { sumAllValues, sumForAndFormat } from "../../persona"; -import { IGame } from "../../../utils/types"; import { Tabs } from "../../common/components/Tabs"; import { useTranslation } from "../../translations"; import { usePersona } from "../context/hooks/player"; @@ -24,18 +13,17 @@ import { Typography } from "../../common/components/Typography"; export { StatsGraphs }; -function isNotFinishedStep(step: number, game: IGame) { - return step > game.lastFinishedStep; -} - function StatsGraphs() { const { t } = useTranslation(); + const { getPersonaAtStep } = usePersona(); const tabs = useMemo(() => { return [ { label: t("page.player.statistics.tabs.energy-balance.label"), - component: , + component: ( + + ), }, { label: t("page.player.statistics.tabs.materials.label"), @@ -46,7 +34,7 @@ function StatsGraphs() { component: , }, ]; - }, [t]); + }, [getPersonaAtStep, t]); return ( @@ -62,134 +50,6 @@ function StatsGraphs() { ); } -function ConsumptionAndProductionGraph() { - const { t } = useTranslation(); - const { game } = usePlay(); - const { personaBySteps, getPersonaAtStep } = usePersona(); - const [selectedBarIdx, setSelectedBarIdx] = useState(); - - const bars = useMemo(() => { - const initialPersona = getPersonaAtStep(0); - const initialValues = [ - { - name: t("graph.step.first.consumption.name"), - total: sumAllValues(initialPersona.consumption) || 0, - grey: sumForAndFormat(initialPersona.consumption, "grey"), - mixte: sumForAndFormat(initialPersona.consumption, "mixte"), - fossil: sumForAndFormat(initialPersona.consumption, "fossil"), - renewable: sumForAndFormat(initialPersona.consumption, "renewable"), - }, - { - name: t("graph.step.first.production.name"), - total: sumAllValues(initialPersona.production) || 0, - offshore: sumForAndFormat(initialPersona.production, "offshore"), - nuclear: sumForAndFormat(initialPersona.production, "nuclear"), - terrestrial: sumForAndFormat(initialPersona.production, "terrestrial"), - }, - ]; - - const stepsDetails = _.range(1, game.lastFinishedStep + 1).map( - (step: number) => { - const persona = personaBySteps[step]; - if (STEPS[step]?.type === "consumption") { - return { - name: t("graph.step.other.name", { stepNumber: step }), - total: sumAllValues(persona.consumption) || 0, - grey: sumForAndFormat(persona.consumption, "grey"), - mixte: sumForAndFormat(persona.consumption, "mixte"), - fossil: sumForAndFormat(persona.consumption, "fossil"), - renewable: sumForAndFormat(persona.consumption, "renewable"), - }; - } else { - return { - name: t("graph.step.other.name", { stepNumber: step }), - total: sumAllValues(persona.production) || 0, - offshore: sumForAndFormat(persona.production, "offshore"), - nuclear: sumForAndFormat(persona.production, "nuclear"), - terrestrial: sumForAndFormat(persona.production, "terrestrial"), - }; - } - } - ); - - return [...initialValues, ...stepsDetails]; - }, [game.lastFinishedStep, personaBySteps, getPersonaAtStep, t]); - - return ( - <> - { - if (chartState?.activeTooltipIndex != null) { - setSelectedBarIdx(chartState.activeTooltipIndex); - } - }} - /> - { - - } - - ); -} - -function ConsumptionAndProductionDetailsGraph({ - barIdx, - bar, -}: { - barIdx: number | undefined; - bar?: { name: string }; -}) { - const { t } = useTranslation(); - const { game } = usePlay(); - const { getPersonaAtStep } = usePersona(); - - const graphTitle = useMemo(() => { - if (bar) { - return t( - "page.player.statistics.tabs.energy-balance.graphs.details.title", - { stackName: bar.name } - ); - } - return ""; - }, [bar, t]); - - if (typeof barIdx === "undefined") return <>; - - const step = Math.max(barIdx - 1, 0); - if (isNotFinishedStep(step, game)) return <>; - - const persona = getPersonaAtStep(step); - - return ( - <> - {step === 0 && barIdx === 0 ? ( - <> - - - - ) : step === 0 && barIdx === 1 ? ( - <> - - - - ) : STEPS[step]?.type === "consumption" ? ( - <> - - - - ) : ( - <> - - - - )} - - ); -} - function MaterialsGraphTab() { const { currentPersona, getPersonaAtStep } = usePersona(); diff --git a/packages/client/src/modules/play/constants/steps.ts b/packages/client/src/modules/play/constants/steps.ts index b555e27e..40231d72 100644 --- a/packages/client/src/modules/play/constants/steps.ts +++ b/packages/client/src/modules/play/constants/steps.ts @@ -6,6 +6,7 @@ export { STEPS, getStepId, getStepIndexById, + getStepTypes, isStepOfType, }; export type { GameStepType, GameStepId, GameStep }; @@ -83,3 +84,9 @@ function getStepIndexById(id: GameStepId): number { function isStepOfType(step: number, type: GameStepType) { return step === 0 || step === STEPS.length - 1 || STEPS[step]?.type === type; } + +function getStepTypes(step: number): GameStepType[] { + return (["consumption", "production"] as const).filter((type) => + isStepOfType(step, type) + ); +} diff --git a/packages/client/src/modules/play/context/playContext.tsx b/packages/client/src/modules/play/context/playContext.tsx index 60933a11..ad779a8b 100644 --- a/packages/client/src/modules/play/context/playContext.tsx +++ b/packages/client/src/modules/play/context/playContext.tsx @@ -27,7 +27,7 @@ export { usePersonaByUserId, }; -export type { TeamIdToValues, TeamValues }; +export type { PersonaUsed, TeamIdToValues, TeamValues }; interface TeamIdToValues { [k: string]: TeamValues; @@ -390,10 +390,10 @@ function usePlay() { return React.useContext(PlayContext); } -function usePersonaByUserId(userIds: number): ReturnType; -function usePersonaByUserId( - userIds: number[] -): Record>; +type PersonaUsed = ReturnType; + +function usePersonaByUserId(userIds: number): PersonaUsed; +function usePersonaByUserId(userIds: number[]): Record; function usePersonaByUserId(userIds: number | number[]) { const { consumptionActionById, game, players, productionActionById, teams } = useLoadedPlay(); diff --git a/packages/client/src/modules/play/context/usePlayStore.ts b/packages/client/src/modules/play/context/usePlayStore.ts index 8566e62a..ae165440 100644 --- a/packages/client/src/modules/play/context/usePlayStore.ts +++ b/packages/client/src/modules/play/context/usePlayStore.ts @@ -9,7 +9,7 @@ import { } from "../../../utils/types"; import { LARGE_GAME_TEAMS, STEPS } from "../constants"; import { indexArrayBy } from "../../../lib/array"; -import { PRODUCTION, ProductionDatum } from "../../persona/production"; +import { ProductionDatum } from "../../persona/production"; import { computeEnergyProduction } from "../utils/production"; export { usePlayStore }; @@ -49,11 +49,12 @@ function usePlayStore() { ); const productionOfCountryToday: ProductionDatum[] = useMemo(() => { - const productionByName = indexArrayBy(PRODUCTION, "name"); - return productionActions.map((productionAction) => { return { - ...(productionByName[productionAction.name] || {}), + name: productionAction.name, + type: productionAction.type, + carbonType: productionAction.carbonType, + revealOnStep: productionAction.revealOnStep, value: computeEnergyProduction( productionAction, productionAction.defaultTeamValue diff --git a/packages/client/src/modules/play/utils/persona.ts b/packages/client/src/modules/play/utils/persona.ts index eff65621..c997014a 100644 --- a/packages/client/src/modules/play/utils/persona.ts +++ b/packages/client/src/modules/play/utils/persona.ts @@ -168,16 +168,30 @@ function computeResultsByStep( basePersona ); + const newProductionDisplayed = newProduction.filter( + (p) => (p.revealOnStep || 0) <= step + ); + const newMaterials = computeMaterials( newProduction, teamActions, productionActionById ); + const newMaterialsDisplayed = computeMaterials( + newProductionDisplayed, + teamActions, + productionActionById + ); const newMetals = computeMetals( newProduction, teamActions, productionActionById ); + const newMetalsDisplayed = computeMetals( + newProductionDisplayed, + teamActions, + productionActionById + ); const { actionPointsUsedAtCurrentStep } = computePlayerActionsStats( step, @@ -199,7 +213,10 @@ function computeResultsByStep( actionPoints: actionPointsUsedAtCurrentStep, consumption: newConsumption, production: newProduction, + productionDisplayed: newProductionDisplayed, materials: newMaterials, + materialsDisplayed: newMaterialsDisplayed, metals: newMetals, + metalsDisplayed: newMetalsDisplayed, }; } diff --git a/packages/client/src/modules/play/utils/production.ts b/packages/client/src/modules/play/utils/production.ts index 615ac474..be6d38c5 100644 --- a/packages/client/src/modules/play/utils/production.ts +++ b/packages/client/src/modules/play/utils/production.ts @@ -1,8 +1,7 @@ -import { indexArrayBy } from "../../../lib/array"; import { fromEntries } from "../../../lib/object"; import { ProductionAction, TeamAction } from "../../../utils/types"; import { Persona } from "../../persona/persona"; -import { PRODUCTION, ProductionDatum } from "../../persona/production"; +import { ProductionDatum } from "../../persona/production"; export { computeNewProductionData, @@ -75,15 +74,16 @@ function computeNewProductionData( productionActionById: Record, persona: Persona ) { - const productionByName = indexArrayBy(PRODUCTION, "name"); const productionNameToNewProduction = fromEntries( performedTeamActions .map((teamAction) => { const productionAction = productionActionById[teamAction.actionId]; - const baseProduction = productionByName[productionAction.name]; return { - ...baseProduction, + name: productionAction.name, + type: productionAction.type, + carbonType: productionAction.carbonType, + revealOnStep: productionAction.revealOnStep, value: computeEnergyProduction(productionAction, teamAction.value), }; }) diff --git a/packages/client/src/modules/translations/resources/en/common.json b/packages/client/src/modules/translations/resources/en/common.json index 6ce971fc..df74b1a8 100644 --- a/packages/client/src/modules/translations/resources/en/common.json +++ b/packages/client/src/modules/translations/resources/en/common.json @@ -266,6 +266,10 @@ "graph.energy.terrestrial": "Onshore production", "graph.energy.nuclear": "Nuclear energy", + "graph.energy-balance-for-player-graph.title": "Evolution of energy balance", + + "graph.energy-balance-for-team-graph.title": "Energy balance of the team", + "graph.materials.steel": "Steel", "graph.materials.cement": "Cement", "graph.materials.glass": "Glass", diff --git a/packages/client/src/modules/translations/resources/fr/common.json b/packages/client/src/modules/translations/resources/fr/common.json index 1ffaaed0..c24a64f2 100644 --- a/packages/client/src/modules/translations/resources/fr/common.json +++ b/packages/client/src/modules/translations/resources/fr/common.json @@ -266,6 +266,10 @@ "graph.energy.terrestrial": "Production terrestre", "graph.energy.nuclear": "Nucléaire", + "graph.energy-balance-for-player-graph.title": "Évolution de l'équilibre énergétique", + + "graph.energy-balance-for-team-graph.title": "Équilibre énergétique de l'équipe", + "graph.materials.steel": "Acier", "graph.materials.cement": "Ciment", "graph.materials.glass": "Verre", diff --git a/packages/client/src/utils/theme.ts b/packages/client/src/utils/theme.ts index cc3a976d..809f24dc 100644 --- a/packages/client/src/utils/theme.ts +++ b/packages/client/src/utils/theme.ts @@ -145,6 +145,9 @@ const globalStyles: GlobalStylesProps["styles"] = { html: { overscrollBehavior: "none", }, + ".clickable": { + cursor: "pointer", + }, ".text-em": { color: `${theme.palette.secondary.main} !important`, }, diff --git a/packages/client/src/utils/types.ts b/packages/client/src/utils/types.ts index 7490b504..c6c8ffe5 100644 --- a/packages/client/src/utils/types.ts +++ b/packages/client/src/utils/types.ts @@ -180,6 +180,7 @@ interface ProductionActionCommon { type: ProductionActionType; order: number; step: number; + revealOnStep?: number; helpCardLink: string; min: number; max: number; @@ -191,6 +192,7 @@ interface ProductionActionCommon { pointsIntervals: PointsInterval[]; defaultTeamValue: number; isPlayable: boolean; + carbonType: ProductionCarbonType; } interface ProductionActionArea extends ProductionActionCommon { diff --git a/packages/server/prisma/migrations/20230815174943_add_production_action_carbon_type/migration.sql b/packages/server/prisma/migrations/20230815174943_add_production_action_carbon_type/migration.sql new file mode 100644 index 00000000..097752c7 --- /dev/null +++ b/packages/server/prisma/migrations/20230815174943_add_production_action_carbon_type/migration.sql @@ -0,0 +1,5 @@ +-- CreateEnum +CREATE TYPE "ProductionActionCarbonType" AS ENUM ('carbonated', 'decarbonated'); + +-- AlterTable +ALTER TABLE "ProductionAction" ADD COLUMN "carbonType" "ProductionActionCarbonType" NOT NULL DEFAULT E'decarbonated'; diff --git a/packages/server/prisma/migrations/20230815175025_add_production_action_reveal_on_step/migration.sql b/packages/server/prisma/migrations/20230815175025_add_production_action_reveal_on_step/migration.sql new file mode 100644 index 00000000..d943c287 --- /dev/null +++ b/packages/server/prisma/migrations/20230815175025_add_production_action_reveal_on_step/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ProductionAction" ADD COLUMN "revealOnStep" INTEGER; diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 4cb5b606..80872f8e 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -188,11 +188,12 @@ model PlayerActions { } model ProductionAction { - id Int @id @default(autoincrement()) - name ProductionActionName @unique + id Int @id @default(autoincrement()) + name ProductionActionName @unique type ProductionActionType order Int step Int + revealOnStep Int? helpCardLink String unit ProductionActionUnit min Float @@ -206,7 +207,8 @@ model ProductionAction { pointsIntervals PointsInterval[] teamActions TeamActions[] defaultTeamValue Float - isPlayable Boolean @default(false) + isPlayable Boolean @default(false) + carbonType ProductionActionCarbonType @default(decarbonated) } enum ProductionActionName { @@ -234,6 +236,11 @@ enum ProductionActionUnit { percentage } +enum ProductionActionCarbonType { + carbonated + decarbonated +} + model PointsInterval { id Int @id @default(autoincrement()) min Float diff --git a/packages/server/src/database/seeds/productionActionsSeed.ts b/packages/server/src/database/seeds/productionActionsSeed.ts index 2bc26edf..f29f0136 100644 --- a/packages/server/src/database/seeds/productionActionsSeed.ts +++ b/packages/server/src/database/seeds/productionActionsSeed.ts @@ -96,6 +96,7 @@ function getProductionActionsDataForProductionStep1(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.ON_SHORE_WIND_TURBINE, @@ -126,6 +127,7 @@ function getProductionActionsDataForProductionStep1(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.PHOTOVOLTAIC_FARM, @@ -156,6 +158,7 @@ function getProductionActionsDataForProductionStep1(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.PHOTOVOLTAIC_ROOF, @@ -186,6 +189,7 @@ function getProductionActionsDataForProductionStep1(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.THERMAL_SOLAR, @@ -216,6 +220,7 @@ function getProductionActionsDataForProductionStep1(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, ] as ProductionActionWithPointInterval[]; @@ -275,6 +280,7 @@ function getProductionActionsDataForProductionStep2(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.OFF_SHORE_WIND_TURBINE, @@ -305,6 +311,7 @@ function getProductionActionsDataForProductionStep2(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.TIDAL, @@ -335,6 +342,7 @@ function getProductionActionsDataForProductionStep2(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, { name: productionActionNames.WAVE, @@ -365,6 +373,7 @@ function getProductionActionsDataForProductionStep2(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, ] as ProductionActionWithPointInterval[]; @@ -394,6 +403,7 @@ function getProductionActionsDataForProductionStep3(): { powerNeededKWh: 3.82, lcoe: 0.095, order: 1, + revealOnStep: getStepIndexById("production-3"), helpCardLink: "https://drive.google.com/file/d/1g2lYTDT0x0kdcqlohWzfMK_VVItQQtCO/view?usp=share_link", currentYearPowerNeedGw: 61.4, @@ -411,6 +421,7 @@ function getProductionActionsDataForProductionStep3(): { }, ], isPlayable: true, + carbonType: "decarbonated", }, ] as ProductionActionWithPointInterval[]; diff --git a/packages/server/src/modules/players/services/index.ts b/packages/server/src/modules/players/services/index.ts index 8488975d..234cc3a2 100644 --- a/packages/server/src/modules/players/services/index.ts +++ b/packages/server/src/modules/players/services/index.ts @@ -1,11 +1,10 @@ -import { Personalization, Players as PlayersPrisma } from "@prisma/client"; +import { Personalization, Players } from "@prisma/client"; import { database } from "../../../database"; -import { Players } from "../types"; import * as playerActionsServices from "../../actions/services/playerActions"; import { batchItems } from "../../../lib/array"; const model = database.players; -type Model = PlayersPrisma; +type Model = Players; export { services }; @@ -15,7 +14,6 @@ const crudServices = { remove, removeMany, setDefaultProfiles, - update, updateMany, validateProfiles, }; @@ -30,31 +28,6 @@ async function getMany( }); } -async function update( - gameId: number, - userId: number, - document: Partial> -): Promise { - // TODO: find a way to link Prisma typing with attributes included in `include` section. - return model.update({ - where: { - userId_gameId: { - gameId, - userId, - }, - }, - data: document, - include: { - actions: { - include: { - action: true, - }, - }, - team: true, - }, - }) as unknown as Players; -} - async function setDefaultProfiles( gameId: number, defaultPersonalization: Personalization @@ -118,7 +91,7 @@ async function validateProfiles(gameId: number): Promise { }, }); - return playersToValidate.forEach(async (player: PlayersPrisma) => { + return playersToValidate.forEach(async (player: Players) => { if (player.profileId) { await database.profile.update({ where: { diff --git a/packages/server/src/modules/players/types/index.ts b/packages/server/src/modules/players/types/index.ts deleted file mode 100644 index 3d8354ec..00000000 --- a/packages/server/src/modules/players/types/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Team, PlayerActions, Profile } from "@prisma/client"; - -export type { Players }; - -interface Players { - gameId: number; - teamId: number; - userId: number; - hasFinishedStep: boolean; - profileId: number | null; - team: Team; - profile: Profile; - actions: PlayerActions; -} diff --git a/packages/server/src/modules/productionActions/types/index.ts b/packages/server/src/modules/productionActions/types/index.ts index 0e5b9fc4..5a7236c5 100644 --- a/packages/server/src/modules/productionActions/types/index.ts +++ b/packages/server/src/modules/productionActions/types/index.ts @@ -21,6 +21,7 @@ interface ProductionActionCommon { type: ProductionActionType; order: number; step: number; + revealOnStep?: number; helpCardLink: string; min: number; max: number; @@ -31,6 +32,7 @@ interface ProductionActionCommon { currentYearPowerNeedGw: number; defaultTeamValue: number; isPlayable: boolean; + carbonType: "carbonated" | "decarbonated"; } interface PointsInterval {