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.
Files changed (242) hide show
  1. package/api/fansunited/constants.js +5 -0
  2. package/api/fansunited/constants.js.map +1 -0
  3. package/api/fansunited/football/competition/index.js +21 -0
  4. package/api/fansunited/football/competition/index.js.map +1 -0
  5. package/api/fansunited/football/competition/transformer.js +41 -0
  6. package/api/fansunited/football/competition/transformer.js.map +1 -0
  7. package/api/fansunited/football/competitions/index.js +56 -0
  8. package/api/fansunited/football/competitions/index.js.map +1 -0
  9. package/api/fansunited/football/competitions/transformer.js +68 -0
  10. package/api/fansunited/football/competitions/transformer.js.map +1 -0
  11. package/api/fansunited/football/http.js +7 -0
  12. package/api/fansunited/football/http.js.map +1 -0
  13. package/api/fansunited/football/matches/index.js +132 -0
  14. package/api/fansunited/football/matches/index.js.map +1 -0
  15. package/api/fansunited/football/matches/transformer.js +148 -0
  16. package/api/fansunited/football/matches/transformer.js.map +1 -0
  17. package/api/fansunited/football/players/index.js +60 -0
  18. package/api/fansunited/football/players/index.js.map +1 -0
  19. package/api/fansunited/football/players/transformer.js +85 -0
  20. package/api/fansunited/football/players/transformer.js.map +1 -0
  21. package/api/fansunited/football/search/index.js +64 -0
  22. package/api/fansunited/football/search/index.js.map +1 -0
  23. package/api/fansunited/football/search/transformer.js +114 -0
  24. package/api/fansunited/football/search/transformer.js.map +1 -0
  25. package/api/fansunited/football/teams/index.js +66 -0
  26. package/api/fansunited/football/teams/index.js.map +1 -0
  27. package/api/fansunited/football/teams/transformer.js +53 -0
  28. package/api/fansunited/football/teams/transformer.js.map +1 -0
  29. package/api/fansunited/http.d.ts.map +1 -1
  30. package/api/fansunited/http.js +70 -0
  31. package/api/fansunited/http.js.map +1 -0
  32. package/api/fansunited/search/constants.js +5 -0
  33. package/api/fansunited/search/constants.js.map +1 -0
  34. package/api/fansunited/search/http.js +7 -0
  35. package/api/fansunited/search/http.js.map +1 -0
  36. package/api/fansunited/search/index.js +308 -0
  37. package/api/fansunited/search/index.js.map +1 -0
  38. package/api/fansunited/search/transformer.js +374 -0
  39. package/api/fansunited/search/transformer.js.map +1 -0
  40. package/api/fansunited-sdk/loyalty/matches.js +21 -0
  41. package/api/fansunited-sdk/loyalty/matches.js.map +1 -0
  42. package/api/fansunited-sdk/loyalty/template.js +52 -0
  43. package/api/fansunited-sdk/loyalty/template.js.map +1 -0
  44. package/api/fansunited-sdk/odds/matches.js +109 -0
  45. package/api/fansunited-sdk/odds/matches.js.map +1 -0
  46. package/api/sportal365-sports/basketball/games/index.js +32 -0
  47. package/api/sportal365-sports/basketball/games/index.js.map +1 -0
  48. package/api/sportal365-sports/basketball/games/transformers/game.js +137 -0
  49. package/api/sportal365-sports/basketball/games/transformers/game.js.map +1 -0
  50. package/api/sportal365-sports/basketball/http.js +7 -0
  51. package/api/sportal365-sports/basketball/http.js.map +1 -0
  52. package/api/sportal365-sports/constants.js +15 -0
  53. package/api/sportal365-sports/constants.js.map +1 -0
  54. package/api/sportal365-sports/football/competitions/index.js +52 -0
  55. package/api/sportal365-sports/football/competitions/index.js.map +1 -0
  56. package/api/sportal365-sports/football/competitions/utils.js +92 -0
  57. package/api/sportal365-sports/football/competitions/utils.js.map +1 -0
  58. package/api/sportal365-sports/football/http.js +7 -0
  59. package/api/sportal365-sports/football/http.js.map +1 -0
  60. package/api/sportal365-sports/football/matches/index.js +206 -0
  61. package/api/sportal365-sports/football/matches/index.js.map +1 -0
  62. package/api/sportal365-sports/football/matches/transformers/commentary.js +27 -0
  63. package/api/sportal365-sports/football/matches/transformers/commentary.js.map +1 -0
  64. package/api/sportal365-sports/football/matches/transformers/lineup.js +74 -0
  65. package/api/sportal365-sports/football/matches/transformers/lineup.js.map +1 -0
  66. package/api/sportal365-sports/football/matches/transformers/match-event.js +60 -0
  67. package/api/sportal365-sports/football/matches/transformers/match-event.js.map +1 -0
  68. package/api/sportal365-sports/football/matches/transformers/match.js +195 -0
  69. package/api/sportal365-sports/football/matches/transformers/match.js.map +1 -0
  70. package/api/sportal365-sports/football/matches/transformers/odds.js +11 -0
  71. package/api/sportal365-sports/football/matches/transformers/odds.js.map +1 -0
  72. package/api/sportal365-sports/football/matches/transformers/statistics.js +41 -0
  73. package/api/sportal365-sports/football/matches/transformers/statistics.js.map +1 -0
  74. package/api/sportal365-sports/football/search/index.js +42 -0
  75. package/api/sportal365-sports/football/search/index.js.map +1 -0
  76. package/api/sportal365-sports/football/search/search.transformer.js +166 -0
  77. package/api/sportal365-sports/football/search/search.transformer.js.map +1 -0
  78. package/api/sportal365-sports/football/standings/index.js +28 -0
  79. package/api/sportal365-sports/football/standings/index.js.map +1 -0
  80. package/api/sportal365-sports/football/standings/standing.transformer.js +107 -0
  81. package/api/sportal365-sports/football/standings/standing.transformer.js.map +1 -0
  82. package/api/sportal365-sports/football/statistics/index.js +154 -0
  83. package/api/sportal365-sports/football/statistics/index.js.map +1 -0
  84. package/api/sportal365-sports/football/statistics/player-career.transformer.js +77 -0
  85. package/api/sportal365-sports/football/statistics/player-career.transformer.js.map +1 -0
  86. package/api/sportal365-sports/football/statistics/player-recent.transformer.js +54 -0
  87. package/api/sportal365-sports/football/statistics/player-recent.transformer.js.map +1 -0
  88. package/api/sportal365-sports/football/statistics/player-season.transformer.js +147 -0
  89. package/api/sportal365-sports/football/statistics/player-season.transformer.js.map +1 -0
  90. package/api/sportal365-sports/football/teams/index.js +73 -0
  91. package/api/sportal365-sports/football/teams/index.js.map +1 -0
  92. package/api/sportal365-sports/football/teams/utils.js +196 -0
  93. package/api/sportal365-sports/football/teams/utils.js.map +1 -0
  94. package/api/sportal365-sports/http.js +58 -0
  95. package/api/sportal365-sports/http.js.map +1 -0
  96. package/api/sportal365-sports/search/http.js +7 -0
  97. package/api/sportal365-sports/search/http.js.map +1 -0
  98. package/api/sportal365-sports/search/index.js +61 -0
  99. package/api/sportal365-sports/search/index.js.map +1 -0
  100. package/api/sportal365-sports/search/search.transformer.js +254 -0
  101. package/api/sportal365-sports/search/search.transformer.js.map +1 -0
  102. package/api/sportal365-sports/shared/odds.transformer.js +65 -0
  103. package/api/sportal365-sports/shared/odds.transformer.js.map +1 -0
  104. package/api/sportal365-sports/shared/providerRef.helper.js +31 -0
  105. package/api/sportal365-sports/shared/providerRef.helper.js.map +1 -0
  106. package/api/sportal365-sports/standings/http.js +7 -0
  107. package/api/sportal365-sports/standings/http.js.map +1 -0
  108. package/api/sportal365-sports/standings/index.js +29 -0
  109. package/api/sportal365-sports/standings/index.js.map +1 -0
  110. package/api/sportal365-sports/standings/standing.transformer.js +185 -0
  111. package/api/sportal365-sports/standings/standing.transformer.js.map +1 -0
  112. package/api/sportal365-sports/statistics/http.js +7 -0
  113. package/api/sportal365-sports/statistics/http.js.map +1 -0
  114. package/api/sportal365-sports/statistics/index.js +25 -0
  115. package/api/sportal365-sports/statistics/index.js.map +1 -0
  116. package/api/sportal365-sports/statistics/team-stats.transformer.js +16 -0
  117. package/api/sportal365-sports/statistics/team-stats.transformer.js.map +1 -0
  118. package/api/sportal365-sports/tennis/http.js +7 -0
  119. package/api/sportal365-sports/tennis/http.js.map +1 -0
  120. package/api/sportal365-sports/tennis/matches/index.js +34 -0
  121. package/api/sportal365-sports/tennis/matches/index.js.map +1 -0
  122. package/api/sportal365-sports/tennis/matches/transformers/match.js +193 -0
  123. package/api/sportal365-sports/tennis/matches/transformers/match.js.map +1 -0
  124. package/cache/cache-manager.js +136 -0
  125. package/cache/cache-manager.js.map +1 -0
  126. package/cache/memory-store.js +29 -0
  127. package/cache/memory-store.js.map +1 -0
  128. package/client.js +38 -12337
  129. package/client.js.map +1 -0
  130. package/config/index.js +20 -0
  131. package/config/index.js.map +1 -0
  132. package/fansunited-data-layer.js +146 -3553
  133. package/fansunited-data-layer.js.map +1 -0
  134. package/helpers/competition.helpers.js +11 -0
  135. package/helpers/competition.helpers.js.map +1 -0
  136. package/helpers/player.helpers.js +165 -0
  137. package/helpers/player.helpers.js.map +1 -0
  138. package/helpers/player.hooks.js +34 -0
  139. package/helpers/player.hooks.js.map +1 -0
  140. package/helpers/team.helpers.js +216 -0
  141. package/helpers/team.helpers.js.map +1 -0
  142. package/package.json +6 -8
  143. package/providers/competition/hooks/useCompetitionStats.js +10 -0
  144. package/providers/competition/hooks/useCompetitionStats.js.map +1 -0
  145. package/providers/competition/hooks/useGoalDistribution.js +23 -0
  146. package/providers/competition/hooks/useGoalDistribution.js.map +1 -0
  147. package/providers/competition/hooks/useMatchHelpers.js +85 -0
  148. package/providers/competition/hooks/useMatchHelpers.js.map +1 -0
  149. package/providers/competition/hooks/useStandingsCalculations.js +89 -0
  150. package/providers/competition/hooks/useStandingsCalculations.js.map +1 -0
  151. package/providers/competition/hooks/useStandingsHelpers.js +61 -0
  152. package/providers/competition/hooks/useStandingsHelpers.js.map +1 -0
  153. package/providers/competition/hooks/useTeamPosition.js +15 -0
  154. package/providers/competition/hooks/useTeamPosition.js.map +1 -0
  155. package/providers/competition/hooks/useTeamResultsTable.js +86 -0
  156. package/providers/competition/hooks/useTeamResultsTable.js.map +1 -0
  157. package/providers/competition/utils/competitionStats.js +109 -0
  158. package/providers/competition/utils/competitionStats.js.map +1 -0
  159. package/providers/competition/utils/goalDistribution.js +64 -0
  160. package/providers/competition/utils/goalDistribution.js.map +1 -0
  161. package/providers/competition/utils/standingsCalculations.js +152 -0
  162. package/providers/competition/utils/standingsCalculations.js.map +1 -0
  163. package/providers/competition.context.js +14 -0
  164. package/providers/competition.context.js.map +1 -0
  165. package/providers/competition.provider.js +187 -0
  166. package/providers/competition.provider.js.map +1 -0
  167. package/providers/fansunited-config.context.js +6 -0
  168. package/providers/fansunited-config.context.js.map +1 -0
  169. package/providers/fansunited-config.hooks.js +20 -0
  170. package/providers/fansunited-config.hooks.js.map +1 -0
  171. package/providers/fansunited-config.provider.js +9 -0
  172. package/providers/fansunited-config.provider.js.map +1 -0
  173. package/providers/fansunited-sdk.hook.js +50 -0
  174. package/providers/fansunited-sdk.hook.js.map +1 -0
  175. package/providers/leaderboard/hooks/useMatchHelpers.js +30 -0
  176. package/providers/leaderboard/hooks/useMatchHelpers.js.map +1 -0
  177. package/providers/leaderboard/hooks/useTemplateHelpers.js +39 -0
  178. package/providers/leaderboard/hooks/useTemplateHelpers.js.map +1 -0
  179. package/providers/leaderboard.context.js +14 -0
  180. package/providers/leaderboard.context.js.map +1 -0
  181. package/providers/leaderboard.provider.js +98 -0
  182. package/providers/leaderboard.provider.js.map +1 -0
  183. package/providers/match/hooks/useBatchMatchOdds.js +72 -0
  184. package/providers/match/hooks/useBatchMatchOdds.js.map +1 -0
  185. package/providers/match/hooks/useEventHelpers.js +46 -0
  186. package/providers/match/hooks/useEventHelpers.js.map +1 -0
  187. package/providers/match/hooks/useHeadToHeadHelpers.js +52 -0
  188. package/providers/match/hooks/useHeadToHeadHelpers.js.map +1 -0
  189. package/providers/match/hooks/useLineupHelpers.js +53 -0
  190. package/providers/match/hooks/useLineupHelpers.js.map +1 -0
  191. package/providers/match/hooks/useMatchStandingsCalculations.js +332 -0
  192. package/providers/match/hooks/useMatchStandingsCalculations.js.map +1 -0
  193. package/providers/match/hooks/useMatchStatus.js +37 -0
  194. package/providers/match/hooks/useMatchStatus.js.map +1 -0
  195. package/providers/match/hooks/useOddsHelpers.js +54 -0
  196. package/providers/match/hooks/useOddsHelpers.js.map +1 -0
  197. package/providers/match/hooks/useScoreHelpers.js +34 -0
  198. package/providers/match/hooks/useScoreHelpers.js.map +1 -0
  199. package/providers/match/hooks/useStandingsHelpers.js +53 -0
  200. package/providers/match/hooks/useStandingsHelpers.js.map +1 -0
  201. package/providers/match/hooks/useStatisticsHelpers.js +40 -0
  202. package/providers/match/hooks/useStatisticsHelpers.js.map +1 -0
  203. package/providers/match/hooks/useTeamHelpers.js +38 -0
  204. package/providers/match/hooks/useTeamHelpers.js.map +1 -0
  205. package/providers/match/hooks/useTeamStatsHelpers.js +141 -0
  206. package/providers/match/hooks/useTeamStatsHelpers.js.map +1 -0
  207. package/providers/match.context.js +14 -0
  208. package/providers/match.context.js.map +1 -0
  209. package/providers/match.provider.js +176 -0
  210. package/providers/match.provider.js.map +1 -0
  211. package/providers/team/hooks/useFormStats.js +116 -0
  212. package/providers/team/hooks/useFormStats.js.map +1 -0
  213. package/providers/team/hooks/useMatchHelpers.js +46 -0
  214. package/providers/team/hooks/useMatchHelpers.js.map +1 -0
  215. package/providers/team/hooks/useSquadHelpers.js +67 -0
  216. package/providers/team/hooks/useSquadHelpers.js.map +1 -0
  217. package/providers/team.context.js +14 -0
  218. package/providers/team.context.js.map +1 -0
  219. package/providers/team.provider.js +98 -0
  220. package/providers/team.provider.js.map +1 -0
  221. package/utilities/stats/core/helpers.js +105 -0
  222. package/utilities/stats/core/helpers.js.map +1 -0
  223. package/utilities/stats/core/types.js +5 -0
  224. package/utilities/stats/core/types.js.map +1 -0
  225. package/utilities/stats/match/headToHead.js +83 -0
  226. package/utilities/stats/match/headToHead.js.map +1 -0
  227. package/utilities/stats/match/homeVsAway.js +140 -0
  228. package/utilities/stats/match/homeVsAway.js.map +1 -0
  229. package/utilities/stats/match/overUnder.js +91 -0
  230. package/utilities/stats/match/overUnder.js.map +1 -0
  231. package/utilities/stats/match/result.js +21 -0
  232. package/utilities/stats/match/result.js.map +1 -0
  233. package/utilities/stats/team/commonOpponents.js +77 -0
  234. package/utilities/stats/team/commonOpponents.js.map +1 -0
  235. package/utilities/stats/team/goalStats.js +50 -0
  236. package/utilities/stats/team/goalStats.js.map +1 -0
  237. package/utilities/stats/team/streaks.js +183 -0
  238. package/utilities/stats/team/streaks.js.map +1 -0
  239. package/client.cjs +0 -8
  240. package/fansunited-data-layer.cjs +0 -1
  241. package/matches-BvHU-bP0.js +0 -1795
  242. package/matches-Ci0Ci_Mw.cjs +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGoalDistribution.js","sources":["../../../../src/lib/providers/competition/hooks/useGoalDistribution.ts"],"sourcesContent":["/**\n * Goal distribution hook\n * Provides memoized goal distribution calculations\n */\n\nimport { useMemo } from \"react\";\nimport type { FUSportsMatch } from \"../../../types/canonical\";\nimport { calculateGoalDistribution } from \"../utils/goalDistribution\";\nimport type { GoalDistribution } from \"../utils/goalDistribution\";\n\nexport function useGoalDistribution(matches: FUSportsMatch[]) {\n // Filter only finished matches\n const finishedMatches = useMemo(() => matches.filter((m) => m.status.type === \"FINISHED\"), [matches]);\n\n // Calculate goal distribution with 15-minute intervals\n const goalDistribution = useMemo(() => calculateGoalDistribution(finishedMatches, 15), [finishedMatches]);\n\n // Return a function that allows custom interval sizes\n const getGoalDistribution = useMemo(\n () =>\n (intervalMinutes: number = 15): GoalDistribution => {\n return calculateGoalDistribution(finishedMatches, intervalMinutes);\n },\n [finishedMatches]\n );\n\n return useMemo(\n () => ({\n goalDistribution,\n getGoalDistribution,\n }),\n [goalDistribution, getGoalDistribution]\n );\n}\n"],"names":[],"mappings":";;AAUO,SAAS,oBAAoB,SAA0B;AAE1D,QAAM,kBAAkB,QAAQ,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,SAAS,UAAU,GAAG,CAAC,OAAO,CAAC;AAGpG,QAAM,mBAAmB,QAAQ,MAAM,0BAA0B,iBAAiB,EAAE,GAAG,CAAC,eAAe,CAAC;AAGxG,QAAM,sBAAsB;AAAA,IACxB,MACI,CAAC,kBAA0B,OAAyB;AAChD,aAAO,0BAA0B,iBAAiB,eAAe;AAAA,IACrE;AAAA,IACJ,CAAC,eAAe;AAAA,EAAA;AAGpB,SAAO;AAAA,IACH,OAAO;AAAA,MACH;AAAA,MACA;AAAA,IAAA;AAAA,IAEJ,CAAC,kBAAkB,mBAAmB;AAAA,EAAA;AAE9C;"}
