From 96c4e38140efc2ce6c2e12086bf552a940e2c0db Mon Sep 17 00:00:00 2001 From: David Date: Mon, 25 May 2026 12:06:13 -0700 Subject: [PATCH 1/3] Work so far to migrate to commander svc --- .../commanderOverview/CommanderDetails.tsx | 20 +++-- .../CommanderMatchupsTable.tsx | 12 ++- .../commanderOverview/CommanderOverview.tsx | 8 +- .../commanderMatchupsColumnHelper.tsx | 14 +++- .../commanderOverviewColumnHelper.tsx | 14 +++- .../matchHistoryColumnHelper.tsx | 14 +++- src/components/matchHistory/MatchDetails.tsx | 6 +- src/components/matchHistory/MatchHistory.tsx | 8 +- .../matchHistory/MatchPlayerImage.tsx | 27 ++++--- .../matchHistory/MatchSubmission.tsx | 14 ++-- .../playerOverview/CommanderHistoryTable.tsx | 8 +- .../playerOverview/CommanderMatchupsTable.tsx | 12 ++- .../playerDetails/PlayerDecks.tsx | 6 +- .../playerDetails/PlayerDetails.tsx | 6 +- .../playerDetails/PlayerDetailsInfoCard.tsx | 8 +- .../settings/FavoriteCommanderSection.tsx | 21 +++-- .../settings/FavoriteDecksSection.tsx | 9 +-- src/logic/dictionaryUtils.ts | 38 +++++---- src/navigation/Root.tsx | 5 ++ src/redux/commanders/commandersActions.ts | 19 +++++ src/redux/commanders/commandersReducer.ts | 41 ++++++++++ src/redux/commanders/commandersSelectors.ts | 46 +++++++++++ src/redux/rootReducer.ts | 5 +- src/services/CommanderService.ts | 79 +++++++++++++++++++ 24 files changed, 348 insertions(+), 92 deletions(-) create mode 100644 src/redux/commanders/commandersActions.ts create mode 100644 src/redux/commanders/commandersReducer.ts create mode 100644 src/redux/commanders/commandersSelectors.ts create mode 100644 src/services/CommanderService.ts diff --git a/src/components/commanderOverview/CommanderDetails.tsx b/src/components/commanderOverview/CommanderDetails.tsx index e943005..fc63b6e 100644 --- a/src/components/commanderOverview/CommanderDetails.tsx +++ b/src/components/commanderOverview/CommanderDetails.tsx @@ -1,5 +1,3 @@ -// CommanderDetails.tsx - import { TooltipItem } from "chart.js"; import React, { useCallback, useState } from "react"; import { useSelector } from "react-redux"; @@ -25,10 +23,10 @@ import { import { AppState } from "../../redux/rootReducer"; import { StatsSelectors } from "../../redux/stats/statsSelectors"; +import { CommandersSelectors } from "../../redux/commanders/commandersSelectors"; import { Loading } from "../Loading"; -import { commanderList } from "../../services/commanderList"; import { SortableTable } from "../dataVisualizations/SortableTable"; -import { matchHistoryColumns } from "../dataVisualizations/columnHelpers/matchHistoryColumnHelper"; +import { getMatchHistoryColumns } from "../dataVisualizations/columnHelpers/matchHistoryColumnHelper"; import { Match } from "../../types/domain/Match"; import { MatchPlayer } from "../../types/domain/MatchPlayer"; import { LineGraph } from "../dataVisualizations/LineGraph"; @@ -50,6 +48,10 @@ export const CommanderDetails = React.memo(function CommanderDetails() { const navigate = useNavigate(); const commanderId = useLoaderData() as string; const commander = useSelector((state: AppState) => StatsSelectors.getCommander(state, commanderId)); + const commanderInfo = useSelector((state: AppState) => + CommandersSelectors.getCommanderByName(state, commander?.name ?? "") + ); + const commandersData = useSelector((state: AppState) => CommandersSelectors.getCommanders(state)); const [showCommanderMatchups, setShowCommanderMatchups] = useState(false); const [tabIndex, setTabIndex] = useState(0); @@ -129,6 +131,8 @@ export const CommanderDetails = React.memo(function CommanderDetails() { return `Winrate: ${item.formattedValue}%`; }; + const matchHistoryColumns = getMatchHistoryColumns(commandersData); + return ( - {commanderList[commander.name] ? ( + {commanderInfo ? ( {`Qualified: ${commander.validMatchesCount >= COMMANDER_MINIMUM_GAMES_REQUIRED ? "Yes" : "No"}`} - {commanderList[commander.name] && ( + {commanderInfo && ( View on Scryfall diff --git a/src/components/commanderOverview/CommanderMatchupsTable.tsx b/src/components/commanderOverview/CommanderMatchupsTable.tsx index b362f4a..ae37499 100644 --- a/src/components/commanderOverview/CommanderMatchupsTable.tsx +++ b/src/components/commanderOverview/CommanderMatchupsTable.tsx @@ -5,11 +5,11 @@ import { useNavigate } from "react-router-dom"; import { SortableTable } from "../dataVisualizations/SortableTable"; import { AppState } from "../../redux/rootReducer"; import { StatsSelectors } from "../../redux/stats/statsSelectors"; +import { CommandersSelectors } from "../../redux/commanders/commandersSelectors"; import { CommanderMatchupItem, - commanderMatchupsColumns + getCommanderMatchupsColumns } from "../dataVisualizations/columnHelpers/commanderMatchupsColumnHelper"; -import { commanderList } from "../../services/commanderList"; import { filterMatchesByPlayerCount } from "../../logic/dictionaryUtils"; import { NUMBER_OF_PLAYERS_FOR_VALID_MATCH } from "../constants"; @@ -20,6 +20,8 @@ export const CommanderMatchupsTable = React.memo(function CommanderMatchupsTable }) { const navigate = useNavigate(); + const commandersData = useSelector((state: AppState) => CommandersSelectors.getCommanders(state)); + // get all the valid matches the commander has participated in const matches = filterMatchesByPlayerCount( useSelector((state: AppState) => StatsSelectors.getMatchesByCommanderName(state, commanderName)), @@ -45,11 +47,12 @@ export const CommanderMatchupsTable = React.memo(function CommanderMatchupsTable // skip that since we don't want their record against themself if (commander !== commanderName) { const potentialCommander = commanderMatchups[commander]; + const commanderDataForThisCommander = commandersData ? commandersData[commander] : undefined; // check to see if the commander already exists in our dictionary. if it doesn't, add it. const commanderMatchup = potentialCommander === undefined ? { - id: commanderList[commander].id, + id: commanderDataForThisCommander ? commanderDataForThisCommander.scryfallId : "", name: commander, matchCount: 1, winCount: 0 @@ -68,10 +71,11 @@ export const CommanderMatchupsTable = React.memo(function CommanderMatchupsTable } const commanderMatchupsArray = Object.values(commanderMatchups).sort((a, b) => b.matchCount - a.matchCount); + const columns = getCommanderMatchupsColumns(commandersData); return ( { return { diff --git a/src/components/commanderOverview/CommanderOverview.tsx b/src/components/commanderOverview/CommanderOverview.tsx index 13a87e6..e0221ec 100644 --- a/src/components/commanderOverview/CommanderOverview.tsx +++ b/src/components/commanderOverview/CommanderOverview.tsx @@ -5,8 +5,9 @@ import { useNavigate } from "react-router-dom"; import { Box, Checkbox, Flex, Input, Tooltip } from "@chakra-ui/react"; import { SortableTable } from "../dataVisualizations/SortableTable"; -import { commanderOverviewColumns } from "../dataVisualizations/columnHelpers/commanderOverviewColumnHelper"; +import { getCommanderOverviewColumns } from "../dataVisualizations/columnHelpers/commanderOverviewColumnHelper"; import { StatsSelectors } from "../../redux/stats/statsSelectors"; +import { CommandersSelectors } from "../../redux/commanders/commandersSelectors"; import { Loading } from "../Loading"; import { Commander } from "../../types/domain/Commander"; import { COMMANDER_MINIMUM_GAMES_REQUIRED } from "../constants"; @@ -17,6 +18,7 @@ export const CommanderOverview = React.memo(function MatchHistory() { const navigate = useNavigate(); const allCommanders = useSelector(StatsSelectors.getCommanders); + const commandersData = useSelector((state: AppState) => CommandersSelectors.getCommanders(state)); const { showOnlyQualfied, searchInput, onShowOnlyQualifiedChange, onSearchChange } = useTableFilters(); const commanders: Commander[] = useSelector((state: AppState) => StatsSelectors.getCommandersByDate(state)); @@ -37,6 +39,8 @@ export const CommanderOverview = React.memo(function MatchHistory() { ); } + const columns = getCommanderOverviewColumns(commandersData); + return ( {commandersArray.length > 0 ? ( { return { diff --git a/src/components/dataVisualizations/columnHelpers/commanderMatchupsColumnHelper.tsx b/src/components/dataVisualizations/columnHelpers/commanderMatchupsColumnHelper.tsx index 8a129a7..edfe773 100644 --- a/src/components/dataVisualizations/columnHelpers/commanderMatchupsColumnHelper.tsx +++ b/src/components/dataVisualizations/columnHelpers/commanderMatchupsColumnHelper.tsx @@ -1,7 +1,7 @@ import { Box, Flex, Image } from "@chakra-ui/react"; import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { getWinRatePercentage } from "../../../logic/utils"; -import { commanderList } from "../../../services/commanderList"; +import { CommanderData } from "../../../services/CommanderService"; export type CommanderMatchupItem = { id: string; @@ -12,13 +12,16 @@ export type CommanderMatchupItem = { const columnHelper = createColumnHelper(); -export const commanderMatchupsColumns: ColumnDef[] = [ +export const getCommanderMatchupsColumns = ( + commandersMap: { [name: string]: CommanderData } | undefined +): ColumnDef[] => [ columnHelper.accessor((row) => row.name, { id: "name", cell: (info) => { const commander = info.getValue(); - const commanderImage = commanderList[commander] - ? commanderList[commander].image.replace("normal", "art_crop") + const commanderData = commandersMap ? commandersMap[commander] : undefined; + const commanderImage = commanderData + ? commanderData.image.replace("normal", "art_crop") : ""; return ( @@ -49,3 +52,6 @@ export const commanderMatchupsColumns: ColumnDef[] = header: () => Winrate Against }) ]; + +// Keep the old export for backward compatibility +export const commanderMatchupsColumns: ColumnDef[] = getCommanderMatchupsColumns(undefined); diff --git a/src/components/dataVisualizations/columnHelpers/commanderOverviewColumnHelper.tsx b/src/components/dataVisualizations/columnHelpers/commanderOverviewColumnHelper.tsx index c57fe96..1634387 100644 --- a/src/components/dataVisualizations/columnHelpers/commanderOverviewColumnHelper.tsx +++ b/src/components/dataVisualizations/columnHelpers/commanderOverviewColumnHelper.tsx @@ -1,18 +1,21 @@ import { Box, Flex, Image } from "@chakra-ui/react"; import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { getWinRatePercentage } from "../../../logic/utils"; -import { commanderList } from "../../../services/commanderList"; +import { CommanderData } from "../../../services/CommanderService"; import { Commander } from "../../../types/domain/Commander"; const columnHelper = createColumnHelper(); -export const commanderOverviewColumns: ColumnDef[] = [ +export const getCommanderOverviewColumns = ( + commandersMap: { [name: string]: CommanderData } | undefined +): ColumnDef[] => [ columnHelper.accessor((row) => row.name, { id: "name", cell: (info) => { const commander = info.getValue(); - const commanderImage = commanderList[commander] - ? commanderList[commander].image.replace("normal", "art_crop") + const commanderData = commandersMap ? commandersMap[commander] : undefined; + const commanderImage = commanderData + ? commanderData.image.replace("normal", "art_crop") : ""; return ( @@ -46,3 +49,6 @@ export const commanderOverviewColumns: ColumnDef[] = [ } ) ]; + +// Keep the old export for backward compatibility, but use undefined for now +export const commanderOverviewColumns: ColumnDef[] = getCommanderOverviewColumns(undefined); diff --git a/src/components/dataVisualizations/columnHelpers/matchHistoryColumnHelper.tsx b/src/components/dataVisualizations/columnHelpers/matchHistoryColumnHelper.tsx index 8aa2b26..c149e37 100644 --- a/src/components/dataVisualizations/columnHelpers/matchHistoryColumnHelper.tsx +++ b/src/components/dataVisualizations/columnHelpers/matchHistoryColumnHelper.tsx @@ -4,13 +4,15 @@ import { Flex, Image, Tag, TagLabel, TagRightIcon, Text } from "@chakra-ui/react import { ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { Match } from "../../../types/domain/Match"; -import { commanderList } from "../../../services/commanderList"; +import { CommanderData } from "../../../services/CommanderService"; import { primaryColor } from "../../../themes/acorn"; import { MatchTag, getMatchTags } from "../../../logic/matchTags"; const columnHelper = createColumnHelper(); -export const matchHistoryColumns: ColumnDef[] = [ +export const getMatchHistoryColumns = ( + commandersMap: { [name: string]: CommanderData } | undefined +): ColumnDef[] => [ columnHelper.accessor((row) => row.id, { id: "id", cell: (info) => info.getValue(), @@ -71,8 +73,9 @@ export const matchHistoryColumns: ColumnDef[] = [ // get the commander image for the winner const winner = match.players.find((player) => player.name === match.winner); const commander = winner ? winner.commanders[0] : ""; - const commanderImage = commanderList[commander] - ? commanderList[commander].image.replace("normal", "art_crop") + const commanderData = commandersMap ? commandersMap[commander] : undefined; + const commanderImage = commanderData + ? commanderData.image.replace("normal", "art_crop") : ""; return ( @@ -92,3 +95,6 @@ export const matchHistoryColumns: ColumnDef[] = [ header: () => Turn Count }) ]; + +// Keep the old export for backward compatibility +export const matchHistoryColumns: ColumnDef[] = getMatchHistoryColumns(undefined); diff --git a/src/components/matchHistory/MatchDetails.tsx b/src/components/matchHistory/MatchDetails.tsx index a4b98e3..7119ce2 100644 --- a/src/components/matchHistory/MatchDetails.tsx +++ b/src/components/matchHistory/MatchDetails.tsx @@ -5,8 +5,8 @@ import { Flex, Heading, Tag, TagLabel, TagRightIcon, Text } from "@chakra-ui/rea import { AppState } from "../../redux/rootReducer"; import { useSelector } from "react-redux"; import { StatsSelectors } from "../../redux/stats/statsSelectors"; +import { CommandersSelectors } from "../../redux/commanders/commandersSelectors"; import { Loading } from "../Loading"; -import { commanderList } from "../../services/commanderList"; import { MatchDisplayPlayer } from "./types/MatchDisplayPlayer"; import { MatchPlayerCard } from "./MatchPlayerCard"; import { rankDictionary } from "../constants"; @@ -24,6 +24,7 @@ export const MatchDetails = React.memo(function MatchDetails() { // look up this matchId in the matchHistory const match = useSelector((state: AppState) => StatsSelectors.getMatch(state, matchId)); + const commandersData = useSelector((state: AppState) => CommandersSelectors.getCommanders(state)); if (match === undefined) { return ; @@ -36,9 +37,10 @@ export const MatchDetails = React.memo(function MatchDetails() { name: player.name, rank: rankDictionary[player.rank], commanders: player.commanders.map((commanderName: string) => { + const commanderData = commandersData ? commandersData[commanderName] : undefined; return { name: commanderName, - id: commanderList[commanderName] ? commanderList[commanderName].id : undefined + id: commanderData ? commanderData.scryfallId : undefined }; }), isWinner: match.winner === player.name diff --git a/src/components/matchHistory/MatchHistory.tsx b/src/components/matchHistory/MatchHistory.tsx index 49e52ee..cc990a3 100644 --- a/src/components/matchHistory/MatchHistory.tsx +++ b/src/components/matchHistory/MatchHistory.tsx @@ -1,16 +1,19 @@ import { Flex } from "@chakra-ui/react"; import React from "react"; import { SortableTable } from "../dataVisualizations/SortableTable"; -import { matchHistoryColumns } from "../dataVisualizations/columnHelpers/matchHistoryColumnHelper"; +import { getMatchHistoryColumns } from "../dataVisualizations/columnHelpers/matchHistoryColumnHelper"; import { StatsSelectors } from "../../redux/stats/statsSelectors"; +import { CommandersSelectors } from "../../redux/commanders/commandersSelectors"; import { useSelector } from "react-redux"; import { Loading } from "../Loading"; import { useNavigate } from "react-router-dom"; import { Match } from "../../types/domain/Match"; +import { AppState } from "../../redux/rootReducer"; export const MatchHistory = React.memo(function MatchHistory() { const navigate = useNavigate(); let matches = useSelector(StatsSelectors.getMatches); + const commandersData = useSelector((state: AppState) => CommandersSelectors.getCommanders(state)); if (matches === undefined) { return ; @@ -18,11 +21,12 @@ export const MatchHistory = React.memo(function MatchHistory() { // Cannot directly mutate state, copy to new array first matches = matches.slice().sort((a: Match, b: Match) => Number(b.id) - Number(a.id)); + const columns = getMatchHistoryColumns(commandersData); return ( { return { diff --git a/src/components/matchHistory/MatchPlayerImage.tsx b/src/components/matchHistory/MatchPlayerImage.tsx index 2380fd7..7b5e162 100644 --- a/src/components/matchHistory/MatchPlayerImage.tsx +++ b/src/components/matchHistory/MatchPlayerImage.tsx @@ -12,8 +12,10 @@ import { useDisclosure } from "@chakra-ui/react"; import React, { useCallback } from "react"; +import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; -import { commanderList } from "../../services/commanderList"; +import { CommandersSelectors } from "../../redux/commanders/commandersSelectors"; +import { AppState } from "../../redux/rootReducer"; import { MatchDisplayPlayer } from "./types/MatchDisplayPlayer"; import { MatchDisplayCommander } from "./types/MatchDisplayCommander"; @@ -21,6 +23,7 @@ export const MatchPlayerImage = React.memo(function MatchPlayerImage({ player }: const navigate = useNavigate(); const { isOpen, onOpen, onClose } = useDisclosure(); const finalRef = React.useRef(null); + const commandersData = useSelector((state: AppState) => CommandersSelectors.getCommanders(state)); const soloCommanderNav = useCallback(() => { if (player.commanders[0].id !== undefined) { @@ -39,6 +42,8 @@ export const MatchPlayerImage = React.memo(function MatchPlayerImage({ player }: } }; + const commanderData = commandersData ? commandersData[value.name] : undefined; + return (