fansunited-data-layer 0.13.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api/fansunited/constants.js +5 -0
- package/api/fansunited/constants.js.map +1 -0
- package/api/fansunited/football/competition/index.js +21 -0
- package/api/fansunited/football/competition/index.js.map +1 -0
- package/api/fansunited/football/competition/transformer.js +41 -0
- package/api/fansunited/football/competition/transformer.js.map +1 -0
- package/api/fansunited/football/competitions/index.js +56 -0
- package/api/fansunited/football/competitions/index.js.map +1 -0
- package/api/fansunited/football/competitions/transformer.js +68 -0
- package/api/fansunited/football/competitions/transformer.js.map +1 -0
- package/api/fansunited/football/http.js +7 -0
- package/api/fansunited/football/http.js.map +1 -0
- package/api/fansunited/football/matches/index.js +132 -0
- package/api/fansunited/football/matches/index.js.map +1 -0
- package/api/fansunited/football/matches/transformer.js +148 -0
- package/api/fansunited/football/matches/transformer.js.map +1 -0
- package/api/fansunited/football/players/index.js +60 -0
- package/api/fansunited/football/players/index.js.map +1 -0
- package/api/fansunited/football/players/transformer.js +85 -0
- package/api/fansunited/football/players/transformer.js.map +1 -0
- package/api/fansunited/football/search/index.js +64 -0
- package/api/fansunited/football/search/index.js.map +1 -0
- package/api/fansunited/football/search/transformer.js +114 -0
- package/api/fansunited/football/search/transformer.js.map +1 -0
- package/api/fansunited/football/teams/index.js +66 -0
- package/api/fansunited/football/teams/index.js.map +1 -0
- package/api/fansunited/football/teams/transformer.js +53 -0
- package/api/fansunited/football/teams/transformer.js.map +1 -0
- package/api/fansunited/http.d.ts.map +1 -1
- package/api/fansunited/http.js +70 -0
- package/api/fansunited/http.js.map +1 -0
- package/api/fansunited/search/constants.js +5 -0
- package/api/fansunited/search/constants.js.map +1 -0
- package/api/fansunited/search/http.js +7 -0
- package/api/fansunited/search/http.js.map +1 -0
- package/api/fansunited/search/index.js +308 -0
- package/api/fansunited/search/index.js.map +1 -0
- package/api/fansunited/search/transformer.js +374 -0
- package/api/fansunited/search/transformer.js.map +1 -0
- package/api/fansunited-sdk/loyalty/matches.js +21 -0
- package/api/fansunited-sdk/loyalty/matches.js.map +1 -0
- package/api/fansunited-sdk/loyalty/template.js +52 -0
- package/api/fansunited-sdk/loyalty/template.js.map +1 -0
- package/api/fansunited-sdk/odds/matches.js +109 -0
- package/api/fansunited-sdk/odds/matches.js.map +1 -0
- package/api/sportal365-sports/basketball/games/index.js +32 -0
- package/api/sportal365-sports/basketball/games/index.js.map +1 -0
- package/api/sportal365-sports/basketball/games/transformers/game.js +137 -0
- package/api/sportal365-sports/basketball/games/transformers/game.js.map +1 -0
- package/api/sportal365-sports/basketball/http.js +7 -0
- package/api/sportal365-sports/basketball/http.js.map +1 -0
- package/api/sportal365-sports/constants.js +15 -0
- package/api/sportal365-sports/constants.js.map +1 -0
- package/api/sportal365-sports/football/competitions/index.js +52 -0
- package/api/sportal365-sports/football/competitions/index.js.map +1 -0
- package/api/sportal365-sports/football/competitions/utils.js +92 -0
- package/api/sportal365-sports/football/competitions/utils.js.map +1 -0
- package/api/sportal365-sports/football/http.js +7 -0
- package/api/sportal365-sports/football/http.js.map +1 -0
- package/api/sportal365-sports/football/matches/index.js +206 -0
- package/api/sportal365-sports/football/matches/index.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/commentary.js +27 -0
- package/api/sportal365-sports/football/matches/transformers/commentary.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/lineup.js +74 -0
- package/api/sportal365-sports/football/matches/transformers/lineup.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/match-event.js +60 -0
- package/api/sportal365-sports/football/matches/transformers/match-event.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/match.js +195 -0
- package/api/sportal365-sports/football/matches/transformers/match.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/odds.js +11 -0
- package/api/sportal365-sports/football/matches/transformers/odds.js.map +1 -0
- package/api/sportal365-sports/football/matches/transformers/statistics.js +41 -0
- package/api/sportal365-sports/football/matches/transformers/statistics.js.map +1 -0
- package/api/sportal365-sports/football/search/index.js +42 -0
- package/api/sportal365-sports/football/search/index.js.map +1 -0
- package/api/sportal365-sports/football/search/search.transformer.js +166 -0
- package/api/sportal365-sports/football/search/search.transformer.js.map +1 -0
- package/api/sportal365-sports/football/standings/index.js +28 -0
- package/api/sportal365-sports/football/standings/index.js.map +1 -0
- package/api/sportal365-sports/football/standings/standing.transformer.js +107 -0
- package/api/sportal365-sports/football/standings/standing.transformer.js.map +1 -0
- package/api/sportal365-sports/football/statistics/index.js +154 -0
- package/api/sportal365-sports/football/statistics/index.js.map +1 -0
- package/api/sportal365-sports/football/statistics/player-career.transformer.js +77 -0
- package/api/sportal365-sports/football/statistics/player-career.transformer.js.map +1 -0
- package/api/sportal365-sports/football/statistics/player-recent.transformer.js +54 -0
- package/api/sportal365-sports/football/statistics/player-recent.transformer.js.map +1 -0
- package/api/sportal365-sports/football/statistics/player-season.transformer.js +147 -0
- package/api/sportal365-sports/football/statistics/player-season.transformer.js.map +1 -0
- package/api/sportal365-sports/football/teams/index.js +73 -0
- package/api/sportal365-sports/football/teams/index.js.map +1 -0
- package/api/sportal365-sports/football/teams/utils.js +196 -0
- package/api/sportal365-sports/football/teams/utils.js.map +1 -0
- package/api/sportal365-sports/http.js +58 -0
- package/api/sportal365-sports/http.js.map +1 -0
- package/api/sportal365-sports/search/http.js +7 -0
- package/api/sportal365-sports/search/http.js.map +1 -0
- package/api/sportal365-sports/search/index.js +61 -0
- package/api/sportal365-sports/search/index.js.map +1 -0
- package/api/sportal365-sports/search/search.transformer.js +254 -0
- package/api/sportal365-sports/search/search.transformer.js.map +1 -0
- package/api/sportal365-sports/shared/odds.transformer.js +65 -0
- package/api/sportal365-sports/shared/odds.transformer.js.map +1 -0
- package/api/sportal365-sports/shared/providerRef.helper.js +31 -0
- package/api/sportal365-sports/shared/providerRef.helper.js.map +1 -0
- package/api/sportal365-sports/standings/http.js +7 -0
- package/api/sportal365-sports/standings/http.js.map +1 -0
- package/api/sportal365-sports/standings/index.js +29 -0
- package/api/sportal365-sports/standings/index.js.map +1 -0
- package/api/sportal365-sports/standings/standing.transformer.js +185 -0
- package/api/sportal365-sports/standings/standing.transformer.js.map +1 -0
- package/api/sportal365-sports/statistics/http.js +7 -0
- package/api/sportal365-sports/statistics/http.js.map +1 -0
- package/api/sportal365-sports/statistics/index.js +25 -0
- package/api/sportal365-sports/statistics/index.js.map +1 -0
- package/api/sportal365-sports/statistics/team-stats.transformer.js +16 -0
- package/api/sportal365-sports/statistics/team-stats.transformer.js.map +1 -0
- package/api/sportal365-sports/tennis/http.js +7 -0
- package/api/sportal365-sports/tennis/http.js.map +1 -0
- package/api/sportal365-sports/tennis/matches/index.js +34 -0
- package/api/sportal365-sports/tennis/matches/index.js.map +1 -0
- package/api/sportal365-sports/tennis/matches/transformers/match.js +193 -0
- package/api/sportal365-sports/tennis/matches/transformers/match.js.map +1 -0
- package/cache/cache-manager.js +136 -0
- package/cache/cache-manager.js.map +1 -0
- package/cache/memory-store.js +29 -0
- package/cache/memory-store.js.map +1 -0
- package/client.js +38 -12337
- package/client.js.map +1 -0
- package/config/index.js +20 -0
- package/config/index.js.map +1 -0
- package/fansunited-data-layer.js +146 -3553
- package/fansunited-data-layer.js.map +1 -0
- package/helpers/competition.helpers.js +11 -0
- package/helpers/competition.helpers.js.map +1 -0
- package/helpers/player.helpers.js +165 -0
- package/helpers/player.helpers.js.map +1 -0
- package/helpers/player.hooks.js +34 -0
- package/helpers/player.hooks.js.map +1 -0
- package/helpers/team.helpers.js +216 -0
- package/helpers/team.helpers.js.map +1 -0
- package/package.json +6 -8
- package/providers/competition/hooks/useCompetitionStats.js +10 -0
- package/providers/competition/hooks/useCompetitionStats.js.map +1 -0
- package/providers/competition/hooks/useGoalDistribution.js +23 -0
- package/providers/competition/hooks/useGoalDistribution.js.map +1 -0
- package/providers/competition/hooks/useMatchHelpers.js +85 -0
- package/providers/competition/hooks/useMatchHelpers.js.map +1 -0
- package/providers/competition/hooks/useStandingsCalculations.js +89 -0
- package/providers/competition/hooks/useStandingsCalculations.js.map +1 -0
- package/providers/competition/hooks/useStandingsHelpers.js +61 -0
- package/providers/competition/hooks/useStandingsHelpers.js.map +1 -0
- package/providers/competition/hooks/useTeamPosition.js +15 -0
- package/providers/competition/hooks/useTeamPosition.js.map +1 -0
- package/providers/competition/hooks/useTeamResultsTable.js +86 -0
- package/providers/competition/hooks/useTeamResultsTable.js.map +1 -0
- package/providers/competition/utils/competitionStats.js +109 -0
- package/providers/competition/utils/competitionStats.js.map +1 -0
- package/providers/competition/utils/goalDistribution.js +64 -0
- package/providers/competition/utils/goalDistribution.js.map +1 -0
- package/providers/competition/utils/standingsCalculations.js +152 -0
- package/providers/competition/utils/standingsCalculations.js.map +1 -0
- package/providers/competition.context.js +14 -0
- package/providers/competition.context.js.map +1 -0
- package/providers/competition.provider.js +187 -0
- package/providers/competition.provider.js.map +1 -0
- package/providers/fansunited-config.context.js +6 -0
- package/providers/fansunited-config.context.js.map +1 -0
- package/providers/fansunited-config.hooks.js +20 -0
- package/providers/fansunited-config.hooks.js.map +1 -0
- package/providers/fansunited-config.provider.js +9 -0
- package/providers/fansunited-config.provider.js.map +1 -0
- package/providers/fansunited-sdk.hook.js +50 -0
- package/providers/fansunited-sdk.hook.js.map +1 -0
- package/providers/leaderboard/hooks/useMatchHelpers.js +30 -0
- package/providers/leaderboard/hooks/useMatchHelpers.js.map +1 -0
- package/providers/leaderboard/hooks/useTemplateHelpers.js +39 -0
- package/providers/leaderboard/hooks/useTemplateHelpers.js.map +1 -0
- package/providers/leaderboard.context.js +14 -0
- package/providers/leaderboard.context.js.map +1 -0
- package/providers/leaderboard.provider.js +98 -0
- package/providers/leaderboard.provider.js.map +1 -0
- package/providers/match/hooks/useBatchMatchOdds.js +72 -0
- package/providers/match/hooks/useBatchMatchOdds.js.map +1 -0
- package/providers/match/hooks/useEventHelpers.js +46 -0
- package/providers/match/hooks/useEventHelpers.js.map +1 -0
- package/providers/match/hooks/useHeadToHeadHelpers.js +52 -0
- package/providers/match/hooks/useHeadToHeadHelpers.js.map +1 -0
- package/providers/match/hooks/useLineupHelpers.js +53 -0
- package/providers/match/hooks/useLineupHelpers.js.map +1 -0
- package/providers/match/hooks/useMatchStandingsCalculations.js +332 -0
- package/providers/match/hooks/useMatchStandingsCalculations.js.map +1 -0
- package/providers/match/hooks/useMatchStatus.js +37 -0
- package/providers/match/hooks/useMatchStatus.js.map +1 -0
- package/providers/match/hooks/useOddsHelpers.js +54 -0
- package/providers/match/hooks/useOddsHelpers.js.map +1 -0
- package/providers/match/hooks/useScoreHelpers.js +34 -0
- package/providers/match/hooks/useScoreHelpers.js.map +1 -0
- package/providers/match/hooks/useStandingsHelpers.js +53 -0
- package/providers/match/hooks/useStandingsHelpers.js.map +1 -0
- package/providers/match/hooks/useStatisticsHelpers.js +40 -0
- package/providers/match/hooks/useStatisticsHelpers.js.map +1 -0
- package/providers/match/hooks/useTeamHelpers.js +38 -0
- package/providers/match/hooks/useTeamHelpers.js.map +1 -0
- package/providers/match/hooks/useTeamStatsHelpers.js +141 -0
- package/providers/match/hooks/useTeamStatsHelpers.js.map +1 -0
- package/providers/match.context.js +14 -0
- package/providers/match.context.js.map +1 -0
- package/providers/match.provider.js +176 -0
- package/providers/match.provider.js.map +1 -0
- package/providers/team/hooks/useFormStats.js +116 -0
- package/providers/team/hooks/useFormStats.js.map +1 -0
- package/providers/team/hooks/useMatchHelpers.js +46 -0
- package/providers/team/hooks/useMatchHelpers.js.map +1 -0
- package/providers/team/hooks/useSquadHelpers.js +67 -0
- package/providers/team/hooks/useSquadHelpers.js.map +1 -0
- package/providers/team.context.js +14 -0
- package/providers/team.context.js.map +1 -0
- package/providers/team.provider.js +98 -0
- package/providers/team.provider.js.map +1 -0
- package/utilities/stats/core/helpers.js +105 -0
- package/utilities/stats/core/helpers.js.map +1 -0
- package/utilities/stats/core/types.js +5 -0
- package/utilities/stats/core/types.js.map +1 -0
- package/utilities/stats/match/headToHead.js +83 -0
- package/utilities/stats/match/headToHead.js.map +1 -0
- package/utilities/stats/match/homeVsAway.js +140 -0
- package/utilities/stats/match/homeVsAway.js.map +1 -0
- package/utilities/stats/match/overUnder.js +91 -0
- package/utilities/stats/match/overUnder.js.map +1 -0
- package/utilities/stats/match/result.js +21 -0
- package/utilities/stats/match/result.js.map +1 -0
- package/utilities/stats/team/commonOpponents.js +77 -0
- package/utilities/stats/team/commonOpponents.js.map +1 -0
- package/utilities/stats/team/goalStats.js +50 -0
- package/utilities/stats/team/goalStats.js.map +1 -0
- package/utilities/stats/team/streaks.js +183 -0
- package/utilities/stats/team/streaks.js.map +1 -0
- package/client.cjs +0 -8
- package/fansunited-data-layer.cjs +0 -1
- package/matches-BvHU-bP0.js +0 -1795
- package/matches-Ci0Ci_Mw.cjs +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fansunited-data-layer.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function getActiveSeason(competition) {
|
|
2
|
+
if (!competition.seasons || competition.seasons.length === 0) {
|
|
3
|
+
return void 0;
|
|
4
|
+
}
|
|
5
|
+
const activeSeason = competition.seasons.find((season) => season.active);
|
|
6
|
+
return activeSeason ?? competition.seasons[0];
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
getActiveSeason
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=competition.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"competition.helpers.js","sources":["../../src/lib/helpers/competition.helpers.ts"],"sourcesContent":["/**\n * Competition helper functions\n */\n\nimport type { FUSportsCompetition, FUSportsSeason } from \"../types/canonical\";\n\n/**\n * Get the active season from a competition\n *\n * Returns the season where `active` is true. If no season is active,\n * returns the first season in the list (typically the most recent).\n *\n * @param competition - The competition with seasons array\n * @returns The active season, or the first season, or undefined if no seasons exist\n *\n * @example\n * ```typescript\n * const competition = await getFootballCompetition('3');\n * const activeSeason = getActiveSeason(competition);\n * console.log(activeSeason?.name); // \"2024/2025\"\n * ```\n */\nexport function getActiveSeason(competition: FUSportsCompetition): FUSportsSeason | undefined {\n if (!competition.seasons || competition.seasons.length === 0) {\n return undefined;\n }\n\n // Find the active season\n const activeSeason = competition.seasons.find((season) => season.active);\n\n // Return active season or fall back to the first one\n return activeSeason ?? competition.seasons[0];\n}\n\n"],"names":[],"mappings":"AAsBO,SAAS,gBAAgB,aAA8D;AAC1F,MAAI,CAAC,YAAY,WAAW,YAAY,QAAQ,WAAW,GAAG;AAC1D,WAAO;AAAA,EACX;AAGA,QAAM,eAAe,YAAY,QAAQ,KAAK,CAAC,WAAW,OAAO,MAAM;AAGvE,SAAO,gBAAgB,YAAY,QAAQ,CAAC;AAChD;"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
function aggregatePlayerStatisticsFromMatches(matches) {
|
|
2
|
+
const playerStatsMap = /* @__PURE__ */ new Map();
|
|
3
|
+
const playerMatchAppearances = /* @__PURE__ */ new Map();
|
|
4
|
+
matches.forEach((match) => {
|
|
5
|
+
if (!match.mainEvents || match.mainEvents.length === 0) return;
|
|
6
|
+
match.mainEvents.forEach((event) => {
|
|
7
|
+
if (event.primaryPlayer) {
|
|
8
|
+
const playerId = event.primaryPlayer.id;
|
|
9
|
+
if (!playerStatsMap.has(playerId)) {
|
|
10
|
+
playerStatsMap.set(playerId, {
|
|
11
|
+
playerId: event.primaryPlayer.id,
|
|
12
|
+
seasonId: "",
|
|
13
|
+
// Not applicable for match aggregation
|
|
14
|
+
teamId: "",
|
|
15
|
+
// Not applicable for match aggregation
|
|
16
|
+
goals: 0,
|
|
17
|
+
assists: 0,
|
|
18
|
+
yellowCards: 0,
|
|
19
|
+
redCards: 0,
|
|
20
|
+
appearances: 0
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const stats = playerStatsMap.get(playerId);
|
|
24
|
+
if (!playerMatchAppearances.has(playerId)) {
|
|
25
|
+
playerMatchAppearances.set(playerId, /* @__PURE__ */ new Set());
|
|
26
|
+
}
|
|
27
|
+
playerMatchAppearances.get(playerId).add(match.id);
|
|
28
|
+
if (event.type === "GOAL" || event.type === "PENALTY_GOAL") {
|
|
29
|
+
stats.goals = (stats.goals ?? 0) + 1;
|
|
30
|
+
}
|
|
31
|
+
if (event.type === "YELLOW_CARD") {
|
|
32
|
+
stats.yellowCards = (stats.yellowCards ?? 0) + 1;
|
|
33
|
+
}
|
|
34
|
+
if (event.type === "RED_CARD" || event.type === "YELLOW_RED_CARD") {
|
|
35
|
+
stats.redCards = (stats.redCards ?? 0) + 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (event.secondaryPlayer && (event.type === "GOAL" || event.type === "PENALTY_GOAL")) {
|
|
39
|
+
const assisterId = event.secondaryPlayer.id;
|
|
40
|
+
if (!playerStatsMap.has(assisterId)) {
|
|
41
|
+
playerStatsMap.set(assisterId, {
|
|
42
|
+
playerId: event.secondaryPlayer.id,
|
|
43
|
+
seasonId: "",
|
|
44
|
+
// Not applicable for match aggregation
|
|
45
|
+
teamId: "",
|
|
46
|
+
// Not applicable for match aggregation
|
|
47
|
+
goals: 0,
|
|
48
|
+
assists: 0,
|
|
49
|
+
yellowCards: 0,
|
|
50
|
+
redCards: 0,
|
|
51
|
+
appearances: 0
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const stats = playerStatsMap.get(assisterId);
|
|
55
|
+
stats.assists = (stats.assists ?? 0) + 1;
|
|
56
|
+
if (!playerMatchAppearances.has(assisterId)) {
|
|
57
|
+
playerMatchAppearances.set(assisterId, /* @__PURE__ */ new Set());
|
|
58
|
+
}
|
|
59
|
+
playerMatchAppearances.get(assisterId).add(match.id);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
playerMatchAppearances.forEach((matchIds, playerId) => {
|
|
64
|
+
const stats = playerStatsMap.get(playerId);
|
|
65
|
+
if (stats && stats.appearances !== void 0) {
|
|
66
|
+
stats.appearances = matchIds.size;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return playerStatsMap;
|
|
70
|
+
}
|
|
71
|
+
function toLeaderboardEntries(statsMap) {
|
|
72
|
+
return Array.from(statsMap.values()).map((stats) => ({
|
|
73
|
+
...stats,
|
|
74
|
+
totalCards: (stats.yellowCards ?? 0) + (stats.redCards ?? 0)
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
function getTopScorersFromMatches(matches, limit = 10) {
|
|
78
|
+
const statsMap = aggregatePlayerStatisticsFromMatches(matches);
|
|
79
|
+
const entries = toLeaderboardEntries(statsMap);
|
|
80
|
+
return entries.filter((entry) => (entry.goals ?? 0) > 0).sort((a, b) => {
|
|
81
|
+
const goalsA = a.goals ?? 0;
|
|
82
|
+
const goalsB = b.goals ?? 0;
|
|
83
|
+
if (goalsB !== goalsA) {
|
|
84
|
+
return goalsB - goalsA;
|
|
85
|
+
}
|
|
86
|
+
const assistsA = a.assists ?? 0;
|
|
87
|
+
const assistsB = b.assists ?? 0;
|
|
88
|
+
return assistsB - assistsA;
|
|
89
|
+
}).slice(0, limit);
|
|
90
|
+
}
|
|
91
|
+
function getTopAssistersFromMatches(matches, limit = 10) {
|
|
92
|
+
const statsMap = aggregatePlayerStatisticsFromMatches(matches);
|
|
93
|
+
const entries = toLeaderboardEntries(statsMap);
|
|
94
|
+
return entries.filter((entry) => (entry.assists ?? 0) > 0).sort((a, b) => {
|
|
95
|
+
const assistsA = a.assists ?? 0;
|
|
96
|
+
const assistsB = b.assists ?? 0;
|
|
97
|
+
if (assistsB !== assistsA) {
|
|
98
|
+
return assistsB - assistsA;
|
|
99
|
+
}
|
|
100
|
+
const goalsA = a.goals ?? 0;
|
|
101
|
+
const goalsB = b.goals ?? 0;
|
|
102
|
+
return goalsB - goalsA;
|
|
103
|
+
}).slice(0, limit);
|
|
104
|
+
}
|
|
105
|
+
function getMostCardedPlayersFromMatches(matches, limit = 10) {
|
|
106
|
+
const statsMap = aggregatePlayerStatisticsFromMatches(matches);
|
|
107
|
+
const entries = toLeaderboardEntries(statsMap);
|
|
108
|
+
return entries.filter((entry) => (entry.totalCards ?? 0) > 0).sort((a, b) => {
|
|
109
|
+
const scoreA = (a.yellowCards ?? 0) + (a.redCards ?? 0) * 3;
|
|
110
|
+
const scoreB = (b.yellowCards ?? 0) + (b.redCards ?? 0) * 3;
|
|
111
|
+
if (scoreB !== scoreA) {
|
|
112
|
+
return scoreB - scoreA;
|
|
113
|
+
}
|
|
114
|
+
const redA = a.redCards ?? 0;
|
|
115
|
+
const redB = b.redCards ?? 0;
|
|
116
|
+
if (redB !== redA) {
|
|
117
|
+
return redB - redA;
|
|
118
|
+
}
|
|
119
|
+
const yellowA = a.yellowCards ?? 0;
|
|
120
|
+
const yellowB = b.yellowCards ?? 0;
|
|
121
|
+
return yellowB - yellowA;
|
|
122
|
+
}).slice(0, limit);
|
|
123
|
+
}
|
|
124
|
+
function getTopGoalContributorsFromMatches(matches, limit = 10) {
|
|
125
|
+
const statsMap = aggregatePlayerStatisticsFromMatches(matches);
|
|
126
|
+
const entries = toLeaderboardEntries(statsMap);
|
|
127
|
+
return entries.filter((entry) => (entry.goals ?? 0) + (entry.assists ?? 0) > 0).sort((a, b) => {
|
|
128
|
+
const contributionsA = (a.goals ?? 0) + (a.assists ?? 0);
|
|
129
|
+
const contributionsB = (b.goals ?? 0) + (b.assists ?? 0);
|
|
130
|
+
if (contributionsB !== contributionsA) {
|
|
131
|
+
return contributionsB - contributionsA;
|
|
132
|
+
}
|
|
133
|
+
const goalsA = a.goals ?? 0;
|
|
134
|
+
const goalsB = b.goals ?? 0;
|
|
135
|
+
if (goalsB !== goalsA) {
|
|
136
|
+
return goalsB - goalsA;
|
|
137
|
+
}
|
|
138
|
+
const assistsA = a.assists ?? 0;
|
|
139
|
+
const assistsB = b.assists ?? 0;
|
|
140
|
+
return assistsB - assistsA;
|
|
141
|
+
}).slice(0, limit);
|
|
142
|
+
}
|
|
143
|
+
function getAllPlayerStatisticsFromMatches(matches) {
|
|
144
|
+
const statsMap = aggregatePlayerStatisticsFromMatches(matches);
|
|
145
|
+
const entries = toLeaderboardEntries(statsMap);
|
|
146
|
+
return entries.sort((a, b) => {
|
|
147
|
+
const appearancesA = a.appearances ?? 0;
|
|
148
|
+
const appearancesB = b.appearances ?? 0;
|
|
149
|
+
if (appearancesB !== appearancesA) {
|
|
150
|
+
return appearancesB - appearancesA;
|
|
151
|
+
}
|
|
152
|
+
const contributionsA = (a.goals ?? 0) + (a.assists ?? 0);
|
|
153
|
+
const contributionsB = (b.goals ?? 0) + (b.assists ?? 0);
|
|
154
|
+
return contributionsB - contributionsA;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
aggregatePlayerStatisticsFromMatches,
|
|
159
|
+
getAllPlayerStatisticsFromMatches,
|
|
160
|
+
getMostCardedPlayersFromMatches,
|
|
161
|
+
getTopAssistersFromMatches,
|
|
162
|
+
getTopGoalContributorsFromMatches,
|
|
163
|
+
getTopScorersFromMatches
|
|
164
|
+
};
|
|
165
|
+
//# sourceMappingURL=player.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.helpers.js","sources":["../../src/lib/helpers/player.helpers.ts"],"sourcesContent":["/**\n * Player statistics helpers\n * Aggregates player statistics from match events across multiple matches\n *\n * All functions return FUSportsPlayerSeasonStatistics (canonical type).\n *\n * Note: When aggregating from match events (not season data):\n * - playerId comes from the player in events\n * - seasonId and teamId are empty strings (not applicable for cross-match aggregation)\n * - Only fields extractable from match events are populated (goals, assists, cards, appearances)\n * - totalCards is computed as yellowCards + redCards\n * - Other fields (minutes, shots, etc.) remain undefined as they're not in basic match events\n */\n\nimport type { FUSportsMatch, FUSportsPlayerSeasonStatistics } from \"../types/canonical\";\n\n// ============================================================================\n// Core Aggregation Function\n// ============================================================================\n\n/**\n * Aggregate player statistics from match events across multiple matches\n *\n * This function extracts player data from mainEvents in matches and aggregates:\n * - Goals (GOAL, PENALTY_GOAL, excluding OWN_GOAL)\n * - Assists (secondaryPlayer in goal events)\n * - Yellow cards\n * - Red cards (including YELLOW_RED_CARD)\n * - Appearances (matches where player appeared in any event)\n *\n * @param matches - Array of matches to analyze\n * @returns Map of player ID to aggregated statistics\n */\nexport function aggregatePlayerStatisticsFromMatches(\n matches: FUSportsMatch[]\n): Map<string, FUSportsPlayerSeasonStatistics> {\n const playerStatsMap = new Map<string, FUSportsPlayerSeasonStatistics>();\n\n // Track which players appeared in which matches\n const playerMatchAppearances = new Map<string, Set<string>>();\n\n matches.forEach((match) => {\n if (!match.mainEvents || match.mainEvents.length === 0) return;\n\n match.mainEvents.forEach((event) => {\n // Process primary player (scorer, card recipient, etc.)\n if (event.primaryPlayer) {\n const playerId = event.primaryPlayer.id;\n\n // Initialize player stats if not exists\n if (!playerStatsMap.has(playerId)) {\n playerStatsMap.set(playerId, {\n playerId: event.primaryPlayer.id,\n seasonId: \"\", // Not applicable for match aggregation\n teamId: \"\", // Not applicable for match aggregation\n goals: 0,\n assists: 0,\n yellowCards: 0,\n redCards: 0,\n appearances: 0,\n });\n }\n\n const stats = playerStatsMap.get(playerId)!;\n\n // Track match appearance\n if (!playerMatchAppearances.has(playerId)) {\n playerMatchAppearances.set(playerId, new Set());\n }\n playerMatchAppearances.get(playerId)!.add(match.id);\n\n // Count goals (excluding own goals)\n if (event.type === \"GOAL\" || event.type === \"PENALTY_GOAL\") {\n stats.goals = (stats.goals ?? 0) + 1;\n }\n\n // Count cards\n if (event.type === \"YELLOW_CARD\") {\n stats.yellowCards = (stats.yellowCards ?? 0) + 1;\n }\n if (event.type === \"RED_CARD\" || event.type === \"YELLOW_RED_CARD\") {\n stats.redCards = (stats.redCards ?? 0) + 1;\n }\n }\n\n // Process secondary player (assister)\n if (event.secondaryPlayer && (event.type === \"GOAL\" || event.type === \"PENALTY_GOAL\")) {\n const assisterId = event.secondaryPlayer.id;\n\n // Initialize player stats if not exists\n if (!playerStatsMap.has(assisterId)) {\n playerStatsMap.set(assisterId, {\n playerId: event.secondaryPlayer.id,\n seasonId: \"\", // Not applicable for match aggregation\n teamId: \"\", // Not applicable for match aggregation\n goals: 0,\n assists: 0,\n yellowCards: 0,\n redCards: 0,\n appearances: 0,\n });\n }\n\n const stats = playerStatsMap.get(assisterId)!;\n stats.assists = (stats.assists ?? 0) + 1;\n\n // Track match appearance\n if (!playerMatchAppearances.has(assisterId)) {\n playerMatchAppearances.set(assisterId, new Set());\n }\n playerMatchAppearances.get(assisterId)!.add(match.id);\n }\n });\n });\n\n // Update appearances count\n playerMatchAppearances.forEach((matchIds, playerId) => {\n const stats = playerStatsMap.get(playerId);\n if (stats && stats.appearances !== undefined) {\n stats.appearances = matchIds.size;\n }\n });\n\n return playerStatsMap;\n}\n\n/**\n * Convert player statistics map to array with computed fields\n */\nfunction toLeaderboardEntries(statsMap: Map<string, FUSportsPlayerSeasonStatistics>): FUSportsPlayerSeasonStatistics[] {\n return Array.from(statsMap.values()).map((stats) => ({\n ...stats,\n totalCards: (stats.yellowCards ?? 0) + (stats.redCards ?? 0),\n }));\n}\n\n// ============================================================================\n// Leaderboard Helper Functions\n// ============================================================================\n\n/**\n * Get top scorers from match events\n *\n * Returns players sorted by goals (descending), with ties broken by assists.\n * Only includes players with at least 1 goal.\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of players sorted by goals\n *\n * @example\n * ```typescript\n * const topScorers = getTopScorersFromMatches(matches, 5);\n * topScorers.forEach(stats => {\n * console.log(`Player ${stats.playerId}: ${stats.goals} goals, ${stats.assists} assists`);\n * });\n * ```\n */\nexport function getTopScorersFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n const statsMap = aggregatePlayerStatisticsFromMatches(matches);\n const entries = toLeaderboardEntries(statsMap);\n\n return entries\n .filter((entry) => (entry.goals ?? 0) > 0)\n .sort((a, b) => {\n // Primary sort: goals (descending)\n const goalsA = a.goals ?? 0;\n const goalsB = b.goals ?? 0;\n if (goalsB !== goalsA) {\n return goalsB - goalsA;\n }\n // Tie-breaker: assists (descending)\n const assistsA = a.assists ?? 0;\n const assistsB = b.assists ?? 0;\n return assistsB - assistsA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get top assisters from match events\n *\n * Returns players sorted by assists (descending), with ties broken by goals.\n * Only includes players with at least 1 assist.\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of players sorted by assists\n *\n * @example\n * ```typescript\n * const topAssisters = getTopAssistersFromMatches(matches, 5);\n * topAssisters.forEach(stats => {\n * console.log(`Player ${stats.playerId}: ${stats.assists} assists, ${stats.goals} goals`);\n * });\n * ```\n */\nexport function getTopAssistersFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n const statsMap = aggregatePlayerStatisticsFromMatches(matches);\n const entries = toLeaderboardEntries(statsMap);\n\n return entries\n .filter((entry) => (entry.assists ?? 0) > 0)\n .sort((a, b) => {\n // Primary sort: assists (descending)\n const assistsA = a.assists ?? 0;\n const assistsB = b.assists ?? 0;\n if (assistsB !== assistsA) {\n return assistsB - assistsA;\n }\n // Tie-breaker: goals (descending)\n const goalsA = a.goals ?? 0;\n const goalsB = b.goals ?? 0;\n return goalsB - goalsA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get players with most cards from match events\n *\n * Returns players sorted by total cards (yellow + red, descending).\n * Red cards are weighted more heavily in sorting (red card = 3 yellow cards).\n * Only includes players with at least 1 card.\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of players sorted by cards\n *\n * @example\n * ```typescript\n * const mostCarded = getMostCardedPlayersFromMatches(matches, 5);\n * mostCarded.forEach(stats => {\n * console.log(`Player ${stats.playerId}: ${stats.yellowCards} yellow, ${stats.redCards} red`);\n * });\n * ```\n */\nexport function getMostCardedPlayersFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n const statsMap = aggregatePlayerStatisticsFromMatches(matches);\n const entries = toLeaderboardEntries(statsMap);\n\n return entries\n .filter((entry) => (entry.totalCards ?? 0) > 0)\n .sort((a, b) => {\n // Calculate weighted card score (red = 3 yellow)\n const scoreA = (a.yellowCards ?? 0) + (a.redCards ?? 0) * 3;\n const scoreB = (b.yellowCards ?? 0) + (b.redCards ?? 0) * 3;\n\n // Primary sort: weighted card score (descending)\n if (scoreB !== scoreA) {\n return scoreB - scoreA;\n }\n\n // Tie-breaker: red cards (descending)\n const redA = a.redCards ?? 0;\n const redB = b.redCards ?? 0;\n if (redB !== redA) {\n return redB - redA;\n }\n\n // Final tie-breaker: yellow cards (descending)\n const yellowA = a.yellowCards ?? 0;\n const yellowB = b.yellowCards ?? 0;\n return yellowB - yellowA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get players with most goal contributions (goals + assists) from match events\n *\n * Returns players sorted by total goal contributions (descending).\n * Ties are broken by goals first, then assists.\n * Only includes players with at least 1 goal contribution.\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of players sorted by goal contributions\n *\n * @example\n * ```typescript\n * const topContributors = getTopGoalContributorsFromMatches(matches, 5);\n * topContributors.forEach(player => {\n * const contributions = player.goals + player.assists;\n * console.log(`${player.player.name}: ${contributions} (${player.goals}G + ${player.assists}A)`);\n * });\n * ```\n */\nexport function getTopGoalContributorsFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n const statsMap = aggregatePlayerStatisticsFromMatches(matches);\n const entries = toLeaderboardEntries(statsMap);\n\n return entries\n .filter((entry) => (entry.goals ?? 0) + (entry.assists ?? 0) > 0)\n .sort((a, b) => {\n const contributionsA = (a.goals ?? 0) + (a.assists ?? 0);\n const contributionsB = (b.goals ?? 0) + (b.assists ?? 0);\n\n // Primary sort: total contributions (descending)\n if (contributionsB !== contributionsA) {\n return contributionsB - contributionsA;\n }\n\n // Tie-breaker: goals (descending)\n const goalsA = a.goals ?? 0;\n const goalsB = b.goals ?? 0;\n if (goalsB !== goalsA) {\n return goalsB - goalsA;\n }\n\n // Final tie-breaker: assists (descending)\n const assistsA = a.assists ?? 0;\n const assistsB = b.assists ?? 0;\n return assistsB - assistsA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get all player statistics from match events\n *\n * Returns all players who appeared in match events, sorted by appearances.\n * Useful for getting complete player statistics across matches.\n *\n * @param matches - Array of matches to analyze\n * @returns Array of all players with their statistics\n *\n * @example\n * ```typescript\n * const allPlayers = getAllPlayerStatisticsFromMatches(matches);\n * console.log(`Total players: ${allPlayers.length}`);\n * ```\n */\nexport function getAllPlayerStatisticsFromMatches(matches: FUSportsMatch[]): FUSportsPlayerSeasonStatistics[] {\n const statsMap = aggregatePlayerStatisticsFromMatches(matches);\n const entries = toLeaderboardEntries(statsMap);\n\n return entries.sort((a, b) => {\n // Sort by appearances (descending)\n const appearancesA = a.appearances ?? 0;\n const appearancesB = b.appearances ?? 0;\n if (appearancesB !== appearancesA) {\n return appearancesB - appearancesA;\n }\n // Tie-breaker: goals + assists (descending)\n const contributionsA = (a.goals ?? 0) + (a.assists ?? 0);\n const contributionsB = (b.goals ?? 0) + (b.assists ?? 0);\n return contributionsB - contributionsA;\n });\n}\n"],"names":[],"mappings":"AAiCO,SAAS,qCACZ,SAC2C;AAC3C,QAAM,qCAAqB,IAAA;AAG3B,QAAM,6CAA6B,IAAA;AAEnC,UAAQ,QAAQ,CAAC,UAAU;AACvB,QAAI,CAAC,MAAM,cAAc,MAAM,WAAW,WAAW,EAAG;AAExD,UAAM,WAAW,QAAQ,CAAC,UAAU;AAEhC,UAAI,MAAM,eAAe;AACrB,cAAM,WAAW,MAAM,cAAc;AAGrC,YAAI,CAAC,eAAe,IAAI,QAAQ,GAAG;AAC/B,yBAAe,IAAI,UAAU;AAAA,YACzB,UAAU,MAAM,cAAc;AAAA,YAC9B,UAAU;AAAA;AAAA,YACV,QAAQ;AAAA;AAAA,YACR,OAAO;AAAA,YACP,SAAS;AAAA,YACT,aAAa;AAAA,YACb,UAAU;AAAA,YACV,aAAa;AAAA,UAAA,CAChB;AAAA,QACL;AAEA,cAAM,QAAQ,eAAe,IAAI,QAAQ;AAGzC,YAAI,CAAC,uBAAuB,IAAI,QAAQ,GAAG;AACvC,iCAAuB,IAAI,UAAU,oBAAI,IAAA,CAAK;AAAA,QAClD;AACA,+BAAuB,IAAI,QAAQ,EAAG,IAAI,MAAM,EAAE;AAGlD,YAAI,MAAM,SAAS,UAAU,MAAM,SAAS,gBAAgB;AACxD,gBAAM,SAAS,MAAM,SAAS,KAAK;AAAA,QACvC;AAGA,YAAI,MAAM,SAAS,eAAe;AAC9B,gBAAM,eAAe,MAAM,eAAe,KAAK;AAAA,QACnD;AACA,YAAI,MAAM,SAAS,cAAc,MAAM,SAAS,mBAAmB;AAC/D,gBAAM,YAAY,MAAM,YAAY,KAAK;AAAA,QAC7C;AAAA,MACJ;AAGA,UAAI,MAAM,oBAAoB,MAAM,SAAS,UAAU,MAAM,SAAS,iBAAiB;AACnF,cAAM,aAAa,MAAM,gBAAgB;AAGzC,YAAI,CAAC,eAAe,IAAI,UAAU,GAAG;AACjC,yBAAe,IAAI,YAAY;AAAA,YAC3B,UAAU,MAAM,gBAAgB;AAAA,YAChC,UAAU;AAAA;AAAA,YACV,QAAQ;AAAA;AAAA,YACR,OAAO;AAAA,YACP,SAAS;AAAA,YACT,aAAa;AAAA,YACb,UAAU;AAAA,YACV,aAAa;AAAA,UAAA,CAChB;AAAA,QACL;AAEA,cAAM,QAAQ,eAAe,IAAI,UAAU;AAC3C,cAAM,WAAW,MAAM,WAAW,KAAK;AAGvC,YAAI,CAAC,uBAAuB,IAAI,UAAU,GAAG;AACzC,iCAAuB,IAAI,YAAY,oBAAI,IAAA,CAAK;AAAA,QACpD;AACA,+BAAuB,IAAI,UAAU,EAAG,IAAI,MAAM,EAAE;AAAA,MACxD;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AAGD,yBAAuB,QAAQ,CAAC,UAAU,aAAa;AACnD,UAAM,QAAQ,eAAe,IAAI,QAAQ;AACzC,QAAI,SAAS,MAAM,gBAAgB,QAAW;AAC1C,YAAM,cAAc,SAAS;AAAA,IACjC;AAAA,EACJ,CAAC;AAED,SAAO;AACX;AAKA,SAAS,qBAAqB,UAAyF;AACnH,SAAO,MAAM,KAAK,SAAS,OAAA,CAAQ,EAAE,IAAI,CAAC,WAAW;AAAA,IACjD,GAAG;AAAA,IACH,aAAa,MAAM,eAAe,MAAM,MAAM,YAAY;AAAA,EAAA,EAC5D;AACN;AAwBO,SAAS,yBACZ,SACA,QAAgB,IACgB;AAChC,QAAM,WAAW,qCAAqC,OAAO;AAC7D,QAAM,UAAU,qBAAqB,QAAQ;AAE7C,SAAO,QACF,OAAO,CAAC,WAAW,MAAM,SAAS,KAAK,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM;AAEZ,UAAM,SAAS,EAAE,SAAS;AAC1B,UAAM,SAAS,EAAE,SAAS;AAC1B,QAAI,WAAW,QAAQ;AACnB,aAAO,SAAS;AAAA,IACpB;AAEA,UAAM,WAAW,EAAE,WAAW;AAC9B,UAAM,WAAW,EAAE,WAAW;AAC9B,WAAO,WAAW;AAAA,EACtB,CAAC,EACA,MAAM,GAAG,KAAK;AACvB;AAoBO,SAAS,2BACZ,SACA,QAAgB,IACgB;AAChC,QAAM,WAAW,qCAAqC,OAAO;AAC7D,QAAM,UAAU,qBAAqB,QAAQ;AAE7C,SAAO,QACF,OAAO,CAAC,WAAW,MAAM,WAAW,KAAK,CAAC,EAC1C,KAAK,CAAC,GAAG,MAAM;AAEZ,UAAM,WAAW,EAAE,WAAW;AAC9B,UAAM,WAAW,EAAE,WAAW;AAC9B,QAAI,aAAa,UAAU;AACvB,aAAO,WAAW;AAAA,IACtB;AAEA,UAAM,SAAS,EAAE,SAAS;AAC1B,UAAM,SAAS,EAAE,SAAS;AAC1B,WAAO,SAAS;AAAA,EACpB,CAAC,EACA,MAAM,GAAG,KAAK;AACvB;AAqBO,SAAS,gCACZ,SACA,QAAgB,IACgB;AAChC,QAAM,WAAW,qCAAqC,OAAO;AAC7D,QAAM,UAAU,qBAAqB,QAAQ;AAE7C,SAAO,QACF,OAAO,CAAC,WAAW,MAAM,cAAc,KAAK,CAAC,EAC7C,KAAK,CAAC,GAAG,MAAM;AAEZ,UAAM,UAAU,EAAE,eAAe,MAAM,EAAE,YAAY,KAAK;AAC1D,UAAM,UAAU,EAAE,eAAe,MAAM,EAAE,YAAY,KAAK;AAG1D,QAAI,WAAW,QAAQ;AACnB,aAAO,SAAS;AAAA,IACpB;AAGA,UAAM,OAAO,EAAE,YAAY;AAC3B,UAAM,OAAO,EAAE,YAAY;AAC3B,QAAI,SAAS,MAAM;AACf,aAAO,OAAO;AAAA,IAClB;AAGA,UAAM,UAAU,EAAE,eAAe;AACjC,UAAM,UAAU,EAAE,eAAe;AACjC,WAAO,UAAU;AAAA,EACrB,CAAC,EACA,MAAM,GAAG,KAAK;AACvB;AAsBO,SAAS,kCACZ,SACA,QAAgB,IACgB;AAChC,QAAM,WAAW,qCAAqC,OAAO;AAC7D,QAAM,UAAU,qBAAqB,QAAQ;AAE7C,SAAO,QACF,OAAO,CAAC,WAAW,MAAM,SAAS,MAAM,MAAM,WAAW,KAAK,CAAC,EAC/D,KAAK,CAAC,GAAG,MAAM;AACZ,UAAM,kBAAkB,EAAE,SAAS,MAAM,EAAE,WAAW;AACtD,UAAM,kBAAkB,EAAE,SAAS,MAAM,EAAE,WAAW;AAGtD,QAAI,mBAAmB,gBAAgB;AACnC,aAAO,iBAAiB;AAAA,IAC5B;AAGA,UAAM,SAAS,EAAE,SAAS;AAC1B,UAAM,SAAS,EAAE,SAAS;AAC1B,QAAI,WAAW,QAAQ;AACnB,aAAO,SAAS;AAAA,IACpB;AAGA,UAAM,WAAW,EAAE,WAAW;AAC9B,UAAM,WAAW,EAAE,WAAW;AAC9B,WAAO,WAAW;AAAA,EACtB,CAAC,EACA,MAAM,GAAG,KAAK;AACvB;AAiBO,SAAS,kCAAkC,SAA4D;AAC1G,QAAM,WAAW,qCAAqC,OAAO;AAC7D,QAAM,UAAU,qBAAqB,QAAQ;AAE7C,SAAO,QAAQ,KAAK,CAAC,GAAG,MAAM;AAE1B,UAAM,eAAe,EAAE,eAAe;AACtC,UAAM,eAAe,EAAE,eAAe;AACtC,QAAI,iBAAiB,cAAc;AAC/B,aAAO,eAAe;AAAA,IAC1B;AAEA,UAAM,kBAAkB,EAAE,SAAS,MAAM,EAAE,WAAW;AACtD,UAAM,kBAAkB,EAAE,SAAS,MAAM,EAAE,WAAW;AACtD,WAAO,iBAAiB;AAAA,EAC5B,CAAC;AACL;"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { getAllPlayerStatisticsFromMatches, getMostCardedPlayersFromMatches, aggregatePlayerStatisticsFromMatches, getTopAssistersFromMatches, getTopGoalContributorsFromMatches, getTopScorersFromMatches } from "./player.helpers.js";
|
|
3
|
+
function useTopScorersFromMatches(matches, limit = 10) {
|
|
4
|
+
return useMemo(() => getTopScorersFromMatches(matches, limit), [matches, limit]);
|
|
5
|
+
}
|
|
6
|
+
function useTopAssistersFromMatches(matches, limit = 10) {
|
|
7
|
+
return useMemo(() => getTopAssistersFromMatches(matches, limit), [matches, limit]);
|
|
8
|
+
}
|
|
9
|
+
function useMostCardedPlayersFromMatches(matches, limit = 10) {
|
|
10
|
+
return useMemo(() => getMostCardedPlayersFromMatches(matches, limit), [matches, limit]);
|
|
11
|
+
}
|
|
12
|
+
function useTopGoalContributorsFromMatches(matches, limit = 10) {
|
|
13
|
+
return useMemo(() => getTopGoalContributorsFromMatches(matches, limit), [matches, limit]);
|
|
14
|
+
}
|
|
15
|
+
function useAllPlayerStatisticsFromMatches(matches) {
|
|
16
|
+
return useMemo(() => getAllPlayerStatisticsFromMatches(matches), [matches]);
|
|
17
|
+
}
|
|
18
|
+
function usePlayerStatisticsMap(matches) {
|
|
19
|
+
return useMemo(() => aggregatePlayerStatisticsFromMatches(matches), [matches]);
|
|
20
|
+
}
|
|
21
|
+
function usePlayerStatistics(matches, playerId) {
|
|
22
|
+
const statsMap = usePlayerStatisticsMap(matches);
|
|
23
|
+
return useMemo(() => statsMap.get(playerId), [statsMap, playerId]);
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
useAllPlayerStatisticsFromMatches,
|
|
27
|
+
useMostCardedPlayersFromMatches,
|
|
28
|
+
usePlayerStatistics,
|
|
29
|
+
usePlayerStatisticsMap,
|
|
30
|
+
useTopAssistersFromMatches,
|
|
31
|
+
useTopGoalContributorsFromMatches,
|
|
32
|
+
useTopScorersFromMatches
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=player.hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.hooks.js","sources":["../../src/lib/helpers/player.hooks.ts"],"sourcesContent":["/**\n * Player statistics React hooks with memoization\n * Provides cached player leaderboard data from match events\n */\n\nimport { useMemo } from \"react\";\nimport type { FUSportsMatch, FUSportsPlayerSeasonStatistics } from \"../types/canonical\";\nimport {\n getTopScorersFromMatches,\n getTopAssistersFromMatches,\n getMostCardedPlayersFromMatches,\n getTopGoalContributorsFromMatches,\n getAllPlayerStatisticsFromMatches,\n aggregatePlayerStatisticsFromMatches,\n} from \"./player.helpers\";\n\n// ============================================================================\n// React Hooks with useMemo\n// ============================================================================\n\n/**\n * Hook to get top scorers from match events with memoization\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Memoized array of top scorers\n *\n * @example\n * ```typescript\n * function MyComponent({ matches }: { matches: FUSportsMatch[] }) {\n * const topScorers = useTopScorersFromMatches(matches, 5);\n *\n * return (\n * <div>\n * {topScorers.map(player => (\n * <div key={player.player.id}>\n * {player.player.name}: {player.goals} goals\n * </div>\n * ))}\n * </div>\n * );\n * }\n * ```\n */\nexport function useTopScorersFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n return useMemo(() => getTopScorersFromMatches(matches, limit), [matches, limit]);\n}\n\n/**\n * Hook to get top assisters from match events with memoization\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Memoized array of top assisters\n */\nexport function useTopAssistersFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n return useMemo(() => getTopAssistersFromMatches(matches, limit), [matches, limit]);\n}\n\n/**\n * Hook to get most carded players from match events with memoization\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Memoized array of most carded players\n */\nexport function useMostCardedPlayersFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n return useMemo(() => getMostCardedPlayersFromMatches(matches, limit), [matches, limit]);\n}\n\n/**\n * Hook to get top goal contributors from match events with memoization\n *\n * @param matches - Array of matches to analyze\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Memoized array of top goal contributors\n */\nexport function useTopGoalContributorsFromMatches(\n matches: FUSportsMatch[],\n limit: number = 10\n): FUSportsPlayerSeasonStatistics[] {\n return useMemo(() => getTopGoalContributorsFromMatches(matches, limit), [matches, limit]);\n}\n\n/**\n * Hook to get all player statistics from match events with memoization\n *\n * @param matches - Array of matches to analyze\n * @returns Memoized array of all players with statistics\n */\nexport function useAllPlayerStatisticsFromMatches(matches: FUSportsMatch[]): FUSportsPlayerSeasonStatistics[] {\n return useMemo(() => getAllPlayerStatisticsFromMatches(matches), [matches]);\n}\n\n/**\n * Hook to get aggregated player statistics map with memoization\n *\n * Returns a Map of player ID to statistics, useful for looking up specific players.\n *\n * @param matches - Array of matches to analyze\n * @returns Memoized map of player ID to statistics\n *\n * @example\n * ```typescript\n * function MyComponent({ matches, playerId }: { matches: FUSportsMatch[], playerId: string }) {\n * const playerStatsMap = usePlayerStatisticsMap(matches);\n * const playerStats = playerStatsMap.get(playerId);\n *\n * if (!playerStats) return <div>Player not found</div>;\n *\n * return (\n * <div>\n * {playerStats.player.name}: {playerStats.goals} goals, {playerStats.assists} assists\n * </div>\n * );\n * }\n * ```\n */\nexport function usePlayerStatisticsMap(matches: FUSportsMatch[]): Map<string, FUSportsPlayerSeasonStatistics> {\n return useMemo(() => aggregatePlayerStatisticsFromMatches(matches), [matches]);\n}\n\n/**\n * Hook to get a specific player's statistics from match events with memoization\n *\n * @param matches - Array of matches to analyze\n * @param playerId - ID of the player to get statistics for\n * @returns Memoized player statistics or undefined if not found\n *\n * @example\n * ```typescript\n * function PlayerCard({ matches, playerId }: { matches: FUSportsMatch[], playerId: string }) {\n * const stats = usePlayerStatistics(matches, playerId);\n *\n * if (!stats) return <div>No statistics available</div>;\n *\n * return (\n * <div>\n * <h3>Player {stats.playerId}</h3>\n * <p>Goals: {stats.goals}</p>\n * <p>Assists: {stats.assists}</p>\n * <p>Appearances: {stats.appearances}</p>\n * </div>\n * );\n * }\n * ```\n */\nexport function usePlayerStatistics(\n matches: FUSportsMatch[],\n playerId: string\n): FUSportsPlayerSeasonStatistics | undefined {\n const statsMap = usePlayerStatisticsMap(matches);\n return useMemo(() => statsMap.get(playerId), [statsMap, playerId]);\n}\n"],"names":[],"mappings":";;AA4CO,SAAS,yBACZ,SACA,QAAgB,IACgB;AAChC,SAAO,QAAQ,MAAM,yBAAyB,SAAS,KAAK,GAAG,CAAC,SAAS,KAAK,CAAC;AACnF;AASO,SAAS,2BACZ,SACA,QAAgB,IACgB;AAChC,SAAO,QAAQ,MAAM,2BAA2B,SAAS,KAAK,GAAG,CAAC,SAAS,KAAK,CAAC;AACrF;AASO,SAAS,gCACZ,SACA,QAAgB,IACgB;AAChC,SAAO,QAAQ,MAAM,gCAAgC,SAAS,KAAK,GAAG,CAAC,SAAS,KAAK,CAAC;AAC1F;AASO,SAAS,kCACZ,SACA,QAAgB,IACgB;AAChC,SAAO,QAAQ,MAAM,kCAAkC,SAAS,KAAK,GAAG,CAAC,SAAS,KAAK,CAAC;AAC5F;AAQO,SAAS,kCAAkC,SAA4D;AAC1G,SAAO,QAAQ,MAAM,kCAAkC,OAAO,GAAG,CAAC,OAAO,CAAC;AAC9E;AA0BO,SAAS,uBAAuB,SAAuE;AAC1G,SAAO,QAAQ,MAAM,qCAAqC,OAAO,GAAG,CAAC,OAAO,CAAC;AACjF;AA2BO,SAAS,oBACZ,SACA,UAC0C;AAC1C,QAAM,WAAW,uBAAuB,OAAO;AAC/C,SAAO,QAAQ,MAAM,SAAS,IAAI,QAAQ,GAAG,CAAC,UAAU,QAAQ,CAAC;AACrE;"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
function getTeamNextMatch(teamId, matches) {
|
|
2
|
+
const now = /* @__PURE__ */ new Date();
|
|
3
|
+
const upcomingMatches = matches.filter((match) => {
|
|
4
|
+
const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;
|
|
5
|
+
const isUpcoming = match.status.type === "NOT_STARTED";
|
|
6
|
+
const isFuture = new Date(match.timing.kickoffTime) >= now;
|
|
7
|
+
return isTeamMatch && isUpcoming && isFuture;
|
|
8
|
+
});
|
|
9
|
+
upcomingMatches.sort((a, b) => new Date(a.timing.kickoffTime).getTime() - new Date(b.timing.kickoffTime).getTime());
|
|
10
|
+
return upcomingMatches[0];
|
|
11
|
+
}
|
|
12
|
+
function getTeamPreviousMatch(teamId, matches) {
|
|
13
|
+
const now = /* @__PURE__ */ new Date();
|
|
14
|
+
const finishedMatches = matches.filter((match) => {
|
|
15
|
+
const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;
|
|
16
|
+
const isFinished = match.status.type === "FINISHED";
|
|
17
|
+
const isPast = new Date(match.timing.kickoffTime) <= now;
|
|
18
|
+
return isTeamMatch && isFinished && isPast;
|
|
19
|
+
});
|
|
20
|
+
finishedMatches.sort((a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime());
|
|
21
|
+
return finishedMatches[0];
|
|
22
|
+
}
|
|
23
|
+
function getTeamUpcomingMatches(teamId, matches) {
|
|
24
|
+
const now = /* @__PURE__ */ new Date();
|
|
25
|
+
const upcomingMatches = matches.filter((match) => {
|
|
26
|
+
const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;
|
|
27
|
+
const isUpcoming = match.status.type === "NOT_STARTED" || match.status.type === "LIVE";
|
|
28
|
+
const isFuture = new Date(match.timing.kickoffTime) >= now || match.status.type === "LIVE";
|
|
29
|
+
return isTeamMatch && isUpcoming && isFuture;
|
|
30
|
+
});
|
|
31
|
+
return upcomingMatches.sort(
|
|
32
|
+
(a, b) => new Date(a.timing.kickoffTime).getTime() - new Date(b.timing.kickoffTime).getTime()
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
function getTeamFinishedMatches(teamId, matches) {
|
|
36
|
+
const now = /* @__PURE__ */ new Date();
|
|
37
|
+
const finishedMatches = matches.filter((match) => {
|
|
38
|
+
const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;
|
|
39
|
+
const isFinished = match.status.type === "FINISHED";
|
|
40
|
+
const isPast = new Date(match.timing.kickoffTime) <= now;
|
|
41
|
+
return isTeamMatch && isFinished && isPast;
|
|
42
|
+
});
|
|
43
|
+
return finishedMatches.sort(
|
|
44
|
+
(a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime()
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
function getTeamResultsTable(teamId, standings, matches) {
|
|
48
|
+
const finishedMatches = matches.filter((match) => match.status.type === "FINISHED");
|
|
49
|
+
const upcomingMatches = matches.filter(
|
|
50
|
+
(match) => match.status.type === "NOT_STARTED" || match.status.type === "POSTPONED"
|
|
51
|
+
);
|
|
52
|
+
const rows = standings.entries.map((entry) => {
|
|
53
|
+
const opponentId = entry.competitor.id;
|
|
54
|
+
const finishedMatchesBetween = finishedMatches.filter(
|
|
55
|
+
(match) => match.competitorOne.id === teamId && match.competitorTwo.id === opponentId || match.competitorOne.id === opponentId && match.competitorTwo.id === teamId
|
|
56
|
+
);
|
|
57
|
+
const upcomingMatchesBetween = upcomingMatches.filter(
|
|
58
|
+
(match) => match.competitorOne.id === teamId && match.competitorTwo.id === opponentId || match.competitorOne.id === opponentId && match.competitorTwo.id === teamId
|
|
59
|
+
);
|
|
60
|
+
let homeResult;
|
|
61
|
+
let awayResult;
|
|
62
|
+
finishedMatchesBetween.forEach((match) => {
|
|
63
|
+
const isTeamHome = match.competitorOne.id === teamId;
|
|
64
|
+
const homeScore = parseInt(match.score?.competitorOne ?? "0");
|
|
65
|
+
const awayScore = parseInt(match.score?.competitorTwo ?? "0");
|
|
66
|
+
const teamScore = isTeamHome ? homeScore : awayScore;
|
|
67
|
+
const opponentScore = isTeamHome ? awayScore : homeScore;
|
|
68
|
+
let result;
|
|
69
|
+
if (teamScore > opponentScore) {
|
|
70
|
+
result = "W";
|
|
71
|
+
} else if (teamScore === opponentScore) {
|
|
72
|
+
result = "D";
|
|
73
|
+
} else {
|
|
74
|
+
result = "L";
|
|
75
|
+
}
|
|
76
|
+
const resultData = {
|
|
77
|
+
result,
|
|
78
|
+
score: `${homeScore} - ${awayScore}`,
|
|
79
|
+
date: match.timing.kickoffTime,
|
|
80
|
+
matchId: match.id,
|
|
81
|
+
isUpcoming: false
|
|
82
|
+
};
|
|
83
|
+
if (isTeamHome) {
|
|
84
|
+
homeResult = resultData;
|
|
85
|
+
} else {
|
|
86
|
+
awayResult = resultData;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
upcomingMatchesBetween.forEach((match) => {
|
|
90
|
+
const isTeamHome = match.competitorOne.id === teamId;
|
|
91
|
+
if (isTeamHome && !homeResult) {
|
|
92
|
+
homeResult = {
|
|
93
|
+
date: match.timing.kickoffTime,
|
|
94
|
+
matchId: match.id,
|
|
95
|
+
isUpcoming: true
|
|
96
|
+
};
|
|
97
|
+
} else if (!isTeamHome && !awayResult) {
|
|
98
|
+
awayResult = {
|
|
99
|
+
date: match.timing.kickoffTime,
|
|
100
|
+
matchId: match.id,
|
|
101
|
+
isUpcoming: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
return {
|
|
106
|
+
rank: entry.rank,
|
|
107
|
+
teamId: entry.competitor.id,
|
|
108
|
+
teamName: entry.competitor.name,
|
|
109
|
+
teamShortName: entry.competitor.shortName,
|
|
110
|
+
teamLogo: entry.competitor.logo,
|
|
111
|
+
gamesPlayed: entry.stats.played ?? 0,
|
|
112
|
+
points: entry.stats.points ?? 0,
|
|
113
|
+
homeResult,
|
|
114
|
+
awayResult
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
teamId,
|
|
119
|
+
rows
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function getTeamTopScorers(squad, limit = 10) {
|
|
123
|
+
return squad.players.filter((player) => (player.seasonStatistics?.goals ?? 0) > 0).sort((a, b) => {
|
|
124
|
+
const goalsA = a.seasonStatistics?.goals ?? 0;
|
|
125
|
+
const goalsB = b.seasonStatistics?.goals ?? 0;
|
|
126
|
+
if (goalsB !== goalsA) {
|
|
127
|
+
return goalsB - goalsA;
|
|
128
|
+
}
|
|
129
|
+
const assistsA = a.seasonStatistics?.assists ?? 0;
|
|
130
|
+
const assistsB = b.seasonStatistics?.assists ?? 0;
|
|
131
|
+
return assistsB - assistsA;
|
|
132
|
+
}).slice(0, limit);
|
|
133
|
+
}
|
|
134
|
+
function getTeamTopAssisters(squad, limit = 10) {
|
|
135
|
+
return squad.players.filter((player) => (player.seasonStatistics?.assists ?? 0) > 0).sort((a, b) => {
|
|
136
|
+
const assistsA = a.seasonStatistics?.assists ?? 0;
|
|
137
|
+
const assistsB = b.seasonStatistics?.assists ?? 0;
|
|
138
|
+
if (assistsB !== assistsA) {
|
|
139
|
+
return assistsB - assistsA;
|
|
140
|
+
}
|
|
141
|
+
const goalsA = a.seasonStatistics?.goals ?? 0;
|
|
142
|
+
const goalsB = b.seasonStatistics?.goals ?? 0;
|
|
143
|
+
return goalsB - goalsA;
|
|
144
|
+
}).slice(0, limit);
|
|
145
|
+
}
|
|
146
|
+
function getTeamTopAppearances(squad, limit = 10) {
|
|
147
|
+
return squad.players.filter((player) => (player.seasonStatistics?.appearances ?? 0) > 0).sort((a, b) => {
|
|
148
|
+
const appearancesA = a.seasonStatistics?.appearances ?? 0;
|
|
149
|
+
const appearancesB = b.seasonStatistics?.appearances ?? 0;
|
|
150
|
+
if (appearancesB !== appearancesA) {
|
|
151
|
+
return appearancesB - appearancesA;
|
|
152
|
+
}
|
|
153
|
+
const minutesA = a.seasonStatistics?.minutes ?? 0;
|
|
154
|
+
const minutesB = b.seasonStatistics?.minutes ?? 0;
|
|
155
|
+
return minutesB - minutesA;
|
|
156
|
+
}).slice(0, limit);
|
|
157
|
+
}
|
|
158
|
+
function getTeamTopMinutesPlayed(squad, limit = 10) {
|
|
159
|
+
return squad.players.filter((player) => (player.seasonStatistics?.minutes ?? 0) > 0).sort((a, b) => {
|
|
160
|
+
const minutesA = a.seasonStatistics?.minutes ?? 0;
|
|
161
|
+
const minutesB = b.seasonStatistics?.minutes ?? 0;
|
|
162
|
+
if (minutesB !== minutesA) {
|
|
163
|
+
return minutesB - minutesA;
|
|
164
|
+
}
|
|
165
|
+
const appearancesA = a.seasonStatistics?.appearances ?? 0;
|
|
166
|
+
const appearancesB = b.seasonStatistics?.appearances ?? 0;
|
|
167
|
+
return appearancesB - appearancesA;
|
|
168
|
+
}).slice(0, limit);
|
|
169
|
+
}
|
|
170
|
+
function getTeamTopCleanSheets(squad, limit = 10) {
|
|
171
|
+
return squad.players.filter((player) => (player.seasonStatistics?.cleansheets ?? 0) > 0).sort((a, b) => {
|
|
172
|
+
const cleansheetsA = a.seasonStatistics?.cleansheets ?? 0;
|
|
173
|
+
const cleansheetsB = b.seasonStatistics?.cleansheets ?? 0;
|
|
174
|
+
if (cleansheetsB !== cleansheetsA) {
|
|
175
|
+
return cleansheetsB - cleansheetsA;
|
|
176
|
+
}
|
|
177
|
+
const appearancesA = a.seasonStatistics?.appearances ?? 0;
|
|
178
|
+
const appearancesB = b.seasonStatistics?.appearances ?? 0;
|
|
179
|
+
return appearancesB - appearancesA;
|
|
180
|
+
}).slice(0, limit);
|
|
181
|
+
}
|
|
182
|
+
function getTeamMostDecorated(squad, limit = 10) {
|
|
183
|
+
return squad.players.filter((player) => {
|
|
184
|
+
const yellowCards = player.seasonStatistics?.yellowCards ?? 0;
|
|
185
|
+
const redCards = player.seasonStatistics?.redCards ?? 0;
|
|
186
|
+
return yellowCards + redCards > 0;
|
|
187
|
+
}).sort((a, b) => {
|
|
188
|
+
const yellowA = a.seasonStatistics?.yellowCards ?? 0;
|
|
189
|
+
const redA = a.seasonStatistics?.redCards ?? 0;
|
|
190
|
+
const yellowB = b.seasonStatistics?.yellowCards ?? 0;
|
|
191
|
+
const redB = b.seasonStatistics?.redCards ?? 0;
|
|
192
|
+
const totalA = yellowA + redA * 3;
|
|
193
|
+
const totalB = yellowB + redB * 3;
|
|
194
|
+
if (totalB !== totalA) {
|
|
195
|
+
return totalB - totalA;
|
|
196
|
+
}
|
|
197
|
+
if (redB !== redA) {
|
|
198
|
+
return redB - redA;
|
|
199
|
+
}
|
|
200
|
+
return yellowB - yellowA;
|
|
201
|
+
}).slice(0, limit);
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
getTeamFinishedMatches,
|
|
205
|
+
getTeamMostDecorated,
|
|
206
|
+
getTeamNextMatch,
|
|
207
|
+
getTeamPreviousMatch,
|
|
208
|
+
getTeamResultsTable,
|
|
209
|
+
getTeamTopAppearances,
|
|
210
|
+
getTeamTopAssisters,
|
|
211
|
+
getTeamTopCleanSheets,
|
|
212
|
+
getTeamTopMinutesPlayed,
|
|
213
|
+
getTeamTopScorers,
|
|
214
|
+
getTeamUpcomingMatches
|
|
215
|
+
};
|
|
216
|
+
//# sourceMappingURL=team.helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"team.helpers.js","sources":["../../src/lib/helpers/team.helpers.ts"],"sourcesContent":["/**\n * Team helper functions\n */\n\nimport type { FUSportsMatch, FUSportsStandings, FUSportsSquad, FUSportsSquadMember } from \"../types/canonical\";\n\n/**\n * Result against a specific team in the results table\n */\nexport interface TeamResultTableResult {\n /** Result type: W (win), D (draw), L (loss) from the team's perspective - used for styling. Undefined for upcoming matches */\n result?: \"W\" | \"D\" | \"L\";\n /** Score in format \"home score - away score\" (e.g., \"2 - 1\" means home team scored 2, away team scored 1). Undefined for upcoming matches */\n score?: string;\n /** Match date (for finished matches) or upcoming match date (for scheduled matches) */\n date: Date;\n /** Match ID for reference */\n matchId: string;\n /** Whether this is an upcoming match (not yet played) */\n isUpcoming: boolean;\n}\n\n/**\n * Single row in the team results table\n */\nexport interface TeamResultTableRow {\n /** Position in the standings */\n rank: number;\n /** Team ID */\n teamId: string;\n /** Team name */\n teamName: string;\n /** Team short name */\n teamShortName?: string;\n /** Team logo URL */\n teamLogo?: string;\n /** Games played */\n gamesPlayed: number;\n /** Points */\n points: number;\n /** Home result against the specified team (finished or upcoming) */\n homeResult?: TeamResultTableResult;\n /** Away result against the specified team (finished or upcoming) */\n awayResult?: TeamResultTableResult;\n}\n\n/**\n * Complete team results table data structure\n */\nexport interface TeamResultTable {\n /** The team ID this table is for */\n teamId: string;\n /** Array of table rows, ordered by standing position */\n rows: TeamResultTableRow[];\n}\n\n/**\n * Get the next upcoming match for a team from a list of matches\n *\n * Filters matches to find the team's next scheduled match (NOT_STARTED or LIVE)\n * with the earliest start time from now.\n *\n * @param teamId - The team ID\n * @param matches - Array of matches to filter through\n * @returns The next match or undefined if no upcoming match found\n *\n * @example\n * ```typescript\n * const matches = await getFootballMatches({ teamIds: ['102'], limit: 50, offset: 0 });\n * const nextMatch = getTeamNextMatch('102', matches);\n * if (nextMatch) {\n * console.log(`Next: ${nextMatch.competitorOne.name} vs ${nextMatch.competitorTwo.name}`);\n * console.log(`Kickoff: ${nextMatch.timing.kickoffTime}`);\n * }\n * ```\n */\nexport function getTeamNextMatch(teamId: string, matches: FUSportsMatch[]): FUSportsMatch | undefined {\n const now = new Date();\n\n // Filter for team's upcoming matches (NOT_STARTED or LIVE)\n const upcomingMatches = matches.filter((match) => {\n const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;\n const isUpcoming = match.status.type === \"NOT_STARTED\";\n const isFuture = new Date(match.timing.kickoffTime) >= now;\n return isTeamMatch && isUpcoming && isFuture;\n });\n\n // Sort by start time ascending and return the first one\n upcomingMatches.sort((a, b) => new Date(a.timing.kickoffTime).getTime() - new Date(b.timing.kickoffTime).getTime());\n\n return upcomingMatches[0];\n}\n\n/**\n * Get the previous/last match for a team from a list of matches\n *\n * Filters matches to find the team's most recent finished match.\n *\n * @param teamId - The team ID\n * @param matches - Array of matches to filter through\n * @returns The previous match or undefined if no finished match found\n *\n * @example\n * ```typescript\n * const matches = await getFootballMatches({ teamIds: ['102'], limit: 50, offset: 0 });\n * const prevMatch = getTeamPreviousMatch('102', matches);\n * if (prevMatch) {\n * console.log(`Last: ${prevMatch.competitorOne.name} ${prevMatch.score?.home}-${prevMatch.score?.away} ${prevMatch.competitorTwo.name}`);\n * }\n * ```\n */\nexport function getTeamPreviousMatch(teamId: string, matches: FUSportsMatch[]): FUSportsMatch | undefined {\n const now = new Date();\n\n // Filter for team's finished matches\n const finishedMatches = matches.filter((match) => {\n const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;\n const isFinished = match.status.type === \"FINISHED\";\n const isPast = new Date(match.timing.kickoffTime) <= now;\n return isTeamMatch && isFinished && isPast;\n });\n\n // Sort by start time descending and return the first one (most recent)\n finishedMatches.sort((a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime());\n\n return finishedMatches[0];\n}\n\n/**\n * Get all upcoming matches for a team from a list of matches\n *\n * Filters matches to find all the team's upcoming matches (NOT_STARTED or LIVE),\n * sorted by kickoff time (earliest first).\n *\n * @param teamId - The team ID\n * @param matches - Array of matches to filter through\n * @returns Array of upcoming matches sorted by kickoff time\n *\n * @example\n * ```typescript\n * const matches = await getFootballMatches({ teamIds: ['102'], limit: 50, offset: 0 });\n * const upcomingMatches = getTeamUpcomingMatches('102', matches);\n * upcomingMatches.forEach(match => {\n * console.log(`${match.competitorOne.name} vs ${match.competitorTwo.name} - ${match.timing.kickoffTime}`);\n * });\n * ```\n */\nexport function getTeamUpcomingMatches(teamId: string, matches: FUSportsMatch[]): FUSportsMatch[] {\n const now = new Date();\n\n // Filter for team's upcoming matches (NOT_STARTED or LIVE)\n const upcomingMatches = matches.filter((match) => {\n const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;\n const isUpcoming = match.status.type === \"NOT_STARTED\" || match.status.type === \"LIVE\";\n const isFuture = new Date(match.timing.kickoffTime) >= now || match.status.type === \"LIVE\";\n return isTeamMatch && isUpcoming && isFuture;\n });\n\n // Sort by start time ascending (earliest first)\n return upcomingMatches.sort(\n (a, b) => new Date(a.timing.kickoffTime).getTime() - new Date(b.timing.kickoffTime).getTime()\n );\n}\n\n/**\n * Get all finished matches for a team from a list of matches\n *\n * Filters matches to find all the team's finished matches,\n * sorted by kickoff time (most recent first).\n *\n * @param teamId - The team ID\n * @param matches - Array of matches to filter through\n * @returns Array of finished matches sorted by kickoff time (most recent first)\n *\n * @example\n * ```typescript\n * const matches = await getFootballMatches({ teamIds: ['102'], limit: 50, offset: 0 });\n * const finishedMatches = getTeamFinishedMatches('102', matches);\n * finishedMatches.forEach(match => {\n * console.log(`${match.competitorOne.name} ${match.score?.competitorOne}-${match.score?.competitorTwo} ${match.competitorTwo.name}`);\n * });\n * ```\n */\nexport function getTeamFinishedMatches(teamId: string, matches: FUSportsMatch[]): FUSportsMatch[] {\n const now = new Date();\n\n // Filter for team's finished matches\n const finishedMatches = matches.filter((match) => {\n const isTeamMatch = match.competitorOne.id === teamId || match.competitorTwo.id === teamId;\n const isFinished = match.status.type === \"FINISHED\";\n const isPast = new Date(match.timing.kickoffTime) <= now;\n return isTeamMatch && isFinished && isPast;\n });\n\n // Sort by start time descending (most recent first)\n return finishedMatches.sort(\n (a, b) => new Date(b.timing.kickoffTime).getTime() - new Date(a.timing.kickoffTime).getTime()\n );\n}\n\n/**\n * Get team results table showing all teams in the standings with their results against a specific team\n *\n * This creates a table showing:\n * - All teams ordered by their standing position\n * - Their games played and points from the standings\n * - Their home and away results against the specified team\n * - The date of their last match against the specified team\n *\n * @param teamId - The team ID to get results against\n * @param standings - The competition standings\n * @param matches - Array of finished matches in the competition\n * @returns Team results table data structure\n *\n * @example\n * ```typescript\n * const standings = await getFootballStandings('123');\n * const matches = await getFootballMatches({ stageIds: ['123'], limit: 500 });\n * const resultsTable = getTeamResultsTable('102', standings, matches);\n *\n * resultsTable.rows.forEach(row => {\n * console.log(`${row.rank}. ${row.teamName} - ${row.points} pts`);\n * if (row.homeResult) {\n * console.log(` Home: ${row.homeResult.result} ${row.homeResult.score}`);\n * }\n * if (row.awayResult) {\n * console.log(` Away: ${row.awayResult.result} ${row.awayResult.score}`);\n * }\n * });\n * ```\n */\nexport function getTeamResultsTable(\n teamId: string,\n standings: FUSportsStandings,\n matches: FUSportsMatch[]\n): TeamResultTable {\n // Filter for finished and upcoming matches\n const finishedMatches = matches.filter((match) => match.status.type === \"FINISHED\");\n const upcomingMatches = matches.filter(\n (match) => match.status.type === \"NOT_STARTED\" || match.status.type === \"POSTPONED\"\n );\n\n // Build the results table rows\n const rows: TeamResultTableRow[] = standings.entries.map((entry) => {\n const opponentId = entry.competitor.id;\n\n // Find all finished matches between the team and this opponent\n const finishedMatchesBetween = finishedMatches.filter(\n (match) =>\n (match.competitorOne.id === teamId && match.competitorTwo.id === opponentId) ||\n (match.competitorOne.id === opponentId && match.competitorTwo.id === teamId)\n );\n\n // Find upcoming matches between the team and this opponent\n const upcomingMatchesBetween = upcomingMatches.filter(\n (match) =>\n (match.competitorOne.id === teamId && match.competitorTwo.id === opponentId) ||\n (match.competitorOne.id === opponentId && match.competitorTwo.id === teamId)\n );\n\n // Separate home and away matches from the team's perspective (the team we're viewing the table for)\n let homeResult: TeamResultTableResult | undefined;\n let awayResult: TeamResultTableResult | undefined;\n\n // Process finished matches\n finishedMatchesBetween.forEach((match) => {\n const isTeamHome = match.competitorOne.id === teamId;\n\n // Always show score as: home team score - away team score\n const homeScore = parseInt(match.score?.competitorOne ?? \"0\");\n const awayScore = parseInt(match.score?.competitorTwo ?? \"0\");\n\n // Determine the team's score and opponent's score\n const teamScore = isTeamHome ? homeScore : awayScore;\n const opponentScore = isTeamHome ? awayScore : homeScore;\n\n // Determine result from the team's perspective (the team we're viewing the table for)\n let result: \"W\" | \"D\" | \"L\";\n if (teamScore > opponentScore) {\n result = \"W\";\n } else if (teamScore === opponentScore) {\n result = \"D\";\n } else {\n result = \"L\";\n }\n\n const resultData: TeamResultTableResult = {\n result,\n score: `${homeScore} - ${awayScore}`,\n date: match.timing.kickoffTime,\n matchId: match.id,\n isUpcoming: false,\n };\n\n // Assign to home or away result based on where the team played\n if (isTeamHome) {\n homeResult = resultData;\n } else {\n awayResult = resultData;\n }\n });\n\n // Process upcoming matches (only if no finished match exists for that venue)\n upcomingMatchesBetween.forEach((match) => {\n const isTeamHome = match.competitorOne.id === teamId;\n\n if (isTeamHome && !homeResult) {\n homeResult = {\n date: match.timing.kickoffTime,\n matchId: match.id,\n isUpcoming: true,\n };\n } else if (!isTeamHome && !awayResult) {\n awayResult = {\n date: match.timing.kickoffTime,\n matchId: match.id,\n isUpcoming: true,\n };\n }\n });\n\n return {\n rank: entry.rank,\n teamId: entry.competitor.id,\n teamName: entry.competitor.name,\n teamShortName: entry.competitor.shortName,\n teamLogo: entry.competitor.logo,\n gamesPlayed: entry.stats.played ?? 0,\n points: entry.stats.points ?? 0,\n homeResult,\n awayResult,\n };\n });\n\n return {\n teamId,\n rows,\n };\n}\n\n// ============================================================================\n// Squad Statistics Helpers\n// ============================================================================\n\n/**\n * Get team's top scorers sorted by goals\n *\n * Returns players sorted by total goals (descending), with ties broken by assists.\n * Only includes players with at least 1 goal.\n *\n * @param squad - The team squad data\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of squad members sorted by goals\n *\n * @example\n * ```typescript\n * const squad = await getFootballSquad('102');\n * const topScorers = getTeamTopScorers(squad, 5);\n * topScorers.forEach(player => {\n * console.log(`${player.name}: ${player.seasonStatistics?.goals} goals`);\n * });\n * ```\n */\nexport function getTeamTopScorers(squad: FUSportsSquad, limit: number = 10): FUSportsSquadMember[] {\n return squad.players\n .filter((player) => (player.seasonStatistics?.goals ?? 0) > 0)\n .sort((a, b) => {\n const goalsA = a.seasonStatistics?.goals ?? 0;\n const goalsB = b.seasonStatistics?.goals ?? 0;\n\n // Primary sort: goals (descending)\n if (goalsB !== goalsA) {\n return goalsB - goalsA;\n }\n\n // Tie-breaker: assists (descending)\n const assistsA = a.seasonStatistics?.assists ?? 0;\n const assistsB = b.seasonStatistics?.assists ?? 0;\n return assistsB - assistsA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get team's top assisters sorted by assists\n *\n * Returns players sorted by total assists (descending), with ties broken by goals.\n * Only includes players with at least 1 assist.\n *\n * @param squad - The team squad data\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of squad members sorted by assists\n *\n * @example\n * ```typescript\n * const squad = await getFootballSquad('102');\n * const topAssisters = getTeamTopAssisters(squad, 5);\n * topAssisters.forEach(player => {\n * console.log(`${player.name}: ${player.seasonStatistics?.assists} assists`);\n * });\n * ```\n */\nexport function getTeamTopAssisters(squad: FUSportsSquad, limit: number = 10): FUSportsSquadMember[] {\n return squad.players\n .filter((player) => (player.seasonStatistics?.assists ?? 0) > 0)\n .sort((a, b) => {\n const assistsA = a.seasonStatistics?.assists ?? 0;\n const assistsB = b.seasonStatistics?.assists ?? 0;\n\n // Primary sort: assists (descending)\n if (assistsB !== assistsA) {\n return assistsB - assistsA;\n }\n\n // Tie-breaker: goals (descending)\n const goalsA = a.seasonStatistics?.goals ?? 0;\n const goalsB = b.seasonStatistics?.goals ?? 0;\n return goalsB - goalsA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get team's players with most appearances sorted by appearances\n *\n * Returns players sorted by total appearances (descending), with ties broken by minutes played.\n * Only includes players with at least 1 appearance.\n *\n * @param squad - The team squad data\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of squad members sorted by appearances\n *\n * @example\n * ```typescript\n * const squad = await getFootballSquad('102');\n * const topAppearances = getTeamTopAppearances(squad, 5);\n * topAppearances.forEach(player => {\n * console.log(`${player.name}: ${player.seasonStatistics?.appearances} appearances`);\n * });\n * ```\n */\nexport function getTeamTopAppearances(squad: FUSportsSquad, limit: number = 10): FUSportsSquadMember[] {\n return squad.players\n .filter((player) => (player.seasonStatistics?.appearances ?? 0) > 0)\n .sort((a, b) => {\n const appearancesA = a.seasonStatistics?.appearances ?? 0;\n const appearancesB = b.seasonStatistics?.appearances ?? 0;\n\n // Primary sort: appearances (descending)\n if (appearancesB !== appearancesA) {\n return appearancesB - appearancesA;\n }\n\n // Tie-breaker: minutes played (descending)\n const minutesA = a.seasonStatistics?.minutes ?? 0;\n const minutesB = b.seasonStatistics?.minutes ?? 0;\n return minutesB - minutesA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get team's players with most minutes played sorted by minutes\n *\n * Returns players sorted by total minutes played (descending), with ties broken by appearances.\n * Only includes players with at least 1 minute played.\n *\n * @param squad - The team squad data\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of squad members sorted by minutes played\n *\n * @example\n * ```typescript\n * const squad = await getFootballSquad('102');\n * const topMinutes = getTeamTopMinutesPlayed(squad, 5);\n * topMinutes.forEach(player => {\n * console.log(`${player.name}: ${player.seasonStatistics?.minutes} minutes`);\n * });\n * ```\n */\nexport function getTeamTopMinutesPlayed(squad: FUSportsSquad, limit: number = 10): FUSportsSquadMember[] {\n return squad.players\n .filter((player) => (player.seasonStatistics?.minutes ?? 0) > 0)\n .sort((a, b) => {\n const minutesA = a.seasonStatistics?.minutes ?? 0;\n const minutesB = b.seasonStatistics?.minutes ?? 0;\n\n // Primary sort: minutes (descending)\n if (minutesB !== minutesA) {\n return minutesB - minutesA;\n }\n\n // Tie-breaker: appearances (descending)\n const appearancesA = a.seasonStatistics?.appearances ?? 0;\n const appearancesB = b.seasonStatistics?.appearances ?? 0;\n return appearancesB - appearancesA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get team's players with most clean sheets sorted by clean sheets\n *\n * Returns players sorted by total clean sheets (descending), with ties broken by appearances.\n * Only includes players with at least 1 clean sheet.\n * Typically used for goalkeepers and defenders.\n *\n * @param squad - The team squad data\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of squad members sorted by clean sheets\n *\n * @example\n * ```typescript\n * const squad = await getFootballSquad('102');\n * const topCleanSheets = getTeamTopCleanSheets(squad, 5);\n * topCleanSheets.forEach(player => {\n * console.log(`${player.name}: ${player.seasonStatistics?.cleansheets} clean sheets`);\n * });\n * ```\n */\nexport function getTeamTopCleanSheets(squad: FUSportsSquad, limit: number = 10): FUSportsSquadMember[] {\n return squad.players\n .filter((player) => (player.seasonStatistics?.cleansheets ?? 0) > 0)\n .sort((a, b) => {\n const cleansheetsA = a.seasonStatistics?.cleansheets ?? 0;\n const cleansheetsB = b.seasonStatistics?.cleansheets ?? 0;\n\n // Primary sort: clean sheets (descending)\n if (cleansheetsB !== cleansheetsA) {\n return cleansheetsB - cleansheetsA;\n }\n\n // Tie-breaker: appearances (descending)\n const appearancesA = a.seasonStatistics?.appearances ?? 0;\n const appearancesB = b.seasonStatistics?.appearances ?? 0;\n return appearancesB - appearancesA;\n })\n .slice(0, limit);\n}\n\n/**\n * Get team's most decorated players (most cards) sorted by total cards\n *\n * Returns players sorted by total cards (yellow + red cards, descending).\n * Red cards are weighted more heavily (red card = 3 yellow cards equivalent).\n * Ties are broken by red cards first, then yellow cards.\n * Only includes players with at least 1 card.\n *\n * @param squad - The team squad data\n * @param limit - Maximum number of players to return (default: 10)\n * @returns Array of squad members sorted by total cards\n *\n * @example\n * ```typescript\n * const squad = await getFootballSquad('102');\n * const mostDecorated = getTeamMostDecorated(squad, 5);\n * mostDecorated.forEach(player => {\n * const yellow = player.seasonStatistics?.yellowCards ?? 0;\n * const red = player.seasonStatistics?.redCards ?? 0;\n * console.log(`${player.name}: ${yellow} yellow, ${red} red`);\n * });\n * ```\n */\nexport function getTeamMostDecorated(squad: FUSportsSquad, limit: number = 10): FUSportsSquadMember[] {\n return squad.players\n .filter((player) => {\n const yellowCards = player.seasonStatistics?.yellowCards ?? 0;\n const redCards = player.seasonStatistics?.redCards ?? 0;\n return yellowCards + redCards > 0;\n })\n .sort((a, b) => {\n const yellowA = a.seasonStatistics?.yellowCards ?? 0;\n const redA = a.seasonStatistics?.redCards ?? 0;\n const yellowB = b.seasonStatistics?.yellowCards ?? 0;\n const redB = b.seasonStatistics?.redCards ?? 0;\n\n // Calculate weighted total (red card = 3 yellow cards)\n const totalA = yellowA + redA * 3;\n const totalB = yellowB + redB * 3;\n\n // Primary sort: total weighted cards (descending)\n if (totalB !== totalA) {\n return totalB - totalA;\n }\n\n // Tie-breaker 1: red cards (descending)\n if (redB !== redA) {\n return redB - redA;\n }\n\n // Tie-breaker 2: yellow cards (descending)\n return yellowB - yellowA;\n })\n .slice(0, limit);\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,27 +4,25 @@
|
|
|
4
4
|
"description": "A TypeScript library for fetching and transforming sports data from multiple API providers. Returns clean, canonical types that are provider-agnostic.",
|
|
5
5
|
"homepage": "https://fansunited.com/",
|
|
6
6
|
"private": false,
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.14.2",
|
|
8
8
|
"type": "module",
|
|
9
|
-
"
|
|
9
|
+
"sideEffects": false,
|
|
10
10
|
"module": "./fansunited-data-layer.js",
|
|
11
11
|
"types": "./index.d.ts",
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./index.d.ts",
|
|
15
|
-
"import": "./fansunited-data-layer.js"
|
|
16
|
-
"require": "./fansunited-data-layer.cjs"
|
|
15
|
+
"import": "./fansunited-data-layer.js"
|
|
17
16
|
},
|
|
18
17
|
"./client": {
|
|
19
18
|
"types": "./client.d.ts",
|
|
20
|
-
"import": "./client.js"
|
|
21
|
-
"require": "./client.cjs"
|
|
19
|
+
"import": "./client.js"
|
|
22
20
|
}
|
|
23
21
|
},
|
|
24
22
|
"scripts": {
|
|
25
23
|
"dev": "vite",
|
|
26
24
|
"build": "tsc -b && vite build",
|
|
27
|
-
"build:lib": "vite build && tsc --project tsconfig.lib.json && sh scripts/
|
|
25
|
+
"build:lib": "vite build && tsc --project tsconfig.lib.json && sh scripts/prepare-package.sh",
|
|
28
26
|
"build:lib:watch": "vite build --watch",
|
|
29
27
|
"lint": "eslint .",
|
|
30
28
|
"preview": "vite preview",
|
|
@@ -33,7 +31,7 @@
|
|
|
33
31
|
},
|
|
34
32
|
"peerDependencies": {
|
|
35
33
|
"fansunited-sdk-esm": "^1.122.0",
|
|
36
|
-
"fansunited-sports-ui": "
|
|
34
|
+
"fansunited-sports-ui": "^0.6.0",
|
|
37
35
|
"react": "^18.0.0 || ^19.0.0",
|
|
38
36
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
39
37
|
},
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { calculateCompetitionStats } from "../utils/competitionStats.js";
|
|
3
|
+
function useCompetitionStats(matches) {
|
|
4
|
+
const competitionStats = useMemo(() => calculateCompetitionStats(matches), [matches]);
|
|
5
|
+
return competitionStats;
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
useCompetitionStats
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=useCompetitionStats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCompetitionStats.js","sources":["../../../../src/lib/providers/competition/hooks/useCompetitionStats.ts"],"sourcesContent":["/**\n * Competition statistics hook\n * Provides memoized competition statistics calculations\n */\n\nimport { useMemo } from \"react\";\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport { calculateCompetitionStats } from \"../utils/competitionStats\";\nimport type { CompetitionStats } from \"../utils/competitionStats\";\n\nexport function useCompetitionStats(matches: FUSportsMatch[]): CompetitionStats {\n // Calculate competition stats and memoize\n const competitionStats = useMemo(() => calculateCompetitionStats(matches), [matches]);\n\n return competitionStats;\n}\n"],"names":[],"mappings":";;AAUO,SAAS,oBAAoB,SAA4C;AAE5E,QAAM,mBAAmB,QAAQ,MAAM,0BAA0B,OAAO,GAAG,CAAC,OAAO,CAAC;AAEpF,SAAO;AACX;"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { calculateGoalDistribution } from "../utils/goalDistribution.js";
|
|
3
|
+
function useGoalDistribution(matches) {
|
|
4
|
+
const finishedMatches = useMemo(() => matches.filter((m) => m.status.type === "FINISHED"), [matches]);
|
|
5
|
+
const goalDistribution = useMemo(() => calculateGoalDistribution(finishedMatches, 15), [finishedMatches]);
|
|
6
|
+
const getGoalDistribution = useMemo(
|
|
7
|
+
() => (intervalMinutes = 15) => {
|
|
8
|
+
return calculateGoalDistribution(finishedMatches, intervalMinutes);
|
|
9
|
+
},
|
|
10
|
+
[finishedMatches]
|
|
11
|
+
);
|
|
12
|
+
return useMemo(
|
|
13
|
+
() => ({
|
|
14
|
+
goalDistribution,
|
|
15
|
+
getGoalDistribution
|
|
16
|
+
}),
|
|
17
|
+
[goalDistribution, getGoalDistribution]
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
useGoalDistribution
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=useGoalDistribution.js.map
|