@@ -0,0 +1,85 @@
1
+ import { useCallback, useMemo } from "react";
2
+ function useMatchHelpers(matches) {
3
+ const getMatch = useCallback((id) => matches.find((m) => m.id === id), [matches]);
4
+ const getMatchesByTeam = useCallback(
5
+ (teamId) => matches.filter((m) => m.competitorOne.id === teamId || m.competitorTwo.id === teamId),
6
+ [matches]
7
+ );
8
+ const getMatchesByDate = useCallback(
9
+ (date) => matches.filter((m) => m.timing.kickoffTime.toISOString().startsWith(date)),
10
+ [matches]
11
+ );
12
+ const getMatchesByRound = useCallback(
13
+ (roundId) => matches.filter((m) => m.round?.id === roundId),
14
+ [matches]
15
+ );
16
+ const getLiveMatches = useCallback(() => matches.filter((m) => m.status.code === "live"), [matches]);
17
+ const getUpcomingMatches = useCallback(
18
+ (teamId) => {
19
+ const now = /* @__PURE__ */ new Date();
20
+ let filtered = matches.filter((m) => m.status.code === "not_started" && m.timing.kickoffTime > now);
21
+ if (teamId) {
22
+ filtered = filtered.filter((m) => m.competitorOne.id === teamId || m.competitorTwo.id === teamId);
23
+ }
24
+ return filtered.sort((a, b) => a.timing.kickoffTime.getTime() - b.timing.kickoffTime.getTime());
25
+ },
26
+ [matches]
27
+ );
28
+ const getFinishedMatches = useCallback(
29
+ (teamId) => {
30
+ let filtered = matches.filter((m) => m.status.type === "FINISHED");
31
+ if (teamId) {
32
+ filtered = filtered.filter((m) => m.competitorOne.id === teamId || m.competitorTwo.id === teamId);
33
+ }
34
+ return filtered.sort((a, b) => b.timing.kickoffTime.getTime() - a.timing.kickoffTime.getTime());
35
+ },
36
+ [matches]
37
+ );
38
+ const getTeamLastMatch = useCallback(
39
+ (teamId) => {
40
+ const finishedMatches = matches.filter(
41
+ (m) => m.status.type === "FINISHED" && (m.competitorOne.id === teamId || m.competitorTwo.id === teamId)
42
+ ).sort((a, b) => b.timing.kickoffTime.getTime() - a.timing.kickoffTime.getTime());
43
+ return finishedMatches[0];
44
+ },
45
+ [matches]
46
+ );
47
+ const getTeamNextMatch = useCallback(
48
+ (teamId) => {
49
+ const now = /* @__PURE__ */ new Date();
50
+ const upcomingMatches = matches.filter(
51
+ (m) => m.status.code === "not_started" && m.timing.kickoffTime > now && (m.competitorOne.id === teamId || m.competitorTwo.id === teamId)
52
+ ).sort((a, b) => a.timing.kickoffTime.getTime() - b.timing.kickoffTime.getTime());
53
+ return upcomingMatches[0];
54
+ },
55
+ [matches]
56
+ );
57
+ return useMemo(
58
+ () => ({
59
+ getMatch,
60
+ getMatchesByTeam,
61
+ getMatchesByDate,
62
+ getMatchesByRound,
63
+ getLiveMatches,
64
+ getUpcomingMatches,
65
+ getFinishedMatches,
66
+ getTeamLastMatch,
67
+ getTeamNextMatch
68
+ }),
69
+ [
70
+ getMatch,
71
+ getMatchesByTeam,
72
+ getMatchesByDate,
73
+ getMatchesByRound,
74
+ getLiveMatches,
75
+ getUpcomingMatches,
76
+ getFinishedMatches,
77
+ getTeamLastMatch,
78
+ getTeamNextMatch
79
+ ]
80
+ );
81
+ }
82
+ export {
83
+ useMatchHelpers
84
+ };
85
+ //# sourceMappingURL=useMatchHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMatchHelpers.js","sources":["../../../../src/lib/providers/competition/hooks/useMatchHelpers.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport type { FUSportsMatch } from \"../../../types/canonical\";\n\n/**\n * Hook for match-related helper functions\n */\nexport function useMatchHelpers(matches: FUSportsMatch[]) {\n const getMatch = useCallback((id: string) => matches.find((m) => m.id === id), [matches]);\n\n const getMatchesByTeam = useCallback(\n (teamId: string) => matches.filter((m) => m.competitorOne.id === teamId || m.competitorTwo.id === teamId),\n [matches]\n );\n\n const getMatchesByDate = useCallback(\n (date: string) => matches.filter((m) => m.timing.kickoffTime.toISOString().startsWith(date)),\n [matches]\n );\n\n const getMatchesByRound = useCallback(\n (roundId: string) => matches.filter((m) => m.round?.id === roundId),\n [matches]\n );\n\n const getLiveMatches = useCallback(() => matches.filter((m) => m.status.code === \"live\"), [matches]);\n\n const getUpcomingMatches = useCallback(\n (teamId?: string) => {\n const now = new Date();\n let filtered = matches.filter((m) => m.status.code === \"not_started\" && m.timing.kickoffTime > now);\n\n if (teamId) {\n filtered = filtered.filter((m) => m.competitorOne.id === teamId || m.competitorTwo.id === teamId);\n }\n\n return filtered.sort((a, b) => a.timing.kickoffTime.getTime() - b.timing.kickoffTime.getTime());\n },\n [matches]\n );\n\n const getFinishedMatches = useCallback(\n (teamId?: string) => {\n let filtered = matches.filter((m) => m.status.type === \"FINISHED\");\n\n if (teamId) {\n filtered = filtered.filter((m) => m.competitorOne.id === teamId || m.competitorTwo.id === teamId);\n }\n\n return filtered.sort((a, b) => b.timing.kickoffTime.getTime() - a.timing.kickoffTime.getTime());\n },\n [matches]\n );\n\n const getTeamLastMatch = useCallback(\n (teamId: string) => {\n const finishedMatches = matches\n .filter(\n (m) =>\n m.status.type === \"FINISHED\" && (m.competitorOne.id === teamId || m.competitorTwo.id === teamId)\n )\n .sort((a, b) => b.timing.kickoffTime.getTime() - a.timing.kickoffTime.getTime());\n\n return finishedMatches[0];\n },\n [matches]\n );\n\n const getTeamNextMatch = useCallback(\n (teamId: string) => {\n const now = new Date();\n const upcomingMatches = matches\n .filter(\n (m) =>\n m.status.code === \"not_started\" &&\n m.timing.kickoffTime > now &&\n (m.competitorOne.id === teamId || m.competitorTwo.id === teamId)\n )\n .sort((a, b) => a.timing.kickoffTime.getTime() - b.timing.kickoffTime.getTime());\n\n return upcomingMatches[0];\n },\n [matches]\n );\n\n return useMemo(\n () => ({\n getMatch,\n getMatchesByTeam,\n getMatchesByDate,\n getMatchesByRound,\n getLiveMatches,\n getUpcomingMatches,\n getFinishedMatches,\n getTeamLastMatch,\n getTeamNextMatch,\n }),\n [\n getMatch,\n getMatchesByTeam,\n getMatchesByDate,\n getMatchesByRound,\n getLiveMatches,\n getUpcomingMatches,\n getFinishedMatches,\n getTeamLastMatch,\n getTeamNextMatch,\n ]\n );\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ import { useMemo } from "react";
2
+ import { calculateFormStandings, calculateOffenceStandings, calculateDefenceStandings, isAwayMatch, isHomeMatch } from "../utils/standingsCalculations.js";
3
+ function useStandingsCalculations(matches, standings, getTeamStanding) {
4
+ const finishedMatches = useMemo(
5
+ () => matches.filter((m) => m.status.type === "FINISHED").sort((a, b) => b.timing.kickoffTime.getTime() - a.timing.kickoffTime.getTime()),
6
+ [matches]
7
+ );
8
+ const teamIds = useMemo(() => new Set(standings.entries.map((entry) => entry.competitor.id)), [standings.entries]);
9
+ const getTeamInfo = useMemo(() => (teamId) => getTeamStanding(teamId)?.competitor, [getTeamStanding]);
10
+ const getFormStandings = useMemo(
11
+ () => (matchCount = 8) => calculateFormStandings(finishedMatches, teamIds, getTeamInfo, matchCount),
12
+ [finishedMatches, teamIds, getTeamInfo]
13
+ );
14
+ const getHomeFormStandings = useMemo(
15
+ () => (matchCount = 8) => calculateFormStandings(finishedMatches, teamIds, getTeamInfo, matchCount, isHomeMatch),
16
+ [finishedMatches, teamIds, getTeamInfo]
17
+ );
18
+ const getAwayFormStandings = useMemo(
19
+ () => (matchCount = 8) => calculateFormStandings(finishedMatches, teamIds, getTeamInfo, matchCount, isAwayMatch),
20
+ [finishedMatches, teamIds, getTeamInfo]
21
+ );
22
+ const getOffenceStandings = useMemo(
23
+ () => () => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo),
24
+ [finishedMatches, teamIds, getTeamInfo]
25
+ );
26
+ const getFormOffenceStandings = useMemo(
27
+ () => (matchCount = 8) => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo, matchCount),
28
+ [finishedMatches, teamIds, getTeamInfo]
29
+ );
30
+ const getHomeOffenceStandings = useMemo(
31
+ () => () => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo, void 0, isHomeMatch),
32
+ [finishedMatches, teamIds, getTeamInfo]
33
+ );
34
+ const getAwayOffenceStandings = useMemo(
35
+ () => () => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo, void 0, isAwayMatch),
36
+ [finishedMatches, teamIds, getTeamInfo]
37
+ );
38
+ const getDefenceStandings = useMemo(
39
+ () => () => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo),
40
+ [finishedMatches, teamIds, getTeamInfo]
41
+ );
42
+ const getFormDefenceStandings = useMemo(
43
+ () => (matchCount = 8) => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo, matchCount),
44
+ [finishedMatches, teamIds, getTeamInfo]
45
+ );
46
+ const getHomeDefenceStandings = useMemo(
47
+ () => () => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo, void 0, isHomeMatch),
48
+ [finishedMatches, teamIds, getTeamInfo]
49
+ );
50
+ const getAwayDefenceStandings = useMemo(
51
+ () => () => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo, void 0, isAwayMatch),
52
+ [finishedMatches, teamIds, getTeamInfo]
53
+ );
54
+ return useMemo(
55
+ () => ({
56
+ // Form-based standings
57
+ getFormStandings,
58
+ getHomeFormStandings,
59
+ getAwayFormStandings,
60
+ // Offence standings
61
+ getOffenceStandings,
62
+ getFormOffenceStandings,
63
+ getHomeOffenceStandings,
64
+ getAwayOffenceStandings,
65
+ // Defence standings
66
+ getDefenceStandings,
67
+ getFormDefenceStandings,
68
+ getHomeDefenceStandings,
69
+ getAwayDefenceStandings
70
+ }),
71
+ [
72
+ getFormStandings,
73
+ getHomeFormStandings,
74
+ getAwayFormStandings,
75
+ getOffenceStandings,
76
+ getFormOffenceStandings,
77
+ getHomeOffenceStandings,
78
+ getAwayOffenceStandings,
79
+ getDefenceStandings,
80
+ getFormDefenceStandings,
81
+ getHomeDefenceStandings,
82
+ getAwayDefenceStandings
83
+ ]
84
+ );
85
+ }
86
+ export {
87
+ useStandingsCalculations
88
+ };
89
+ //# sourceMappingURL=useStandingsCalculations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStandingsCalculations.js","sources":["../../../../src/lib/providers/competition/hooks/useStandingsCalculations.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport type { FUSportsMatch, FUSportsStandingEntry, FUSportsStandings } from \"../../../types/canonical\";\nimport {\n calculateFormStandings,\n calculateOffenceStandings,\n calculateDefenceStandings,\n isHomeMatch,\n isAwayMatch,\n} from \"../utils/standingsCalculations\";\n\n/**\n * Hook for advanced standings calculations with memoization\n */\nexport function useStandingsCalculations(\n matches: FUSportsMatch[],\n standings: FUSportsStandings,\n getTeamStanding: (teamId: string) => FUSportsStandingEntry | undefined\n) {\n // Get finished matches sorted by date (most recent first)\n const finishedMatches = useMemo(\n () =>\n matches\n .filter((m) => m.status.type === \"FINISHED\")\n .sort((a, b) => b.timing.kickoffTime.getTime() - a.timing.kickoffTime.getTime()),\n [matches]\n );\n\n // Get all team IDs\n const teamIds = useMemo(() => new Set(standings.entries.map((entry) => entry.competitor.id)), [standings.entries]);\n\n // Helper to get team info\n const getTeamInfo = useMemo(() => (teamId: string) => getTeamStanding(teamId)?.competitor, [getTeamStanding]);\n\n // Form-based standings (points)\n const getFormStandings = useMemo(\n () =>\n (matchCount: number = 8) =>\n calculateFormStandings(finishedMatches, teamIds, getTeamInfo, matchCount),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getHomeFormStandings = useMemo(\n () =>\n (matchCount: number = 8) =>\n calculateFormStandings(finishedMatches, teamIds, getTeamInfo, matchCount, isHomeMatch),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getAwayFormStandings = useMemo(\n () =>\n (matchCount: number = 8) =>\n calculateFormStandings(finishedMatches, teamIds, getTeamInfo, matchCount, isAwayMatch),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n // Offence standings (goals scored)\n const getOffenceStandings = useMemo(\n () => () => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getFormOffenceStandings = useMemo(\n () =>\n (matchCount: number = 8) =>\n calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo, matchCount),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getHomeOffenceStandings = useMemo(\n () => () => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo, undefined, isHomeMatch),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getAwayOffenceStandings = useMemo(\n () => () => calculateOffenceStandings(finishedMatches, teamIds, getTeamInfo, undefined, isAwayMatch),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n // Defence standings (goals conceded)\n const getDefenceStandings = useMemo(\n () => () => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getFormDefenceStandings = useMemo(\n () =>\n (matchCount: number = 8) =>\n calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo, matchCount),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getHomeDefenceStandings = useMemo(\n () => () => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo, undefined, isHomeMatch),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n const getAwayDefenceStandings = useMemo(\n () => () => calculateDefenceStandings(finishedMatches, teamIds, getTeamInfo, undefined, isAwayMatch),\n [finishedMatches, teamIds, getTeamInfo]\n );\n\n return useMemo(\n () => ({\n // Form-based standings\n getFormStandings,\n getHomeFormStandings,\n getAwayFormStandings,\n\n // Offence standings\n getOffenceStandings,\n getFormOffenceStandings,\n getHomeOffenceStandings,\n getAwayOffenceStandings,\n\n // Defence standings\n getDefenceStandings,\n getFormDefenceStandings,\n getHomeDefenceStandings,\n getAwayDefenceStandings,\n }),\n [\n getFormStandings,\n getHomeFormStandings,\n getAwayFormStandings,\n getOffenceStandings,\n getFormOffenceStandings,\n getHomeOffenceStandings,\n getAwayOffenceStandings,\n getDefenceStandings,\n getFormDefenceStandings,\n getHomeDefenceStandings,\n getAwayDefenceStandings,\n ]\n );\n}\n"],"names":[],"mappings":";;AAaO,SAAS,yBACZ,SACA,WACA,iBACF;AAEE,QAAM,kBAAkB;AAAA,IACpB,MACI,QACK,OAAO,CAAC,MAAM,EAAE,OAAO,SAAS,UAAU,EAC1C,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,YAAY,YAAY,EAAE,OAAO,YAAY,SAAS;AAAA,IACvF,CAAC,OAAO;AAAA,EAAA;AAIZ,QAAM,UAAU,QAAQ,MAAM,IAAI,IAAI,UAAU,QAAQ,IAAI,CAAC,UAAU,MAAM,WAAW,EAAE,CAAC,GAAG,CAAC,UAAU,OAAO,CAAC;AAGjH,QAAM,cAAc,QAAQ,MAAM,CAAC,WAAmB,gBAAgB,MAAM,GAAG,YAAY,CAAC,eAAe,CAAC;AAG5G,QAAM,mBAAmB;AAAA,IACrB,MACI,CAAC,aAAqB,MAClB,uBAAuB,iBAAiB,SAAS,aAAa,UAAU;AAAA,IAChF,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,uBAAuB;AAAA,IACzB,MACI,CAAC,aAAqB,MAClB,uBAAuB,iBAAiB,SAAS,aAAa,YAAY,WAAW;AAAA,IAC7F,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,uBAAuB;AAAA,IACzB,MACI,CAAC,aAAqB,MAClB,uBAAuB,iBAAiB,SAAS,aAAa,YAAY,WAAW;AAAA,IAC7F,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAI1C,QAAM,sBAAsB;AAAA,IACxB,MAAM,MAAM,0BAA0B,iBAAiB,SAAS,WAAW;AAAA,IAC3E,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,0BAA0B;AAAA,IAC5B,MACI,CAAC,aAAqB,MAClB,0BAA0B,iBAAiB,SAAS,aAAa,UAAU;AAAA,IACnF,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,0BAA0B;AAAA,IAC5B,MAAM,MAAM,0BAA0B,iBAAiB,SAAS,aAAa,QAAW,WAAW;AAAA,IACnG,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,0BAA0B;AAAA,IAC5B,MAAM,MAAM,0BAA0B,iBAAiB,SAAS,aAAa,QAAW,WAAW;AAAA,IACnG,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAI1C,QAAM,sBAAsB;AAAA,IACxB,MAAM,MAAM,0BAA0B,iBAAiB,SAAS,WAAW;AAAA,IAC3E,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,0BAA0B;AAAA,IAC5B,MACI,CAAC,aAAqB,MAClB,0BAA0B,iBAAiB,SAAS,aAAa,UAAU;AAAA,IACnF,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,0BAA0B;AAAA,IAC5B,MAAM,MAAM,0BAA0B,iBAAiB,SAAS,aAAa,QAAW,WAAW;AAAA,IACnG,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,QAAM,0BAA0B;AAAA,IAC5B,MAAM,MAAM,0BAA0B,iBAAiB,SAAS,aAAa,QAAW,WAAW;AAAA,IACnG,CAAC,iBAAiB,SAAS,WAAW;AAAA,EAAA;AAG1C,SAAO;AAAA,IACH,OAAO;AAAA;AAAA,MAEH;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAGA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAGA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEJ;AAAA,MACI;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,EACJ;AAER;"}
@@ -0,0 +1,61 @@
1
+ import { useMemo, useCallback } from "react";
2
+ function useStandingsHelpers(standings) {
3
+ const entries = useMemo(() => standings.entries, [standings.entries]);
4
+ const getTeamStanding = useCallback(
5
+ (teamId) => entries.find((entry) => entry.competitor.id === teamId),
6
+ [entries]
7
+ );
8
+ const getTeamRank = useCallback(
9
+ (teamId) => {
10
+ const standing = getTeamStanding(teamId);
11
+ return standing?.rank;
12
+ },
13
+ [getTeamStanding]
14
+ );
15
+ const getTopTeams = useCallback((count = 5) => standings.entries.slice(0, count), [standings.entries]);
16
+ const getBottomTeams = useCallback((count = 5) => standings.entries.slice(-count), [standings.entries]);
17
+ const getTeamsAlphabetically = useMemo(() => {
18
+ return standings.entries.map(
19
+ (entry) => ({
20
+ id: entry.competitor.id,
21
+ name: entry.competitor.name,
22
+ shortName: entry.competitor.shortName,
23
+ logo: entry.competitor.logo,
24
+ type: "club"
25
+ })
26
+ ).sort((a, b) => a.name.localeCompare(b.name));
27
+ }, [standings.entries]);
28
+ const getTeamSeasonStats = useMemo(
29
+ () => (teamId) => {
30
+ const standing = getTeamStanding(teamId);
31
+ if (!standing) return void 0;
32
+ return {
33
+ position: standing.rank,
34
+ points: standing.stats.points ?? 0,
35
+ played: standing.stats.played ?? 0,
36
+ won: standing.stats.won ?? 0,
37
+ drawn: standing.stats.drawn ?? 0,
38
+ lost: standing.stats.lost ?? 0,
39
+ goalsFor: standing.stats.goalsFor ?? 0,
40
+ goalsAgainst: standing.stats.goalsAgainst ?? 0,
41
+ goalDifference: standing.stats.goalDifference ?? 0
42
+ };
43
+ },
44
+ [getTeamStanding]
45
+ );
46
+ return useMemo(
47
+ () => ({
48
+ getTeamStanding,
49
+ getTeamRank,
50
+ getTopTeams,
51
+ getBottomTeams,
52
+ getTeamsAlphabetically,
53
+ getTeamSeasonStats
54
+ }),
55
+ [getTeamStanding, getTeamRank, getTopTeams, getBottomTeams, getTeamsAlphabetically, getTeamSeasonStats]
56
+ );
57
+ }
58
+ export {
59
+ useStandingsHelpers
60
+ };
61
+ //# sourceMappingURL=useStandingsHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStandingsHelpers.js","sources":["../../../../src/lib/providers/competition/hooks/useStandingsHelpers.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport type { FUSportsStandings, FUSportsCompetitor } from \"../../../types/canonical\";\n\n/**\n * Hook for basic standings helper functions\n */\nexport function useStandingsHelpers(standings: FUSportsStandings) {\n // Memoize the entries array to prevent unnecessary recalculations\n const entries = useMemo(() => standings.entries, [standings.entries]);\n\n const getTeamStanding = useCallback(\n (teamId: string) => entries.find((entry) => entry.competitor.id === teamId),\n [entries]\n );\n\n const getTeamRank = useCallback(\n (teamId: string) => {\n const standing = getTeamStanding(teamId);\n return standing?.rank;\n },\n [getTeamStanding]\n );\n\n const getTopTeams = useCallback((count: number = 5) => standings.entries.slice(0, count), [standings.entries]);\n\n const getBottomTeams = useCallback((count: number = 5) => standings.entries.slice(-count), [standings.entries]);\n\n /**\n * Get all teams from standings ordered alphabetically by name\n */\n const getTeamsAlphabetically = useMemo(() => {\n return standings.entries\n .map(\n (entry) =>\n ({\n id: entry.competitor.id,\n name: entry.competitor.name,\n shortName: entry.competitor.shortName,\n logo: entry.competitor.logo,\n type: \"club\" as const,\n }) as FUSportsCompetitor\n )\n .sort((a, b) => a.name.localeCompare(b.name));\n }, [standings.entries]);\n\n /**\n * Get team's season statistics from standings\n * Returns formatted stats including position, points, matches played, wins, losses, draws, goals\n */\n const getTeamSeasonStats = useMemo(\n () => (teamId: string) => {\n const standing = getTeamStanding(teamId);\n if (!standing) return undefined;\n\n return {\n position: standing.rank,\n points: standing.stats.points ?? 0,\n played: standing.stats.played ?? 0,\n won: standing.stats.won ?? 0,\n drawn: standing.stats.drawn ?? 0,\n lost: standing.stats.lost ?? 0,\n goalsFor: standing.stats.goalsFor ?? 0,\n goalsAgainst: standing.stats.goalsAgainst ?? 0,\n goalDifference: standing.stats.goalDifference ?? 0,\n };\n },\n [getTeamStanding]\n );\n\n return useMemo(\n () => ({\n getTeamStanding,\n getTeamRank,\n getTopTeams,\n getBottomTeams,\n getTeamsAlphabetically,\n getTeamSeasonStats,\n }),\n [getTeamStanding, getTeamRank, getTopTeams, getBottomTeams, getTeamsAlphabetically, getTeamSeasonStats]\n );\n}\n"],"names":[],"mappings":";AAMO,SAAS,oBAAoB,WAA8B;AAE9D,QAAM,UAAU,QAAQ,MAAM,UAAU,SAAS,CAAC,UAAU,OAAO,CAAC;AAEpE,QAAM,kBAAkB;AAAA,IACpB,CAAC,WAAmB,QAAQ,KAAK,CAAC,UAAU,MAAM,WAAW,OAAO,MAAM;AAAA,IAC1E,CAAC,OAAO;AAAA,EAAA;AAGZ,QAAM,cAAc;AAAA,IAChB,CAAC,WAAmB;AAChB,YAAM,WAAW,gBAAgB,MAAM;AACvC,aAAO,UAAU;AAAA,IACrB;AAAA,IACA,CAAC,eAAe;AAAA,EAAA;AAGpB,QAAM,cAAc,YAAY,CAAC,QAAgB,MAAM,UAAU,QAAQ,MAAM,GAAG,KAAK,GAAG,CAAC,UAAU,OAAO,CAAC;AAE7G,QAAM,iBAAiB,YAAY,CAAC,QAAgB,MAAM,UAAU,QAAQ,MAAM,CAAC,KAAK,GAAG,CAAC,UAAU,OAAO,CAAC;AAK9G,QAAM,yBAAyB,QAAQ,MAAM;AACzC,WAAO,UAAU,QACZ;AAAA,MACG,CAAC,WACI;AAAA,QACG,IAAI,MAAM,WAAW;AAAA,QACrB,MAAM,MAAM,WAAW;AAAA,QACvB,WAAW,MAAM,WAAW;AAAA,QAC5B,MAAM,MAAM,WAAW;AAAA,QACvB,MAAM;AAAA,MAAA;AAAA,IACV,EAEP,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAAA,EACpD,GAAG,CAAC,UAAU,OAAO,CAAC;AAMtB,QAAM,qBAAqB;AAAA,IACvB,MAAM,CAAC,WAAmB;AACtB,YAAM,WAAW,gBAAgB,MAAM;AACvC,UAAI,CAAC,SAAU,QAAO;AAEtB,aAAO;AAAA,QACH,UAAU,SAAS;AAAA,QACnB,QAAQ,SAAS,MAAM,UAAU;AAAA,QACjC,QAAQ,SAAS,MAAM,UAAU;AAAA,QACjC,KAAK,SAAS,MAAM,OAAO;AAAA,QAC3B,OAAO,SAAS,MAAM,SAAS;AAAA,QAC/B,MAAM,SAAS,MAAM,QAAQ;AAAA,QAC7B,UAAU,SAAS,MAAM,YAAY;AAAA,QACrC,cAAc,SAAS,MAAM,gBAAgB;AAAA,QAC7C,gBAAgB,SAAS,MAAM,kBAAkB;AAAA,MAAA;AAAA,IAEzD;AAAA,IACA,CAAC,eAAe;AAAA,EAAA;AAGpB,SAAO;AAAA,IACH,OAAO;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IAAA;AAAA,IAEJ,CAAC,iBAAiB,aAAa,aAAa,gBAAgB,wBAAwB,kBAAkB;AAAA,EAAA;AAE9G;"}
@@ -0,0 +1,15 @@
1
+ import { useCallback } from "react";
2
+ function useTeamPosition(standings) {
3
+ const getTeamPosition = useCallback(
4
+ (teamId) => {
5
+ const entry = standings.entries.find((entry2) => entry2.competitor.id === teamId);
6
+ return entry?.rank;
7
+ },
8
+ [standings]
9
+ );
10
+ return { getTeamPosition };
11
+ }
12
+ export {
13
+ useTeamPosition
14
+ };
15
+ //# sourceMappingURL=useTeamPosition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTeamPosition.js","sources":["../../../../src/lib/providers/competition/hooks/useTeamPosition.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { FUSportsStandings } from \"../../../types/canonical\";\n\n/**\n * Hook to get a team's position in the standings\n */\nexport function useTeamPosition(standings: FUSportsStandings) {\n const getTeamPosition = useCallback(\n (teamId: string): number | undefined => {\n const entry = standings.entries.find((entry) => entry.competitor.id === teamId);\n return entry?.rank;\n },\n [standings]\n );\n\n return { getTeamPosition };\n}\n\n"],"names":["entry"],"mappings":";AAMO,SAAS,gBAAgB,WAA8B;AAC1D,QAAM,kBAAkB;AAAA,IACpB,CAAC,WAAuC;AACpC,YAAM,QAAQ,UAAU,QAAQ,KAAK,CAACA,WAAUA,OAAM,WAAW,OAAO,MAAM;AAC9E,aAAO,OAAO;AAAA,IAClB;AAAA,IACA,CAAC,SAAS;AAAA,EAAA;AAGd,SAAO,EAAE,gBAAA;AACb;"}
@@ -0,0 +1,86 @@
1
+ import { useCallback } from "react";
2
+ function useTeamResultsTable(matches, standings) {
3
+ const getTeamResultsTable = useCallback(
4
+ (teamId) => {
5
+ const finishedMatches = matches.filter((match) => match.status.type === "FINISHED");
6
+ const upcomingMatches = matches.filter(
7
+ (match) => match.status.type === "NOT_STARTED" || match.status.type === "POSTPONED"
8
+ );
9
+ const rows = standings.entries.map((entry) => {
10
+ const opponentId = entry.competitor.id;
11
+ const finishedMatchesBetween = finishedMatches.filter(
12
+ (match) => match.competitorOne.id === teamId && match.competitorTwo.id === opponentId || match.competitorOne.id === opponentId && match.competitorTwo.id === teamId
13
+ );
14
+ const upcomingMatchesBetween = upcomingMatches.filter(
15
+ (match) => match.competitorOne.id === teamId && match.competitorTwo.id === opponentId || match.competitorOne.id === opponentId && match.competitorTwo.id === teamId
16
+ );
17
+ let homeResult;
18
+ let awayResult;
19
+ finishedMatchesBetween.forEach((match) => {
20
+ const isTeamHome = match.competitorOne.id === teamId;
21
+ const homeScore = parseInt(match.score?.competitorOne ?? "0");
22
+ const awayScore = parseInt(match.score?.competitorTwo ?? "0");
23
+ const teamScore = isTeamHome ? homeScore : awayScore;
24
+ const opponentScore = isTeamHome ? awayScore : homeScore;
25
+ let result;
26
+ if (teamScore > opponentScore) {
27
+ result = "W";
28
+ } else if (teamScore === opponentScore) {
29
+ result = "D";
30
+ } else {
31
+ result = "L";
32
+ }
33
+ const resultData = {
34
+ result,
35
+ score: `${homeScore} - ${awayScore}`,
36
+ date: match.timing.kickoffTime,
37
+ matchId: match.id,
38
+ isUpcoming: false
39
+ };
40
+ if (isTeamHome) {
41
+ homeResult = resultData;
42
+ } else {
43
+ awayResult = resultData;
44
+ }
45
+ });
46
+ upcomingMatchesBetween.forEach((match) => {
47
+ const isTeamHome = match.competitorOne.id === teamId;
48
+ if (isTeamHome && !homeResult) {
49
+ homeResult = {
50
+ date: match.timing.kickoffTime,
51
+ matchId: match.id,
52
+ isUpcoming: true
53
+ };
54
+ } else if (!isTeamHome && !awayResult) {
55
+ awayResult = {
56
+ date: match.timing.kickoffTime,
57
+ matchId: match.id,
58
+ isUpcoming: true
59
+ };
60
+ }
61
+ });
62
+ return {
63
+ rank: entry.rank,
64
+ teamId: entry.competitor.id,
65
+ teamName: entry.competitor.name,
66
+ teamShortName: entry.competitor.shortName,
67
+ teamLogo: entry.competitor.logo,
68
+ gamesPlayed: entry.stats.played ?? 0,
69
+ points: entry.stats.points ?? 0,
70
+ homeResult,
71
+ awayResult
72
+ };
73
+ });
74
+ return {
75
+ teamId,
76
+ rows
77
+ };
78
+ },
79
+ [matches, standings.entries]
80
+ );
81
+ return { getTeamResultsTable };
82
+ }
83
+ export {
84
+ useTeamResultsTable
85
+ };
86
+ //# sourceMappingURL=useTeamResultsTable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTeamResultsTable.js","sources":["../../../../src/lib/providers/competition/hooks/useTeamResultsTable.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { FUSportsMatch, FUSportsStandings } from \"../../../types/canonical\";\nimport type { TeamResultTable, TeamResultTableRow, TeamResultTableResult } from \"../../../helpers/team.helpers\";\n\n/**\n * Hook for team results table helper function\n */\nexport function useTeamResultsTable(matches: FUSportsMatch[], standings: FUSportsStandings) {\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 const getTeamResultsTable = useCallback(\n (teamId: string): 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 [matches, standings.entries]\n );\n\n return { getTeamResultsTable };\n}\n"],"names":[],"mappings":";AAOO,SAAS,oBAAoB,SAA0B,WAA8B;AAUxF,QAAM,sBAAsB;AAAA,IACxB,CAAC,WAAoC;AAEjC,YAAM,kBAAkB,QAAQ,OAAO,CAAC,UAAU,MAAM,OAAO,SAAS,UAAU;AAClF,YAAM,kBAAkB,QAAQ;AAAA,QAC5B,CAAC,UAAU,MAAM,OAAO,SAAS,iBAAiB,MAAM,OAAO,SAAS;AAAA,MAAA;AAI5E,YAAM,OAA6B,UAAU,QAAQ,IAAI,CAAC,UAAU;AAChE,cAAM,aAAa,MAAM,WAAW;AAGpC,cAAM,yBAAyB,gBAAgB;AAAA,UAC3C,CAAC,UACI,MAAM,cAAc,OAAO,UAAU,MAAM,cAAc,OAAO,cAChE,MAAM,cAAc,OAAO,cAAc,MAAM,cAAc,OAAO;AAAA,QAAA;AAI7E,cAAM,yBAAyB,gBAAgB;AAAA,UAC3C,CAAC,UACI,MAAM,cAAc,OAAO,UAAU,MAAM,cAAc,OAAO,cAChE,MAAM,cAAc,OAAO,cAAc,MAAM,cAAc,OAAO;AAAA,QAAA;AAI7E,YAAI;AACJ,YAAI;AAGJ,+BAAuB,QAAQ,CAAC,UAAU;AACtC,gBAAM,aAAa,MAAM,cAAc,OAAO;AAG9C,gBAAM,YAAY,SAAS,MAAM,OAAO,iBAAiB,GAAG;AAC5D,gBAAM,YAAY,SAAS,MAAM,OAAO,iBAAiB,GAAG;AAG5D,gBAAM,YAAY,aAAa,YAAY;AAC3C,gBAAM,gBAAgB,aAAa,YAAY;AAG/C,cAAI;AACJ,cAAI,YAAY,eAAe;AAC3B,qBAAS;AAAA,UACb,WAAW,cAAc,eAAe;AACpC,qBAAS;AAAA,UACb,OAAO;AACH,qBAAS;AAAA,UACb;AAEA,gBAAM,aAAoC;AAAA,YACtC;AAAA,YACA,OAAO,GAAG,SAAS,MAAM,SAAS;AAAA,YAClC,MAAM,MAAM,OAAO;AAAA,YACnB,SAAS,MAAM;AAAA,YACf,YAAY;AAAA,UAAA;AAIhB,cAAI,YAAY;AACZ,yBAAa;AAAA,UACjB,OAAO;AACH,yBAAa;AAAA,UACjB;AAAA,QACJ,CAAC;AAGD,+BAAuB,QAAQ,CAAC,UAAU;AACtC,gBAAM,aAAa,MAAM,cAAc,OAAO;AAE9C,cAAI,cAAc,CAAC,YAAY;AAC3B,yBAAa;AAAA,cACT,MAAM,MAAM,OAAO;AAAA,cACnB,SAAS,MAAM;AAAA,cACf,YAAY;AAAA,YAAA;AAAA,UAEpB,WAAW,CAAC,cAAc,CAAC,YAAY;AACnC,yBAAa;AAAA,cACT,MAAM,MAAM,OAAO;AAAA,cACnB,SAAS,MAAM;AAAA,cACf,YAAY;AAAA,YAAA;AAAA,UAEpB;AAAA,QACJ,CAAC;AAED,eAAO;AAAA,UACH,MAAM,MAAM;AAAA,UACZ,QAAQ,MAAM,WAAW;AAAA,UACzB,UAAU,MAAM,WAAW;AAAA,UAC3B,eAAe,MAAM,WAAW;AAAA,UAChC,UAAU,MAAM,WAAW;AAAA,UAC3B,aAAa,MAAM,MAAM,UAAU;AAAA,UACnC,QAAQ,MAAM,MAAM,UAAU;AAAA,UAC9B;AAAA,UACA;AAAA,QAAA;AAAA,MAER,CAAC;AAED,aAAO;AAAA,QACH;AAAA,QACA;AAAA,MAAA;AAAA,IAER;AAAA,IACA,CAAC,SAAS,UAAU,OAAO;AAAA,EAAA;AAG/B,SAAO,EAAE,oBAAA;AACb;"}
@@ -0,0 +1,109 @@
1
+ function parseScore(score) {
2
+ if (!score) return 0;
3
+ const parsed = parseInt(score, 10);
4
+ return isNaN(parsed) ? 0 : parsed;
5
+ }
6
+ function getFirstGoalScorer(match) {
7
+ if (!match.mainEvents || match.mainEvents.length === 0) return "NONE";
8
+ const goalEvents = match.mainEvents.filter(
9
+ (event) => event.type === "GOAL" || event.type === "PENALTY_GOAL" || event.type === "OWN_GOAL"
10
+ );
11
+ if (goalEvents.length === 0) return "NONE";
12
+ const sortedGoals = [...goalEvents].sort((a, b) => {
13
+ const minuteA = (a.minute ?? 0) + (a.injuryMinute ?? 0);
14
+ const minuteB = (b.minute ?? 0) + (b.injuryMinute ?? 0);
15
+ return minuteA - minuteB;
16
+ });
17
+ const firstGoal = sortedGoals[0];
18
+ if (firstGoal.type === "OWN_GOAL") {
19
+ return firstGoal.competitorPosition === "ONE" ? "AWAY" : "HOME";
20
+ }
21
+ return firstGoal.competitorPosition === "ONE" ? "HOME" : "AWAY";
22
+ }
23
+ function calculateCompetitionStats(matches) {
24
+ const totalMatches = matches.length;
25
+ const finishedMatches = matches.filter((m) => m.status.type === "FINISHED");
26
+ const finishedCount = finishedMatches.length;
27
+ let homeWins = 0;
28
+ let awayWins = 0;
29
+ let draws = 0;
30
+ let totalGoals = 0;
31
+ let totalHomeGoals = 0;
32
+ let totalAwayGoals = 0;
33
+ let over1_5 = 0;
34
+ let over2_5 = 0;
35
+ let over3_5 = 0;
36
+ let bothTeamsScored = 0;
37
+ let homeTeamScoredFirst = 0;
38
+ let awayTeamScoredFirst = 0;
39
+ let noGoalScored = 0;
40
+ finishedMatches.forEach((match) => {
41
+ const homeScore = parseScore(match.score?.competitorOne);
42
+ const awayScore = parseScore(match.score?.competitorTwo);
43
+ const totalMatchGoals = homeScore + awayScore;
44
+ if (homeScore > awayScore) {
45
+ homeWins++;
46
+ } else if (awayScore > homeScore) {
47
+ awayWins++;
48
+ } else {
49
+ draws++;
50
+ }
51
+ totalGoals += totalMatchGoals;
52
+ totalHomeGoals += homeScore;
53
+ totalAwayGoals += awayScore;
54
+ if (totalMatchGoals > 1.5) over1_5++;
55
+ if (totalMatchGoals > 2.5) over2_5++;
56
+ if (totalMatchGoals > 3.5) over3_5++;
57
+ if (homeScore > 0 && awayScore > 0) {
58
+ bothTeamsScored++;
59
+ }
60
+ const firstScorer = getFirstGoalScorer(match);
61
+ if (firstScorer === "HOME") {
62
+ homeTeamScoredFirst++;
63
+ } else if (firstScorer === "AWAY") {
64
+ awayTeamScoredFirst++;
65
+ } else {
66
+ noGoalScored++;
67
+ }
68
+ });
69
+ const round1 = (value) => Math.round(value * 10) / 10;
70
+ return {
71
+ // Match completion
72
+ totalMatches,
73
+ finishedMatches: finishedCount,
74
+ completionPercentage: totalMatches > 0 ? round1(finishedCount / totalMatches * 100) : 0,
75
+ // Match outcomes
76
+ homeWins,
77
+ awayWins,
78
+ draws,
79
+ homeWinPercentage: finishedCount > 0 ? round1(homeWins / finishedCount * 100) : 0,
80
+ awayWinPercentage: finishedCount > 0 ? round1(awayWins / finishedCount * 100) : 0,
81
+ drawPercentage: finishedCount > 0 ? round1(draws / finishedCount * 100) : 0,
82
+ // Goals overview
83
+ totalGoals,
84
+ goalsPerMatch: finishedCount > 0 ? totalGoals / finishedCount : 0,
85
+ homeGoalsPerMatch: finishedCount > 0 ? totalHomeGoals / finishedCount : 0,
86
+ awayGoalsPerMatch: finishedCount > 0 ? totalAwayGoals / finishedCount : 0,
87
+ // Over/Under goals
88
+ over1_5Goals: over1_5,
89
+ over2_5Goals: over2_5,
90
+ over3_5Goals: over3_5,
91
+ over1_5Percentage: finishedCount > 0 ? round1(over1_5 / finishedCount * 100) : 0,
92
+ over2_5Percentage: finishedCount > 0 ? round1(over2_5 / finishedCount * 100) : 0,
93
+ over3_5Percentage: finishedCount > 0 ? round1(over3_5 / finishedCount * 100) : 0,
94
+ // Both teams scored
95
+ bothTeamsScored,
96
+ bothTeamsScoredPercentage: finishedCount > 0 ? round1(bothTeamsScored / finishedCount * 100) : 0,
97
+ // First goal scored
98
+ homeTeamScoredFirst,
99
+ awayTeamScoredFirst,
100
+ noGoalScored,
101
+ homeTeamScoredFirstPercentage: finishedCount > 0 ? round1(homeTeamScoredFirst / finishedCount * 100) : 0,
102
+ awayTeamScoredFirstPercentage: finishedCount > 0 ? round1(awayTeamScoredFirst / finishedCount * 100) : 0,
103
+ noGoalScoredPercentage: finishedCount > 0 ? round1(noGoalScored / finishedCount * 100) : 0
104
+ };
105
+ }
106
+ export {
107
+ calculateCompetitionStats
108
+ };
109
+ //# sourceMappingURL=competitionStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"competitionStats.js","sources":["../../../../src/lib/providers/competition/utils/competitionStats.ts"],"sourcesContent":["/**\n * Competition statistics calculation utilities\n * Provides comprehensive stats about matches, goals, and outcomes\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\n\n/**\n * Competition statistics overview\n */\nexport interface CompetitionStats {\n // Match completion\n totalMatches: number;\n finishedMatches: number;\n completionPercentage: number;\n\n // Match outcomes\n homeWins: number;\n awayWins: number;\n draws: number;\n homeWinPercentage: number;\n awayWinPercentage: number;\n drawPercentage: number;\n\n // Goals overview\n totalGoals: number;\n goalsPerMatch: number;\n homeGoalsPerMatch: number;\n awayGoalsPerMatch: number;\n\n // Over/Under goals\n over1_5Goals: number;\n over2_5Goals: number;\n over3_5Goals: number;\n over1_5Percentage: number;\n over2_5Percentage: number;\n over3_5Percentage: number;\n\n // Both teams scored\n bothTeamsScored: number;\n bothTeamsScoredPercentage: number;\n\n // First goal scored\n homeTeamScoredFirst: number;\n awayTeamScoredFirst: number;\n noGoalScored: number;\n homeTeamScoredFirstPercentage: number;\n awayTeamScoredFirstPercentage: number;\n noGoalScoredPercentage: number;\n}\n\n/**\n * Parse score from string to number\n */\nfunction parseScore(score?: string | null): number {\n if (!score) return 0;\n const parsed = parseInt(score, 10);\n return isNaN(parsed) ? 0 : parsed;\n}\n\n/**\n * Get first goal scorer from match events\n */\nfunction getFirstGoalScorer(match: FUSportsMatch): \"HOME\" | \"AWAY\" | \"NONE\" {\n if (!match.mainEvents || match.mainEvents.length === 0) return \"NONE\";\n\n // Filter goal events\n const goalEvents = match.mainEvents.filter(\n (event) => event.type === \"GOAL\" || event.type === \"PENALTY_GOAL\" || event.type === \"OWN_GOAL\"\n );\n\n if (goalEvents.length === 0) return \"NONE\";\n\n // Sort by minute (and injury minute if available)\n const sortedGoals = [...goalEvents].sort((a, b) => {\n const minuteA = (a.minute ?? 0) + (a.injuryMinute ?? 0);\n const minuteB = (b.minute ?? 0) + (b.injuryMinute ?? 0);\n return minuteA - minuteB;\n });\n\n const firstGoal = sortedGoals[0];\n\n // For own goals, the scoring team is the opposite\n if (firstGoal.type === \"OWN_GOAL\") {\n return firstGoal.competitorPosition === \"ONE\" ? \"AWAY\" : \"HOME\";\n }\n\n return firstGoal.competitorPosition === \"ONE\" ? \"HOME\" : \"AWAY\";\n}\n\n/**\n * Calculate comprehensive competition statistics\n */\nexport function calculateCompetitionStats(matches: FUSportsMatch[]): CompetitionStats {\n const totalMatches = matches.length;\n const finishedMatches = matches.filter((m) => m.status.type === \"FINISHED\");\n const finishedCount = finishedMatches.length;\n\n // Initialize counters\n let homeWins = 0;\n let awayWins = 0;\n let draws = 0;\n let totalGoals = 0;\n let totalHomeGoals = 0;\n let totalAwayGoals = 0;\n let over1_5 = 0;\n let over2_5 = 0;\n let over3_5 = 0;\n let bothTeamsScored = 0;\n let homeTeamScoredFirst = 0;\n let awayTeamScoredFirst = 0;\n let noGoalScored = 0;\n\n finishedMatches.forEach((match) => {\n const homeScore = parseScore(match.score?.competitorOne);\n const awayScore = parseScore(match.score?.competitorTwo);\n const totalMatchGoals = homeScore + awayScore;\n\n // Match outcomes\n if (homeScore > awayScore) {\n homeWins++;\n } else if (awayScore > homeScore) {\n awayWins++;\n } else {\n draws++;\n }\n\n // Goals\n totalGoals += totalMatchGoals;\n totalHomeGoals += homeScore;\n totalAwayGoals += awayScore;\n\n // Over/Under\n if (totalMatchGoals > 1.5) over1_5++;\n if (totalMatchGoals > 2.5) over2_5++;\n if (totalMatchGoals > 3.5) over3_5++;\n\n // Both teams scored\n if (homeScore > 0 && awayScore > 0) {\n bothTeamsScored++;\n }\n\n // First goal scorer\n const firstScorer = getFirstGoalScorer(match);\n if (firstScorer === \"HOME\") {\n homeTeamScoredFirst++;\n } else if (firstScorer === \"AWAY\") {\n awayTeamScoredFirst++;\n } else {\n noGoalScored++;\n }\n });\n\n // Helper function to round to 1 decimal place\n const round1 = (value: number): number => Math.round(value * 10) / 10;\n\n return {\n // Match completion\n totalMatches,\n finishedMatches: finishedCount,\n completionPercentage: totalMatches > 0 ? round1((finishedCount / totalMatches) * 100) : 0,\n\n // Match outcomes\n homeWins,\n awayWins,\n draws,\n homeWinPercentage: finishedCount > 0 ? round1((homeWins / finishedCount) * 100) : 0,\n awayWinPercentage: finishedCount > 0 ? round1((awayWins / finishedCount) * 100) : 0,\n drawPercentage: finishedCount > 0 ? round1((draws / finishedCount) * 100) : 0,\n\n // Goals overview\n totalGoals,\n goalsPerMatch: finishedCount > 0 ? totalGoals / finishedCount : 0,\n homeGoalsPerMatch: finishedCount > 0 ? totalHomeGoals / finishedCount : 0,\n awayGoalsPerMatch: finishedCount > 0 ? totalAwayGoals / finishedCount : 0,\n\n // Over/Under goals\n over1_5Goals: over1_5,\n over2_5Goals: over2_5,\n over3_5Goals: over3_5,\n over1_5Percentage: finishedCount > 0 ? round1((over1_5 / finishedCount) * 100) : 0,\n over2_5Percentage: finishedCount > 0 ? round1((over2_5 / finishedCount) * 100) : 0,\n over3_5Percentage: finishedCount > 0 ? round1((over3_5 / finishedCount) * 100) : 0,\n\n // Both teams scored\n bothTeamsScored,\n bothTeamsScoredPercentage: finishedCount > 0 ? round1((bothTeamsScored / finishedCount) * 100) : 0,\n\n // First goal scored\n homeTeamScoredFirst,\n awayTeamScoredFirst,\n noGoalScored,\n homeTeamScoredFirstPercentage: finishedCount > 0 ? round1((homeTeamScoredFirst / finishedCount) * 100) : 0,\n awayTeamScoredFirstPercentage: finishedCount > 0 ? round1((awayTeamScoredFirst / finishedCount) * 100) : 0,\n noGoalScoredPercentage: finishedCount > 0 ? round1((noGoalScored / finishedCount) * 100) : 0,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ function isGoalEvent(eventType) {
2
+ return eventType === "GOAL" || eventType === "PENALTY_GOAL" || eventType === "OWN_GOAL";
3
+ }
4
+ function calculateGoalDistribution(matches, intervalMinutes = 15) {
5
+ const round1 = (value) => Math.round(value * 10) / 10;
6
+ const goals = [];
7
+ matches.forEach((match) => {
8
+ if (match.status.type !== "FINISHED") return;
9
+ match.mainEvents?.forEach((event) => {
10
+ if (isGoalEvent(event.type)) {
11
+ if (event.minute !== void 0) {
12
+ goals.push({
13
+ baseMinute: event.minute,
14
+ totalMinute: event.minute + (event.injuryMinute ?? 0)
15
+ });
16
+ }
17
+ }
18
+ });
19
+ });
20
+ const totalGoals = goals.length;
21
+ const intervals = [];
22
+ for (let start = 1; start <= 45; start += intervalMinutes) {
23
+ const end = Math.min(start + intervalMinutes - 1, 45);
24
+ const goalsInInterval = goals.filter((g) => g.baseMinute >= start && g.baseMinute <= end).length;
25
+ intervals.push({
26
+ label: end === 45 ? `${start}-${end}+` : `${start}-${end}`,
27
+ startMinute: start,
28
+ endMinute: end,
29
+ goals: goalsInInterval,
30
+ percentage: totalGoals > 0 ? round1(goalsInInterval / totalGoals * 100) : 0
31
+ });
32
+ }
33
+ for (let start = 46; start <= 90; start += intervalMinutes) {
34
+ const end = Math.min(start + intervalMinutes - 1, 90);
35
+ const goalsInInterval = end === 90 ? goals.filter((g) => g.baseMinute >= start).length : goals.filter((g) => g.baseMinute >= start && g.baseMinute <= end).length;
36
+ intervals.push({
37
+ label: end === 90 ? `${start}-${end}+` : `${start}-${end}`,
38
+ startMinute: start,
39
+ endMinute: end,
40
+ goals: goalsInInterval,
41
+ percentage: totalGoals > 0 ? round1(goalsInInterval / totalGoals * 100) : 0
42
+ });
43
+ }
44
+ const firstHalfGoals = goals.filter((g) => g.baseMinute <= 45).length;
45
+ const secondHalfGoals = goals.filter((g) => g.baseMinute >= 46).length;
46
+ return {
47
+ intervals,
48
+ byHalf: {
49
+ firstHalf: {
50
+ goals: firstHalfGoals,
51
+ percentage: totalGoals > 0 ? round1(firstHalfGoals / totalGoals * 100) : 0
52
+ },
53
+ secondHalf: {
54
+ goals: secondHalfGoals,
55
+ percentage: totalGoals > 0 ? round1(secondHalfGoals / totalGoals * 100) : 0
56
+ }
57
+ },
58
+ totalGoals
59
+ };
60
+ }
61
+ export {
62
+ calculateGoalDistribution
63
+ };
64
+ //# sourceMappingURL=goalDistribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"goalDistribution.js","sources":["../../../../src/lib/providers/competition/utils/goalDistribution.ts"],"sourcesContent":["/**\n * Goal distribution calculation utilities\n * Analyzes when goals are scored across matches\n */\n\nimport type { FUSportsMatch } from \"../../../types/canonical\";\n\n/**\n * Time interval for goal distribution\n */\nexport interface GoalDistributionInterval {\n label: string; // e.g., \"0-15 min\"\n startMinute: number;\n endMinute: number;\n goals: number;\n percentage: number;\n}\n\n/**\n * Half-based goal distribution\n */\nexport interface GoalDistributionByHalf {\n firstHalf: {\n goals: number;\n percentage: number;\n };\n secondHalf: {\n goals: number;\n percentage: number;\n };\n}\n\n/**\n * Complete goal distribution data\n */\nexport interface GoalDistribution {\n intervals: GoalDistributionInterval[];\n byHalf: GoalDistributionByHalf;\n totalGoals: number;\n}\n\n/**\n * Check if an event is a goal\n */\nfunction isGoalEvent(eventType: string): boolean {\n return eventType === \"GOAL\" || eventType === \"PENALTY_GOAL\" || eventType === \"OWN_GOAL\";\n}\n\n/**\n * Goal data with base minute (for interval assignment) and total minute (for sorting)\n */\ninterface GoalData {\n baseMinute: number; // The minute without injury time (e.g., 45)\n totalMinute: number; // The minute including injury time (e.g., 47 for 45+2)\n}\n\n/**\n * Calculate goal distribution across time intervals\n */\nexport function calculateGoalDistribution(matches: FUSportsMatch[], intervalMinutes: number = 15): GoalDistribution {\n // Helper function to round to 1 decimal place\n const round1 = (value: number): number => Math.round(value * 10) / 10;\n\n // Collect all goals with their base and total minutes\n const goals: GoalData[] = [];\n\n matches.forEach((match) => {\n if (match.status.type !== \"FINISHED\") return;\n\n match.mainEvents?.forEach((event) => {\n if (isGoalEvent(event.type)) {\n if (event.minute !== undefined) {\n goals.push({\n baseMinute: event.minute,\n totalMinute: event.minute + (event.injuryMinute ?? 0),\n });\n }\n }\n });\n });\n\n const totalGoals = goals.length;\n\n // Create intervals (1-15, 16-30, 31-45+, 46-60, 61-75, 76-90+)\n // Minutes are 1-based (minute 1 is the first minute of the match)\n // We use baseMinute to determine which interval a goal belongs to\n const intervals: GoalDistributionInterval[] = [];\n\n // First half intervals (1-15, 16-30, 31-45+)\n for (let start = 1; start <= 45; start += intervalMinutes) {\n const end = Math.min(start + intervalMinutes - 1, 45);\n // Filter by baseMinute - this ensures 45+1, 45+2 are counted in 31-45+ interval\n const goalsInInterval = goals.filter((g) => g.baseMinute >= start && g.baseMinute <= end).length;\n\n intervals.push({\n label: end === 45 ? `${start}-${end}+` : `${start}-${end}`,\n startMinute: start,\n endMinute: end,\n goals: goalsInInterval,\n percentage: totalGoals > 0 ? round1((goalsInInterval / totalGoals) * 100) : 0,\n });\n }\n\n // Second half intervals (46-60, 61-75, 76-90+)\n for (let start = 46; start <= 90; start += intervalMinutes) {\n const end = Math.min(start + intervalMinutes - 1, 90);\n // Filter by baseMinute - this ensures 90+1, 90+2 are counted in 76-90+ interval\n const goalsInInterval =\n end === 90\n ? goals.filter((g) => g.baseMinute >= start).length\n : goals.filter((g) => g.baseMinute >= start && g.baseMinute <= end).length;\n\n intervals.push({\n label: end === 90 ? `${start}-${end}+` : `${start}-${end}`,\n startMinute: start,\n endMinute: end,\n goals: goalsInInterval,\n percentage: totalGoals > 0 ? round1((goalsInInterval / totalGoals) * 100) : 0,\n });\n }\n\n // Calculate by half (using baseMinute to determine which half)\n const firstHalfGoals = goals.filter((g) => g.baseMinute <= 45).length;\n const secondHalfGoals = goals.filter((g) => g.baseMinute >= 46).length;\n\n return {\n intervals,\n byHalf: {\n firstHalf: {\n goals: firstHalfGoals,\n percentage: totalGoals > 0 ? round1((firstHalfGoals / totalGoals) * 100) : 0,\n },\n secondHalf: {\n goals: secondHalfGoals,\n percentage: totalGoals > 0 ? round1((secondHalfGoals / totalGoals) * 100) : 0,\n },\n },\n totalGoals,\n };\n}\n"],"names":[],"mappings":"AA4CA,SAAS,YAAY,WAA4B;AAC7C,SAAO,cAAc,UAAU,cAAc,kBAAkB,cAAc;AACjF;AAaO,SAAS,0BAA0B,SAA0B,kBAA0B,IAAsB;AAEhH,QAAM,SAAS,CAAC,UAA0B,KAAK,MAAM,QAAQ,EAAE,IAAI;AAGnE,QAAM,QAAoB,CAAA;AAE1B,UAAQ,QAAQ,CAAC,UAAU;AACvB,QAAI,MAAM,OAAO,SAAS,WAAY;AAEtC,UAAM,YAAY,QAAQ,CAAC,UAAU;AACjC,UAAI,YAAY,MAAM,IAAI,GAAG;AACzB,YAAI,MAAM,WAAW,QAAW;AAC5B,gBAAM,KAAK;AAAA,YACP,YAAY,MAAM;AAAA,YAClB,aAAa,MAAM,UAAU,MAAM,gBAAgB;AAAA,UAAA,CACtD;AAAA,QACL;AAAA,MACJ;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AAED,QAAM,aAAa,MAAM;AAKzB,QAAM,YAAwC,CAAA;AAG9C,WAAS,QAAQ,GAAG,SAAS,IAAI,SAAS,iBAAiB;AACvD,UAAM,MAAM,KAAK,IAAI,QAAQ,kBAAkB,GAAG,EAAE;AAEpD,UAAM,kBAAkB,MAAM,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS,EAAE,cAAc,GAAG,EAAE;AAE1F,cAAU,KAAK;AAAA,MACX,OAAO,QAAQ,KAAK,GAAG,KAAK,IAAI,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG;AAAA,MACxD,aAAa;AAAA,MACb,WAAW;AAAA,MACX,OAAO;AAAA,MACP,YAAY,aAAa,IAAI,OAAQ,kBAAkB,aAAc,GAAG,IAAI;AAAA,IAAA,CAC/E;AAAA,EACL;AAGA,WAAS,QAAQ,IAAI,SAAS,IAAI,SAAS,iBAAiB;AACxD,UAAM,MAAM,KAAK,IAAI,QAAQ,kBAAkB,GAAG,EAAE;AAEpD,UAAM,kBACF,QAAQ,KACF,MAAM,OAAO,CAAC,MAAM,EAAE,cAAc,KAAK,EAAE,SAC3C,MAAM,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS,EAAE,cAAc,GAAG,EAAE;AAE5E,cAAU,KAAK;AAAA,MACX,OAAO,QAAQ,KAAK,GAAG,KAAK,IAAI,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG;AAAA,MACxD,aAAa;AAAA,MACb,WAAW;AAAA,MACX,OAAO;AAAA,MACP,YAAY,aAAa,IAAI,OAAQ,kBAAkB,aAAc,GAAG,IAAI;AAAA,IAAA,CAC/E;AAAA,EACL;AAGA,QAAM,iBAAiB,MAAM,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE;AAC/D,QAAM,kBAAkB,MAAM,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE;AAEhE,SAAO;AAAA,IACH;AAAA,IACA,QAAQ;AAAA,MACJ,WAAW;AAAA,QACP,OAAO;AAAA,QACP,YAAY,aAAa,IAAI,OAAQ,iBAAiB,aAAc,GAAG,IAAI;AAAA,MAAA;AAAA,MAE/E,YAAY;AAAA,QACR,OAAO;AAAA,QACP,YAAY,aAAa,IAAI,OAAQ,kBAAkB,aAAc,GAAG,IAAI;AAAA,MAAA;AAAA,IAChF;AAAA,IAEJ;AAAA,EAAA;AAER;"}