From 4c2915210ac1397d8a632af5a090103b605025cb Mon Sep 17 00:00:00 2001 From: luketd Date: Fri, 14 Mar 2025 19:02:53 -0400 Subject: [PATCH 1/3] Init --- components/tables/PlayerAwardsTable.tsx | 34 +++++++++++++++++ pages/[league]/player/[id].tsx | 50 +++++++++++++++++++++++-- typings/portalApi.d.ts | 21 +++++++++++ utils/query.ts | 9 +++++ 4 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 components/tables/PlayerAwardsTable.tsx create mode 100644 typings/portalApi.d.ts diff --git a/components/tables/PlayerAwardsTable.tsx b/components/tables/PlayerAwardsTable.tsx new file mode 100644 index 00000000..5ce30641 --- /dev/null +++ b/components/tables/PlayerAwardsTable.tsx @@ -0,0 +1,34 @@ +import { InternalPlayerAchievement } from 'typings/portalApi'; + +export const PlayerAwards = ({ + playerAwards, +}: { + playerAwards: InternalPlayerAchievement[]; +}) => { + const uniqueAwardsNames = playerAwards + .map((playerAward) => playerAward.achievementName) + .filter((value, index, self) => self.indexOf(value) === index); + + return ( +
+
Awards
+
+ {playerAwards.map((playerAward) => ( +
+
+ {playerAward.achievementName} +
+
TeamID {playerAward.teamID}
+ +
+ {playerAward.won ? 'Won' : 'Lost'} +
+
+ ))} +
+
+ ); +}; diff --git a/pages/[league]/player/[id].tsx b/pages/[league]/player/[id].tsx index bb68f250..f95d85fd 100644 --- a/pages/[league]/player/[id].tsx +++ b/pages/[league]/player/[id].tsx @@ -1,4 +1,6 @@ +import { ExternalLinkIcon } from '@chakra-ui/icons'; import { + Link, Spinner, Tab, TabList, @@ -8,11 +10,16 @@ import { } from '@chakra-ui/react'; import { dehydrate, QueryClient, useQuery } from '@tanstack/react-query'; import classnames from 'classnames'; +import { PlayerAwards } from 'components/tables/PlayerAwardsTable'; import { GetServerSideProps } from 'next'; import { useRouter } from 'next/router'; import { NextSeo } from 'next-seo'; import { useTheme } from 'next-themes'; import { useEffect, useRef } from 'react'; +import { + InternalIndexPlayerID, + InternalPlayerAchievement, +} from 'typings/portalApi'; import { Footer } from '../../../components/Footer'; import { Header } from '../../../components/Header'; @@ -31,7 +38,7 @@ import { PlayerWithAdvancedStats, } from '../../../typings/api'; import { League, leagueNameToId } from '../../../utils/leagueHelpers'; -import { query } from '../../../utils/query'; +import { portalQuery, query } from '../../../utils/query'; import { seasonTypeToApiFriendlyParam } from '../../../utils/seasonTypeHelpers'; import { GoalieRatings } from '../../api/v1/goalies/ratings/[id]'; import { SkaterRatings as PlayerRatings } from '../../api/v1/players/ratings/[id]'; @@ -49,6 +56,20 @@ const fetchPlayerName = (league: League, playerId: string) => )}&playerId=${playerId}`, ); +const fetchPlayerAwards = (league: League, playerId: string) => + portalQuery( + `api/v1/history/player?leagueID=${leagueNameToId( + league, + )}&fhmID=${playerId}`, + ); + +const fetchPortalID = (league: League, playerId: string) => + portalQuery( + `api/v1/player/index-ids?leagueID=${leagueNameToId( + league, + )}&indexID=${playerId}`, + ); + export default ({ playerId, league }: { playerId: string; league: League }) => { const router = useRouter(); @@ -94,6 +115,16 @@ export default ({ playerId, league }: { playerId: string; league: League }) => { enabled: !!playerTypeInfo, }); + const { data: playerAwards } = useQuery({ + queryKey: ['playerAwards', league, playerId], + queryFn: () => fetchPlayerAwards(league, playerId), + }); + + const { data: playerPortalID } = useQuery({ + queryKey: ['playerPortalID', league, playerId], + queryFn: () => fetchPortalID(league, playerId), + }); + const { data: playerRatings } = useQuery({ queryKey: ['playerRatings', league, playerId, playerTypeInfo?.playerType], queryFn: () => { @@ -105,7 +136,7 @@ export default ({ playerId, league }: { playerId: string; league: League }) => { )}`, ); }, - enabled: !!playerTypeInfo?.playerType , + enabled: !!playerTypeInfo?.playerType, }); const { data: playerStats } = useQuery({ @@ -184,9 +215,19 @@ export default ({ playerId, league }: { playerId: string; league: League }) => { teamAbbreviation={playerInfo[0]?.team} className="mt-10 size-40 md:mt-2.5" /> -
+
{playerNameInfo?.name ?? 'Player'}
+
+ {playerPortalID && playerPortalID.length > 0 && ( + + View in portal + + )} +
{'position' in playerInfo[0] ? playerInfo[0].position : 'G'} |{' '} {Math.floor(playerInfo[0].height / 12)} ft{' '} @@ -282,6 +323,9 @@ export default ({ playerId, league }: { playerId: string; league: League }) => { + {playerAwards && playerAwards.length > 0 && ( + + )}
)}
diff --git a/typings/portalApi.d.ts b/typings/portalApi.d.ts new file mode 100644 index 00000000..18d0be54 --- /dev/null +++ b/typings/portalApi.d.ts @@ -0,0 +1,21 @@ +export type InternalPlayerAchievement = { + playerUpdateID: number | null; + playerName: string; + userID: number | null; + fhmID: number; + leagueID: number; + seasonID: number; + teamID: number; + achievement: number; + achievementName: string; + achievementDescription: string; + isAward: boolean; + won: boolean; +}; + +export type InternalIndexPlayerID = { + playerUpdateID: number; + leagueID: number; + indexID: number; + startSeason: number; +}; diff --git a/utils/query.ts b/utils/query.ts index f7b6f978..00dfa7f2 100644 --- a/utils/query.ts +++ b/utils/query.ts @@ -8,3 +8,12 @@ export const query = async (uri: string) => { } return response.json(); }; + +export const portalQuery = async (uri: string) => { + const response = await fetch(`https://portal.simulationhockey.com/${uri}`); + + if (!response.ok) { + throw new Error('Network request failed'); + } + return response.json(); +}; From e0370e4b797010da20f46d17bc7df99603933544 Mon Sep 17 00:00:00 2001 From: luketd Date: Mon, 17 Mar 2025 20:10:39 -0400 Subject: [PATCH 2/3] turned it into a table. need to solve some issues - TeamID displays as TEamID instead of name or logo - In the SMJHL/IIHF/WJC there are moments where 2 users occupy the same ID. DB cleanup or more code massaging. DB cleanup would be preffered --- components/tables/PlayerAwardsTable.tsx | 78 ++++++++++++++++------- components/tables/tableBehavioralFlags.ts | 9 +++ pages/[league]/player/[id].tsx | 3 + 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/components/tables/PlayerAwardsTable.tsx b/components/tables/PlayerAwardsTable.tsx index 5ce30641..a3878cf6 100644 --- a/components/tables/PlayerAwardsTable.tsx +++ b/components/tables/PlayerAwardsTable.tsx @@ -1,34 +1,64 @@ -import { InternalPlayerAchievement } from 'typings/portalApi'; +import { + createColumnHelper, + getCoreRowModel, + getSortedRowModel, + useReactTable, +} from '@tanstack/react-table'; +import { useMemo } from 'react'; + +import { InternalPlayerAchievement } from '../../typings/portalApi'; + +import { Table } from './Table'; +import { AWARD_TABLE_FLAGS } from './tableBehavioralFlags'; +import { TableHeader } from './TableHeader'; + +const columnHelper = createColumnHelper(); export const PlayerAwards = ({ playerAwards, }: { playerAwards: InternalPlayerAchievement[]; }) => { - const uniqueAwardsNames = playerAwards - .map((playerAward) => playerAward.achievementName) - .filter((value, index, self) => self.indexOf(value) === index); + const columns = useMemo( + () => [ + columnHelper.accessor('seasonID', { + header: () => Season, + enableGlobalFilter: true, + }), + columnHelper.accessor( + (row) => { + const result = row.isAward ? (row.won ? 'Won' : 'Nom') : ''; + return `${row.achievementName}${result ? ` - ${result}` : ''}`; + }, + { + id: 'awardResult', + header: () => Award, + enableGlobalFilter: true, + }, + ), + columnHelper.accessor('teamID', { + header: () => Team, //Figure out way to put team Abbr or logo here + enableGlobalFilter: true, + }), + ], + [], + ); - return ( -
-
Awards
-
- {playerAwards.map((playerAward) => ( -
-
- {playerAward.achievementName} -
-
TeamID {playerAward.teamID}
+ const table = useReactTable({ + columns, + data: playerAwards, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + initialState: { + sorting: [{ id: 'seasonID', desc: true }], + }, + }); -
- {playerAward.won ? 'Won' : 'Lost'} -
-
- ))} -
-
+ return ( + + table={table} + tableBehavioralFlags={AWARD_TABLE_FLAGS()} + label="player_awards" + /> ); }; diff --git a/components/tables/tableBehavioralFlags.ts b/components/tables/tableBehavioralFlags.ts index f984fbf2..6ab1d4c6 100644 --- a/components/tables/tableBehavioralFlags.ts +++ b/components/tables/tableBehavioralFlags.ts @@ -33,6 +33,15 @@ export const SKATER_TABLE_FLAGS = ({ showTableFilterOptions: playerType !== 'goalie', }); +export const AWARD_TABLE_FLAGS = (): TableBehavioralFlags => ({ + stickyFirstColumn: false, + showTableFooter: false, + showCSVExportButton: false, + enablePagination: false, + enableFiltering: false, + showTableFilterOptions: false, +}); + export interface TableBehavioralFlags { stickyFirstColumn: boolean; showTableFooter: boolean; diff --git a/pages/[league]/player/[id].tsx b/pages/[league]/player/[id].tsx index f95d85fd..d3a41a4f 100644 --- a/pages/[league]/player/[id].tsx +++ b/pages/[league]/player/[id].tsx @@ -223,6 +223,9 @@ export default ({ playerId, league }: { playerId: string; league: League }) => { View in portal From c7a1ca6530f02c108b550e62c5108f2372352f75 Mon Sep 17 00:00:00 2001 From: luketd Date: Wed, 16 Apr 2025 22:21:16 -0400 Subject: [PATCH 3/3] Update [id].tsx --- pages/[league]/player/[id].tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/[league]/player/[id].tsx b/pages/[league]/player/[id].tsx index d3a41a4f..9743baf7 100644 --- a/pages/[league]/player/[id].tsx +++ b/pages/[league]/player/[id].tsx @@ -223,6 +223,7 @@ export default ({ playerId, league }: { playerId: string; league: League }) => {