feat(in-140): playing episodes

pull/66/head
Ruslan Khayrullin 3 years ago
parent e16953f1b0
commit 94463a558e
  1. 1
      src/config/index.tsx
  2. 3
      src/config/keyboardKeys.tsx
  3. 39
      src/features/CircleAnimationBar/index.tsx
  4. 21
      src/features/CircleAnimationBar/styled.tsx
  5. 4
      src/features/MatchCard/styled.tsx
  6. 10
      src/features/MatchPage/components/FinishedMatch/index.tsx
  7. 14
      src/features/MatchPage/components/LiveMatch/hooks/index.tsx
  8. 10
      src/features/MatchPage/components/LiveMatch/index.tsx
  9. 52
      src/features/MatchPage/helpers/getHalfTime.tsx
  10. 147
      src/features/MatchPage/store/hooks/index.tsx
  11. 44
      src/features/MatchPage/store/hooks/useMatchData.tsx
  12. 29
      src/features/MatchPage/store/hooks/usePlayersStats.tsx
  13. 125
      src/features/MatchPage/store/hooks/useStatsTab.tsx
  14. 24
      src/features/MatchPage/store/hooks/useTeamsStats.tsx
  15. 4
      src/features/MatchSidePlaylists/components/CircleAnimationBar/index.tsx
  16. 24
      src/features/MatchSidePlaylists/components/PlayersTable/Cell.tsx
  17. 15
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx
  18. 10
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx
  19. 65
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
  20. 50
      src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
  21. 58
      src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
  22. 3
      src/features/MatchSidePlaylists/components/PlayersTable/types.tsx
  23. 17
      src/features/MatchSidePlaylists/components/TabEvents/index.tsx
  24. 10
      src/features/MatchSidePlaylists/components/TabStats/index.tsx
  25. 163
      src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx
  26. 28
      src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
  27. 83
      src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
  28. 26
      src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
  29. 6
      src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx
  30. 10
      src/features/MatchSidePlaylists/hooks.tsx
  31. 9
      src/features/MatchSidePlaylists/index.tsx
  32. 11
      src/features/MatchSidePlaylists/styled.tsx
  33. 9
      src/features/MultiSourcePlayer/hooks/index.tsx
  34. 18
      src/features/StreamPlayer/hooks/index.tsx
  35. 6
      src/features/StreamPlayer/index.tsx
  36. 2
      src/requests/getMatchInfo.tsx
  37. 5
      src/requests/getMatchParticipants.tsx
  38. 4
      src/requests/getPlayersStats.tsx
  39. 57
      src/requests/getStatsEvents.tsx
  40. 4
      src/requests/getTeamsStats.tsx
  41. 1
      src/requests/index.tsx

@ -10,3 +10,4 @@ export * from './currencies'
export * from './dashes'
export * from './env'
export * from './userAgent'
export * from './keyboardKeys'

@ -0,0 +1,3 @@
export enum KEYBOARD_KEYS {
Enter = 'Enter',
}

@ -4,7 +4,7 @@ import { useEffect } from 'react'
import isEmpty from 'lodash/isEmpty'
import size from 'lodash/size'
import type { Events } from 'requests'
import { useMatchPageStore } from 'features/MatchPage/store'
import { fullEpisodesDuration } from './helpers'
import { Svg, Circle } from './styled'
@ -24,26 +24,33 @@ export const initialCircleAnimation: TCircleAnimation = {
}
type Props = {
circleAnimation?: TCircleAnimation,
filteredEvents: Events,
setWatchAllEpisodesTimer: (showTimer: boolean) => void,
className?: string,
size?: number,
text?: string,
}
export type TSetCircleAnimation = Dispatch<SetStateAction<TCircleAnimation>>
export const CircleAnimationBar = ({
className,
size: svgSize = 14,
text,
}: Props) => {
const {
circleAnimation,
filteredEvents,
setWatchAllEpisodesTimer,
}: Props) => {
} = useMatchPageStore()
const {
plaingOrder,
playedProgress,
playing,
ready,
} = circleAnimation!
} = circleAnimation
const timeOfAllEpisodes = fullEpisodesDuration(filteredEvents)
const remainingEvents = filteredEvents.slice(plaingOrder - 1)
const remainingEvents = filteredEvents.slice(plaingOrder && plaingOrder - 1)
const fullTimeOfRemainingEpisodes = !isEmpty(remainingEvents)
? fullEpisodesDuration(remainingEvents)
: 0
@ -52,6 +59,8 @@ export const CircleAnimationBar = ({
const currentAnimationTime = Math.round(fullTimeOfRemainingEpisodes - (playedProgress / 1000))
const currentEpisodesPercent = 100 - (100 / (timeOfAllEpisodes / currentAnimationTime))
const strokeDashOffset = svgSize * Math.PI
useEffect(() => {
if (currentEpisodesPercent >= 100 && (plaingOrder === size(filteredEvents))) {
setWatchAllEpisodesTimer(false)
@ -64,7 +73,10 @@ export const CircleAnimationBar = ({
])
return (
<Svg>
<Svg
className={className}
size={svgSize}
>
<Circle
cx='50%'
cy='50%'
@ -72,7 +84,18 @@ export const CircleAnimationBar = ({
currentAnimationTime={currentAnimationTime}
animationPause={animationPause}
currentEpisodesPercent={currentEpisodesPercent}
strokeDashOffset={strokeDashOffset}
/>
{text && (
<text
x='50%'
y='50%'
dominantBaseline='middle'
textAnchor='middle'
>
{text}
</text>
)}
</Svg>
)
}

@ -4,9 +4,12 @@ type TCircle = {
animationPause?: boolean,
currentAnimationTime: number,
currentEpisodesPercent: number,
strokeDashOffset: number,
}
const strokeDashOffset = 43.5
type SvgProps = {
size: number,
}
const clockAnimation = (currentEpisodesPercent?: number) => keyframes`
from {
@ -17,10 +20,12 @@ const clockAnimation = (currentEpisodesPercent?: number) => keyframes`
}
`
export const Svg = styled.svg`
export const Svg = styled.svg<SvgProps>`
--size: ${({ size }) => `${size}px`};
background-color: #5EB2FF;
width: 14px;
height: 14px;
width: var(--size);
height: var(--size);
position: relative;
border-radius: 50%;
`
@ -28,12 +33,12 @@ export const Svg = styled.svg`
export const Circle = styled.circle<TCircle>`
fill: transparent;
stroke: white;
stroke-width: 14px;
stroke-dasharray: ${strokeDashOffset};
stroke-dashoffset: ${strokeDashOffset};
stroke-width: var(--size);
stroke-dasharray: ${({ strokeDashOffset }) => strokeDashOffset};
stroke-dashoffset: ${({ strokeDashOffset }) => strokeDashOffset};
transform: rotate(-90deg);
transform-origin: center;
animation-name: ${({ currentEpisodesPercent }) => (
animation-name: ${({ currentEpisodesPercent, strokeDashOffset }) => (
clockAnimation(strokeDashOffset - (strokeDashOffset * currentEpisodesPercent / 100))
)};
animation-duration: ${({ currentAnimationTime }) => `${currentAnimationTime}s`};

@ -108,7 +108,7 @@ export const Preview = styled.img<CardProps>`
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.4;
opacity: 0.2;
`
export const MatchTimeInfo = styled.div<CardProps>`
@ -243,7 +243,7 @@ export const Team = styled.span<CardProps>`
justify-content: space-between;
align-items: center;
font-weight: 600;
font-size: 0.85rem;
font-size: 13px;
line-height: 1.14rem;
color: #fff;

@ -1,9 +1,7 @@
import { Fragment, useState } from 'react'
import { Fragment } from 'react'
import isEmpty from 'lodash/isEmpty'
import type { TCircleAnimation } from 'features/CircleAnimationBar'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import { MatchSidePlaylists } from 'features/MatchSidePlaylists'
import { MultiSourcePlayer } from 'features/MultiSourcePlayer'
@ -16,12 +14,10 @@ import { MatchDescription } from '../MatchDescription'
import { useMatchPageStore } from '../../store'
export const FinishedMatch = () => {
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const {
access,
isOpenFiltersPopup,
profile,
setPlayingProgress,
} = useMatchPageStore()
const {
chapters,
@ -53,11 +49,9 @@ export const FinishedMatch = () => {
<Fragment>
<MultiSourcePlayer
access={access}
setCircleAnimation={setCircleAnimation}
isOpenPopup={isOpenFiltersPopup}
chapters={chapters}
onPlayingChange={onPlayingChange}
onPlayerProgressChange={setPlayingProgress}
profile={profile}
/>
<MatchDescription />
@ -66,8 +60,6 @@ export const FinishedMatch = () => {
</Container>
<MatchSidePlaylists
setCircleAnimation={setCircleAnimation}
circleAnimation={circleAnimation}
selectedPlaylist={selectedPlaylist}
onSelect={onPlaylistSelect}
/>

@ -16,12 +16,9 @@ import { usePlaylistLogger } from './usePlaylistLogger'
export const useLiveMatch = () => {
const {
handlePlaylistClick,
isPlayFilterEpisodes,
playNextEpisode,
profile,
selectedPlaylist,
setFullMatchPlaylistDuration,
setPlayingProgress,
} = useMatchPageStore()
const { profileId: matchId, sportType } = usePageParams()
const resume = useResumeUrlParam()
@ -46,7 +43,7 @@ export const useLiveMatch = () => {
} = usePlaylistLogger()
const {
onPlayerProgressChange: playerProgressChange,
onPlayerProgressChange,
onPlayingChange: notifyProgressLogger,
} = usePlayerProgressReporter()
@ -66,22 +63,13 @@ export const useLiveMatch = () => {
}
handlePlaylistClick(playlist, e)
}
const onPlayerProgressChange = (seconds: number, period = 0) => {
playerProgressChange(seconds, period)
setPlayingProgress(seconds * 1000)
}
return {
chapters,
isPlayFilterEpisodes,
onDurationChange,
onPlayerProgressChange,
onPlayingChange,
onPlaylistSelect,
playNextEpisode,
resume: resume ?? fromStartIfStreamPaused,
selectedPlaylist,
streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`,
}
}

@ -1,9 +1,7 @@
import { Fragment, useState } from 'react'
import { Fragment } from 'react'
import isEmpty from 'lodash/isEmpty'
import type { TCircleAnimation } from 'features/CircleAnimationBar'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import { useMatchPageStore } from 'features/MatchPage/store'
import { StreamPlayer } from 'features/StreamPlayer'
import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer'
@ -15,8 +13,6 @@ import { useLiveMatch } from './hooks'
import { MatchDescription } from '../MatchDescription'
export const LiveMatch = () => {
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const {
profile,
selectedPlaylist,
@ -43,12 +39,10 @@ export const LiveMatch = () => {
onProgressChange={onPlayerProgressChange}
resumeFrom={resume}
url={streamUrl}
setCircleAnimation={setCircleAnimation}
/>
) : (
!isEmpty(chapters) && (
<StreamPlayer
setCircleAnimation={setCircleAnimation}
onDurationChange={onDurationChange}
onPlayingChange={onPlayingChange}
onProgressChange={onPlayerProgressChange}
@ -62,8 +56,6 @@ export const LiveMatch = () => {
</Container>
<MatchSidePlaylists
setCircleAnimation={setCircleAnimation}
circleAnimation={circleAnimation}
onSelect={onPlaylistSelect}
selectedPlaylist={selectedPlaylist}
/>

@ -0,0 +1,52 @@
import head from 'lodash/head'
import last from 'lodash/last'
import inRange from 'lodash/inRange'
import type { VideoBounds } from 'requests'
export const getHalfTime = (videoBounds: VideoBounds, currentTime: number) => {
const firstBound = head(videoBounds)
const lastBound = last(videoBounds)
const matchSecond = (Number(firstBound?.s) || 0) + currentTime
if (matchSecond > (Number(lastBound?.e) || 0)) {
return {}
}
if (matchSecond < Number(videoBounds[1].s)) {
return {
period: 1,
second: 1,
}
}
let period = 1
let second = 1
for (let i = 1; i < videoBounds.length; i++) {
const { e, s } = videoBounds[i]
if (inRange(
matchSecond,
Number(s),
Number(e) + 1,
)) {
period = i
second = matchSecond - Number(videoBounds[i].s)
break
} else if (inRange(
matchSecond,
Number(e) + 1,
Number(videoBounds[i + 1].s),
)) {
period = i + 1
break
}
}
return {
period,
second,
}
}

@ -10,13 +10,15 @@ import isEmpty from 'lodash/isEmpty'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { useAuthStore } from 'features/AuthStore'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import type { TCircleAnimation } from 'features/CircleAnimationBar'
import type { MatchInfo } from 'requests/getMatchInfo'
import { getMatchInfo } from 'requests/getMatchInfo'
import { getViewMatchDuration } from 'requests/getViewMatchDuration'
import { usePageParams } from 'hooks/usePageParams'
import { useToggle } from 'hooks/useToggle'
import { usePageParams, useToggle } from 'hooks'
import { parseDate } from 'helpers/parseDate'
@ -24,6 +26,31 @@ import { useTournamentData } from './useTournamentData'
import { useMatchData } from './useMatchData'
import { useFiltersPopup } from './useFitersPopup'
import { useTabEvents } from './useTabEvents'
import { useTeamsStats } from './useTeamsStats'
import { useStatsTab } from './useStatsTab'
import { usePlayersStats } from './usePlayersStats'
type PlayingData = {
player: {
id: number | null,
paramId: number | null,
},
team: {
id: number | null,
paramId: number | null,
},
}
const initPlayingData: PlayingData = {
player: {
id: null,
paramId: null,
},
team: {
id: null,
paramId: null,
},
}
const ACCESS_TIME = 60
@ -31,6 +58,15 @@ export const useMatchPage = () => {
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null)
const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false)
const [access, setAccess] = useState(true)
const [playingProgress, setPlayingProgress] = useState(0)
const [playingData, setPlayingData] = useState<PlayingData>(initPlayingData)
const [plaingOrder, setPlaingOrder] = useState(0)
const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false)
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH)
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const isStatsTab = selectedTab === Tabs.STATS
const { profileId: matchId, sportType } = usePageParams()
const { user, userInfo } = useAuthStore()
@ -47,6 +83,7 @@ export const useMatchPage = () => {
matchPlaylists,
selectedPlaylist,
setFullMatchPlaylistDuration,
setSelectedPlaylist,
} = useMatchData(matchProfile)
const profile = matchProfile
@ -63,9 +100,9 @@ export const useMatchPage = () => {
filters,
isAllActionsChecked,
isEmptyFilters,
isFirstTeamPlayersChecked,
isOpen: isOpenFiltersPopup,
resetEvents,
resetPlayers,
isSecondTeamPlayersChecked,
toggle: togglePopup,
toggleActiveEvents,
toggleActivePlayers,
@ -131,24 +168,56 @@ export const useMatchPage = () => {
return () => clearInterval(getIntervalMatch)
})
const disablePlayingEpisodes = () => {
setIsPlayingFiltersEpisodes(false)
setWatchAllEpisodesTimer(false)
setCircleAnimation(initialCircleAnimation)
}
const {
events,
handlePlaylistClick,
isEmptyPlayersStats,
circleAnimation: statsCircleAnimation,
filteredEvents: statsFilteredEvents,
isPlayersStatsFetching,
isPlayFilterEpisodes: isStatsPlayFilterEpisodes,
isTeamsStatsFetching,
matchPlaylists,
plaingOrder: statsPlaingOrder,
playEpisodes: playStatsEpisodes,
playNextEpisode: playStatsNextEpisode,
setCircleAnimation: setStatsCircleAnimation,
setIsPlayersStatsFetching,
setIsPlayingFiltersEpisodes: setStatsIsPlayinFiltersEpisodes,
setIsTeamsStatsFetching,
setPlaingOrder: setStatsPlaingOrder,
setWatchAllEpisodesTimer: setStatsWatchAllEpisodesTimer,
statsType,
toggleStatsType,
watchAllEpisodesTimer: statsWatchAllEpisodesTimer,
} = useStatsTab({
disablePlayingEpisodes,
handlePlaylistClick,
selectedPlaylist,
setSelectedPlaylist,
})
const { teamsStats } = useTeamsStats({
matchProfile,
playingProgress,
selectedPlaylist,
setIsTeamsStatsFetching,
statsType,
})
const {
isEmptyPlayersStats,
playersData,
playersStats,
} = usePlayersStats({
matchProfile,
playingProgress,
selectedPlaylist,
setFullMatchPlaylistDuration,
setPlayingProgress,
setIsPlayersStatsFetching,
statsType,
teamsStats,
toggleStatsType,
} = useMatchData(matchProfile)
const profile = matchProfile
})
const isStarted = useMemo(() => (
profile?.date
@ -171,9 +240,6 @@ export const useMatchPage = () => {
}
}, [events, filters])
const [plaingOrder, setPlaingOrder] = useState(0)
const [isPlayFilterEpisodes, setIsPlayinFiltersEpisodes] = useState(false)
const {
activeStatus,
episodesToPlay,
@ -185,16 +251,12 @@ export const useMatchPage = () => {
setUnreversed,
} = useTabEvents({ events: filteredEvents, profile })
useEffect(() => {
if (plaingOrder > episodesToPlay.length) setPlaingOrder(0)
}, [plaingOrder, episodesToPlay])
const playNextEpisode = (order?: number) => {
const isLastEpisode = plaingOrder === episodesToPlay.length
const currentOrder = order === 0 ? order : plaingOrder
if (isLastEpisode) {
setIsPlayinFiltersEpisodes(false)
setIsPlayingFiltersEpisodes(false)
return
}
@ -202,11 +264,16 @@ export const useMatchPage = () => {
setPlaingOrder(currentOrder + 1)
}
const playEpisodes = () => {
setPlayingData(initPlayingData)
setStatsWatchAllEpisodesTimer(true)
setStatsIsPlayinFiltersEpisodes(false)
setStatsCircleAnimation(initialCircleAnimation)
if (!watchAllEpisodesTimer) {
setWatchAllEpisodesTimer(true)
}
setIsPlayinFiltersEpisodes(true)
setIsPlayingFiltersEpisodes(true)
if (matchProfile?.live) {
handlePlaylistClick({
@ -219,10 +286,6 @@ export const useMatchPage = () => {
}
}
const disablePlayingEpisodes = () => {
setIsPlayinFiltersEpisodes(false)
}
return {
access,
activeEvents,
@ -232,41 +295,53 @@ export const useMatchPage = () => {
allActionsToggle,
allPlayersToggle,
applyFilters,
circleAnimation: isStatsTab ? statsCircleAnimation : circleAnimation,
closePopup,
countOfFilters,
disablePlayingEpisodes,
events,
filteredEvents,
filteredEvents: isStatsTab ? statsFilteredEvents : filteredEvents,
handlePlaylistClick,
hideProfileCard,
isAllActionsChecked,
isEmptyFilters,
isEmptyPlayersStats,
isFirstTeamPlayersChecked,
isLiveMatch,
isOpenFiltersPopup,
isPlayFilterEpisodes,
isPlayFilterEpisodes: isStatsTab ? isStatsPlayFilterEpisodes : isPlayFilterEpisodes,
isPlayersStatsFetching,
isSecondTeamPlayersChecked,
isStarted,
isTeamsStatsFetching,
likeImage,
likeToggle,
matchPlaylists,
plaingOrder,
plaingOrder: isStatsTab ? statsPlaingOrder : plaingOrder,
playEpisodes,
playNextEpisode,
playNextEpisode: isStatsTab ? playStatsNextEpisode : playNextEpisode,
playStatsEpisodes,
playersData,
playersStats,
playingData,
playingProgress,
profile,
profileCardShown,
reversedGroupEvents,
selectedPlaylist,
selectedTab,
setCircleAnimation: isStatsTab ? setStatsCircleAnimation : setCircleAnimation,
setFullMatchPlaylistDuration,
setIsPlayinFiltersEpisodes,
setPlaingOrder,
setIsPlayingFiltersEpisodes: isStatsTab
? setStatsIsPlayinFiltersEpisodes
: setIsPlayersStatsFetching,
setPlaingOrder: isStatsTab ? setStatsPlaingOrder : setPlaingOrder,
setPlayingData,
setPlayingProgress,
setReversed,
setSelectedTab,
setUnreversed,
setWatchAllEpisodesTimer,
setWatchAllEpisodesTimer: isStatsTab ? setStatsWatchAllEpisodesTimer : setWatchAllEpisodesTimer,
showProfileCard,
statsType,
teamsStats,
@ -277,6 +352,6 @@ export const useMatchPage = () => {
tournamentData,
uniqEvents,
user,
watchAllEpisodesTimer,
watchAllEpisodesTimer: isStatsTab ? statsWatchAllEpisodesTimer : watchAllEpisodesTimer,
}
}

@ -11,14 +11,12 @@ import type { MatchInfo } from 'requests/getMatchInfo'
import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { useDuration } from 'features/MultiSourcePlayer/hooks/useDuration'
import { useMatchPopupStore } from 'features/MatchPopup'
import { useMatchPlaylists } from './useMatchPlaylists'
import { useEvents } from './useEvents'
import { useTeamsStats } from './useTeamsStats'
import { useStatsTab } from './useStatsTab'
import { usePlayersStats } from './usePlayersStats'
const MATCH_DATA_POLL_INTERVAL = 60000
const MATCH_PLAYLISTS_DELAY = 5000
@ -27,7 +25,6 @@ export const useMatchData = (profile: MatchInfo) => {
const { profileId: matchId, sportType } = usePageParams()
const { chapters } = useMatchPopupStore()
const [matchDuration, setMatchDuration] = useState(0)
const [playingProgress, setPlayingProgress] = useState(0)
const {
fetchMatchPlaylists,
handlePlaylistClick,
@ -39,33 +36,6 @@ export const useMatchData = (profile: MatchInfo) => {
const { events, fetchMatchEvents } = useEvents()
const {
isPlayersStatsFetching,
isTeamsStatsFetching,
setIsPlayersStatsFetching,
setIsTeamsStatsFetching,
statsType,
toggleStatsType,
} = useStatsTab()
const { teamsStats } = useTeamsStats({
matchProfile: profile,
playingProgress,
setIsTeamsStatsFetching,
statsType,
})
const {
isEmptyPlayersStats,
playersData,
playersStats,
} = usePlayersStats({
matchProfile: profile,
playingProgress,
setIsPlayersStatsFetching,
statsType,
})
const fetchPlaylistsDebounced = useMemo(
() => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY),
[fetchMatchPlaylists],
@ -113,7 +83,7 @@ export const useMatchData = (profile: MatchInfo) => {
}, [profile?.live, start, stop])
useEffect(() => {
selectedPlaylist?.id === 'full_game' && setMatchDuration(chaptersDuration)
selectedPlaylist?.id === FULL_GAME_KEY && setMatchDuration(chaptersDuration)
// eslint-disable-next-line
}, [profile, chaptersDuration])
@ -125,17 +95,9 @@ export const useMatchData = (profile: MatchInfo) => {
return {
events,
handlePlaylistClick,
isEmptyPlayersStats,
isPlayersStatsFetching,
isTeamsStatsFetching,
matchPlaylists,
playersData,
playersStats,
selectedPlaylist,
setFullMatchPlaylistDuration,
setPlayingProgress,
statsType,
teamsStats,
toggleStatsType,
setSelectedPlaylist,
}
}

@ -9,6 +9,7 @@ import throttle from 'lodash/throttle'
import isEmpty from 'lodash/isEmpty'
import every from 'lodash/every'
import find from 'lodash/find'
import isUndefined from 'lodash/isUndefined'
import type {
MatchInfo,
@ -19,7 +20,10 @@ import { getPlayersStats, getMatchParticipants } from 'requests'
import { useObjectState, usePageParams } from 'hooks'
import type{ PlaylistOption } from 'features/MatchPage/types'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
const REQUEST_DELAY = 3000
const STATS_POLL_INTERVAL = 30000
@ -27,6 +31,7 @@ const STATS_POLL_INTERVAL = 30000
type UsePlayersStatsArgs = {
matchProfile: MatchInfo,
playingProgress: number,
selectedPlaylist?: PlaylistOption,
setIsPlayersStatsFetching: Dispatch<SetStateAction<boolean>>,
statsType: StatsType,
}
@ -39,6 +44,7 @@ type PlayersData = {
export const usePlayersStats = ({
matchProfile,
playingProgress,
selectedPlaylist,
setIsPlayersStatsFetching,
statsType,
}: UsePlayersStatsArgs) => {
@ -53,8 +59,6 @@ export const usePlayersStats = ({
const isCurrentStats = statsType === StatsType.CURRENT_STATS
const progressSec = Math.floor(playingProgress / 1000)
const isEmptyPlayersStats = (teamId: number) => (
isEmpty(playersStats[teamId])
|| every(playersStats[teamId], isEmpty)
@ -62,13 +66,17 @@ export const usePlayersStats = ({
)
const fetchPlayers = useMemo(() => throttle(async (second?: number) => {
if (!matchProfile?.team1.id || !matchProfile?.team2.id) return null
if (
!matchProfile?.team1.id
|| !matchProfile?.team2.id
|| !matchProfile?.video_bounds
) return null
try {
return getMatchParticipants({
matchId,
second,
sportType,
...(!isUndefined(second) && getHalfTime(matchProfile.video_bounds, second)),
})
} catch (e) {
return Promise.reject(e)
@ -77,18 +85,19 @@ export const usePlayersStats = ({
matchId,
matchProfile?.team1.id,
matchProfile?.team2.id,
matchProfile?.video_bounds,
sportType,
])
const fetchPlayersStats = useMemo(() => (async (team: 'team1' | 'team2', second?: number) => {
if (!sportName || !matchProfile?.[team].id) return null
if (!sportName || !matchProfile?.[team].id || !matchProfile?.video_bounds) return null
try {
return getPlayersStats({
matchId,
second,
sportName,
teamId: matchProfile[team].id,
...(!isUndefined(second) && getHalfTime(matchProfile.video_bounds, second)),
})
} catch (e) {
return Promise.reject(e)
@ -102,6 +111,8 @@ export const usePlayersStats = ({
])
const fetchData = useMemo(() => throttle(async (second?: number) => {
if (selectedPlaylist?.id !== FULL_GAME_KEY || !matchProfile?.video_bounds) return
const [res1, res2, res3] = await Promise.all([
fetchPlayers(second),
fetchPlayersStats('team1', second),
@ -123,11 +134,13 @@ export const usePlayersStats = ({
setIsPlayersStatsFetching(false)
}, REQUEST_DELAY), [
selectedPlaylist?.id,
fetchPlayers,
fetchPlayersStats,
setPlayersStats,
matchProfile?.team1.id,
matchProfile?.team2.id,
matchProfile?.video_bounds,
setIsPlayersStatsFetching,
])
@ -153,11 +166,11 @@ export const usePlayersStats = ({
useEffect(() => {
if (isCurrentStats) {
fetchData(progressSec)
fetchData(playingProgress)
}
}, [
fetchData,
progressSec,
playingProgress,
isCurrentStats,
matchProfile?.live,
])

@ -1,11 +1,62 @@
import type { Dispatch, SetStateAction } from 'react'
import { useState } from 'react'
import map from 'lodash/map'
import isEqual from 'lodash/isEqual'
import { isIOS } from 'config'
import type {
Episode,
Episodes,
Events,
} from 'requests'
import type { EventPlaylistOption, PlaylistOption } from 'features/MatchPage/types'
import type { TCircleAnimation } from 'features/CircleAnimationBar'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import { PlaylistTypes } from 'features/MatchPage/types'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
export const useStatsTab = () => {
type UseStatsTabArgs = {
disablePlayingEpisodes: () => void,
handlePlaylistClick: (playlist: PlaylistOption) => void,
selectedPlaylist?: PlaylistOption,
setSelectedPlaylist: Dispatch<SetStateAction<PlaylistOption | undefined>>,
}
type PlayNextEpisodeArgs = {
episodesToPlay?: Array<EventPlaylistOption>,
order?: number,
}
const EPISODE_TIMESTAMP_OFFSET = 0.001
const addOffset = ({
e,
h,
s,
}: Episode) => ({
e: e + EPISODE_TIMESTAMP_OFFSET,
h,
s: s + EPISODE_TIMESTAMP_OFFSET,
})
export const useStatsTab = ({
disablePlayingEpisodes,
handlePlaylistClick,
selectedPlaylist,
setSelectedPlaylist,
}: UseStatsTabArgs) => {
const [statsType, setStatsType] = useState<StatsType>(StatsType.FINAL_STATS)
const [isPlayersStatsFetching, setIsPlayersStatsFetching] = useState(false)
const [isTeamsStatsFetching, setIsTeamsStatsFetching] = useState(false)
const [stateEpisodesToPlay, setEpisodesToPlay] = useState<Array<EventPlaylistOption>>([])
const [filteredEvents, setFilteredEvents] = useState<Events>([])
const [plaingOrder, setPlaingOrder] = useState(0)
const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false)
const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false)
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const isFinalStatsType = statsType === StatsType.FINAL_STATS
@ -17,12 +68,84 @@ export const useStatsTab = () => {
setIsPlayersStatsFetching(true)
}
const getEpisodesToPlay = (episodes: Episodes) => map(episodes, (episode, i) => ({
episodes: [
/** При проигрывании нового эпизода с такими же e и s, как у текущего
воспроизведение начинается не с начала, чтобы пофиксить это добавляем
небольшой оффсет
*/
isEqual(episode, selectedPlaylist?.episodes[0])
? addOffset(episode)
: episode,
],
id: i,
type: PlaylistTypes.EVENT,
})) as Array<EventPlaylistOption>
const playNextEpisode = ({
order,
episodesToPlay = stateEpisodesToPlay,
}: PlayNextEpisodeArgs = {}) => {
const currentOrder = order === 0 ? order : plaingOrder
const isLastEpisode = currentOrder === episodesToPlay.length
if (isLastEpisode) {
setPlaingOrder(0)
setIsPlayingFiltersEpisodes(false)
return
}
if (currentOrder !== 0) {
handlePlaylistClick(episodesToPlay[currentOrder])
}
setPlaingOrder(currentOrder + 1)
}
const playEpisodes = (episodes: Episodes) => {
disablePlayingEpisodes()
const episodesToPlay = getEpisodesToPlay(episodes)
/**
* на ios видео иногда не останавлвается после завершения эпизода,
* данный хак исправляет проблему
*/
if (isIOS) {
setSelectedPlaylist({
...episodesToPlay[0],
episodes: [addOffset(episodesToPlay[0].episodes[0])],
})
}
setEpisodesToPlay(episodesToPlay)
setFilteredEvents(episodes as Events)
setWatchAllEpisodesTimer(true)
setIsPlayingFiltersEpisodes(true)
handlePlaylistClick(episodesToPlay[0])
playNextEpisode({ episodesToPlay, order: 0 })
}
return {
circleAnimation,
filteredEvents,
isPlayFilterEpisodes,
isPlayersStatsFetching,
isTeamsStatsFetching,
plaingOrder,
playEpisodes,
playNextEpisode,
setCircleAnimation,
setIsPlayersStatsFetching,
setIsPlayingFiltersEpisodes,
setIsTeamsStatsFetching,
setPlaingOrder,
setWatchAllEpisodesTimer,
statsType,
toggleStatsType,
watchAllEpisodesTimer,
}
}

@ -6,13 +6,17 @@ import {
} from 'react'
import throttle from 'lodash/throttle'
import isUndefined from 'lodash/isUndefined'
import type { MatchInfo } from 'requests'
import { getTeamsStats, TeamStatItem } from 'requests'
import { usePageParams } from 'hooks/usePageParams'
import type { PlaylistOption } from 'features/MatchPage/types'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
const REQUEST_DELAY = 3000
const STATS_POLL_INTERVAL = 30000
@ -20,6 +24,7 @@ const STATS_POLL_INTERVAL = 30000
type UseTeamsStatsArgs = {
matchProfile: MatchInfo,
playingProgress: number,
selectedPlaylist?: PlaylistOption,
setIsTeamsStatsFetching: Dispatch<SetStateAction<boolean>>,
statsType: StatsType,
}
@ -27,6 +32,7 @@ type UseTeamsStatsArgs = {
export const useTeamsStats = ({
matchProfile,
playingProgress,
selectedPlaylist,
setIsTeamsStatsFetching,
statsType,
}: UseTeamsStatsArgs) => {
@ -36,18 +42,16 @@ export const useTeamsStats = ({
const { profileId: matchId, sportName } = usePageParams()
const progressSec = Math.floor(playingProgress / 1000)
const isCurrentStats = statsType === StatsType.CURRENT_STATS
const fetchTeamsStats = useMemo(() => throttle(async (second?: number) => {
if (!sportName) return
if (!sportName || selectedPlaylist?.id !== FULL_GAME_KEY || !matchProfile?.video_bounds) return
try {
const data = await getTeamsStats({
matchId,
second,
sportName,
...(!isUndefined(second) && getHalfTime(matchProfile.video_bounds, second)),
})
setTeamsStats(data)
@ -55,7 +59,13 @@ export const useTeamsStats = ({
// eslint-disable-next-line no-empty
} catch (e) {}
}, REQUEST_DELAY), [matchId, setIsTeamsStatsFetching, sportName])
}, REQUEST_DELAY), [
matchProfile?.video_bounds,
selectedPlaylist?.id,
matchId,
setIsTeamsStatsFetching,
sportName,
])
useEffect(() => {
let interval: NodeJS.Timeout
@ -75,9 +85,9 @@ export const useTeamsStats = ({
useEffect(() => {
if (isCurrentStats) {
fetchTeamsStats(progressSec)
fetchTeamsStats(playingProgress)
}
}, [fetchTeamsStats, progressSec, isCurrentStats])
}, [fetchTeamsStats, playingProgress, isCurrentStats])
return {
statsType,

@ -4,7 +4,9 @@ import { CircleAnimationBar as CircleAnimationBarBase } from 'features/CircleAni
export const CircleAnimationBar = styled(CircleAnimationBarBase)`
position: absolute;
transform: translateY(-50%);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
circle {
stroke: #4086C6;

@ -1,10 +1,14 @@
import type { PropsWithChildren, HTMLProps } from 'react'
import { memo } from 'react'
import { memo, useRef } from 'react'
import { createPortal } from 'react-dom'
import { isMobileDevice } from 'config'
import { isMobileDevice, KEYBOARD_KEYS } from 'config'
import { useModalRoot, useTooltip } from 'hooks'
import {
useEventListener,
useModalRoot,
useTooltip,
} from 'hooks'
import { Tooltip, CellContainer } from './styled'
@ -27,6 +31,8 @@ const CellFC = ({
sorted,
tooltipText,
}: PropsWithChildren<CellProps>) => {
const cellRef = useRef<HTMLTableCellElement | null>(null)
const {
isTooltipShown,
onMouseLeave,
@ -36,8 +42,20 @@ const CellFC = ({
const modalRoot = useModalRoot()
useEventListener({
callback: (e) => {
if (e.key !== KEYBOARD_KEYS.Enter) return
// @ts-expect-error
onClick()
},
event: 'keydown',
target: cellRef,
})
return (
<CellContainer
ref={cellRef}
as={as}
onClick={onClick}
clickable={clickable}

@ -1,4 +1,6 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { useMatchPageStore } from 'features/MatchPage/store'
import type { SortCondition, PlayersTableProps } from '../types'
import { usePlayers } from './usePlayers'
@ -11,6 +13,8 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
paramId: null,
})
const { plaingOrder, setCircleAnimation } = useMatchPageStore()
const {
getPlayerName,
getPlayerParams,
@ -20,6 +24,7 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
const {
containerRef,
getDisplayedValue,
handleParamClick,
handleScroll,
handleSortClick,
isExpanded,
@ -37,11 +42,19 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
teamId,
})
useEffect(() => {
setCircleAnimation((state) => ({
...state,
plaingOrder,
}))
}, [setCircleAnimation, plaingOrder])
return {
containerRef,
getDisplayedValue,
getPlayerName,
getPlayerParams,
handleParamClick,
handleScroll,
handleSortClick,
isExpanded,

@ -4,7 +4,7 @@ import orderBy from 'lodash/orderBy'
import isNil from 'lodash/isNil'
import trim from 'lodash/trim'
import type { Player, PlayerParam } from 'requests'
import type { Player } from 'requests'
import { useToggle } from 'hooks'
@ -32,8 +32,6 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => {
[playersStats, teamId],
)
const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : val)
const getPlayerName = useCallback((player: Player) => (
trim(player[`lastname_${suffix}`] || '')
), [suffix])
@ -49,7 +47,7 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => {
const players = playersData[matchProfile?.team1.id === teamId ? 'team1' : 'team2']
return isNil(sortCondition.paramId)
? orderBy(players, getPlayerName)
? orderBy(players, 'ord')
: orderBy(
players,
[
@ -58,12 +56,11 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => {
return isNil(paramValue) ? -1 : paramValue
},
getPlayerName,
'ord',
],
sortCondition.dir,
)
}, [
getPlayerName,
getParamValue,
playersData,
matchProfile?.team1.id,
@ -73,7 +70,6 @@ export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => {
])
return {
getDisplayedValue,
getPlayerName,
getPlayerParams,
isExpanded,

@ -17,17 +17,18 @@ import isNil from 'lodash/isNil'
import reduce from 'lodash/reduce'
import forEach from 'lodash/forEach'
import values from 'lodash/values'
import round from 'lodash/round'
import map from 'lodash/map'
import { isMobileDevice } from 'config'
import type { PlayerParam, PlayersStats } from 'requests'
import { getStatsEvents } from 'requests'
import { useToggle } from 'hooks'
import { usePageParams, useToggle } from 'hooks'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsConfig } from 'features/LexicsStore'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import type { SortCondition } from '../types'
import {
@ -36,6 +37,7 @@ import {
DISPLAYED_PARAMS_COLUMNS,
SCROLLBAR_WIDTH,
} from '../config'
import { StatsType } from '../../TabStats/config'
type UseTableArgs = {
setSortCondition: Dispatch<SetStateAction<SortCondition>>,
@ -52,7 +54,7 @@ export const useTable = ({
const tableWrapperRef = useRef<HTMLDivElement>(null)
const [showLeftArrow, setShowLeftArrow] = useState(false)
const [showRightArrow, setShowRightArrow] = useState(false)
const [showRightArrow, setShowRightArrow] = useState(true)
const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT)
const {
@ -60,7 +62,17 @@ export const useTable = ({
isOpen: isExpanded,
toggle: toggleIsExpanded,
} = useToggle()
const { playersStats } = useMatchPageStore()
const {
playersStats,
playingProgress,
playStatsEpisodes,
profile,
setIsPlayingFiltersEpisodes,
setPlayingData,
setWatchAllEpisodesTimer,
statsType,
} = useMatchPageStore()
const { profileId, sportType } = usePageParams()
const params = useMemo(() => (
reduce<PlayersStats, Record<string, HeaderParam>>(
@ -135,7 +147,7 @@ export const useTable = ({
tableWrapperRef.current?.scrollBy(paramColumnWidth, 0)
}
const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : round(val, 2))
const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : String(val))
const handleScroll = (e: SyntheticEvent<HTMLDivElement>) => {
const {
@ -165,6 +177,42 @@ export const useTable = ({
})
}
const handleParamClick = async (paramId: number, playerId: number) => {
setWatchAllEpisodesTimer(false)
setIsPlayingFiltersEpisodes(false)
setPlayingData({
player: {
id: playerId,
paramId,
},
team: {
id: null,
paramId: null,
},
})
try {
const events = await getStatsEvents({
matchId: profileId,
paramId,
playerId,
sportType,
teamId,
...(statsType === StatsType.CURRENT_STATS && profile?.video_bounds && (
getHalfTime(profile.video_bounds, playingProgress)
)),
})
playStatsEpisodes(events)
// eslint-disable-next-line no-empty
} catch (e) {}
}
useLayoutEffect(() => {
setParamColumnWidth(getParamColumnWidth())
}, [getParamColumnWidth, containerRef.current?.clientWidth])
useLayoutEffect(() => {
const {
clientWidth = 0,
@ -175,11 +223,7 @@ export const useTable = ({
const scrollRight = scrollWidth - (scrollLeft + clientWidth)
setShowRightArrow(scrollRight > 0)
}, [isExpanded, tableWrapperRef.current?.clientWidth, paramsCount])
useLayoutEffect(() => {
setParamColumnWidth(getParamColumnWidth())
}, [getParamColumnWidth, tableWrapperRef.current?.clientWidth])
}, [isExpanded])
useEffect(() => {
if (isExpanded && paramsCount <= DISPLAYED_PARAMS_COLUMNS) {
@ -190,6 +234,7 @@ export const useTable = ({
return {
containerRef,
getDisplayedValue,
handleParamClick,
handleScroll,
handleSortClick,
isExpanded,

@ -1,7 +1,7 @@
import { Fragment } from 'react'
import map from 'lodash/map'
// import includes from 'lodash/includes'
import includes from 'lodash/includes'
import { PlayerParam } from 'requests'
@ -30,6 +30,7 @@ import {
Arrow,
ExpandButton,
} from './styled'
import { CircleAnimationBar } from '../CircleAnimationBar'
export const PlayersTable = (props: PlayersTableProps) => {
const {
@ -37,6 +38,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
getDisplayedValue,
getPlayerName,
getPlayerParams,
handleParamClick,
handleScroll,
handleSortClick,
isExpanded,
@ -54,13 +56,11 @@ export const PlayersTable = (props: PlayersTableProps) => {
} = usePlayersTable(props)
const { translate } = useLexicsStore()
const { sportName } = usePageParams()
const { isPlayersStatsFetching } = useMatchPageStore()
if (isPlayersStatsFetching) {
return (
<Loader color={defaultTheme.colors.white} />
)
}
const {
isPlayersStatsFetching,
playingData,
watchAllEpisodesTimer,
} = useMatchPageStore()
const firstColumnWidth = isExpanded ? FIRST_COLUMN_WIDTH_EXPANDED : FIRST_COLUMN_WIDTH_DEFAULT
@ -69,6 +69,9 @@ export const PlayersTable = (props: PlayersTableProps) => {
ref={containerRef}
isExpanded={isExpanded}
>
{isPlayersStatsFetching
? <Loader color={defaultTheme.colors.white} />
: (
<TableWrapper
ref={tableWrapperRef}
isExpanded={isExpanded}
@ -152,22 +155,36 @@ export const PlayersTable = (props: PlayersTableProps) => {
{playerName}
</PlayerName>
</Cell>
{map(params, ({ id }) => {
const playerParam = getPlayerParams(player.id)[id] as PlayerParam | undefined
{map(params, (param) => {
const playerParam = getPlayerParams(player.id)[
param.id
] as PlayerParam | undefined
const value = playerParam ? getDisplayedValue(playerParam) : '-'
// eslint-disable-next-line max-len
// const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value)
const sorted = sortCondition.paramId === id
const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value)
const sorted = sortCondition.paramId === param.id
const onClick = () => {
clickable && handleParamClick(param.id, player.id)
}
return (
<Cell
columnWidth={paramColumnWidth}
key={id}
// clickable={clickable}
clickable={false}
key={param.id}
clickable={clickable}
sorted={sorted}
onClick={onClick}
>
{value}
{watchAllEpisodesTimer
&& param.id === playingData.player.paramId
&& player.id === playingData.player.id
? (
<CircleAnimationBar
text={value}
size={20}
/>
)
: value}
</Cell>
)
})}
@ -177,6 +194,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
</tbody>
</Table>
</TableWrapper>
)}
</Container>
)
}

@ -2,7 +2,7 @@ import { Link } from 'react-router-dom'
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { isIOS, isMobileDevice } from 'config'
import { customScrollbar } from 'features/Common'
import { TooltipWrapper } from 'features/Tooltip'
@ -34,8 +34,7 @@ type TableWrapperProps = {
export const TableWrapper = styled.div<TableWrapperProps>`
max-width: 100%;
max-height: calc(100vh - 203px);
border-radius: 5px;
clip-path: inset(0 0 0 0 round 5px);
overflow-x: auto;
scroll-behavior: smooth;
background:
@ -55,6 +54,18 @@ export const TableWrapper = styled.div<TableWrapperProps>`
right: 14px;
`
: '')}
${isMobileDevice
? ''
: css`
max-height: calc(100vh - 203px);
`};
${isIOS
? css`
overscroll-behavior: none;
`
: ''};
`
export const Table = styled.table`
@ -141,6 +152,7 @@ type CellContainerProps = {
export const CellContainer = styled.td.attrs(({ clickable }: CellContainerProps) => ({
...clickable && { tabIndex: 0 },
}))<CellContainerProps>`
position: relative;
display: flex;
justify-content: center;
align-items: center;
@ -177,29 +189,12 @@ export const CellContainer = styled.td.attrs(({ clickable }: CellContainerProps)
: '')}
`
export const Header = styled.thead`
position: sticky;
left: 0;
top: 0;
z-index: 2;
${CellContainer} {
background-color: #292929;
color: ${({ theme }) => theme.colors.white};
cursor: pointer;
}
${CellContainer}:first-child {
cursor: unset;
}
`
export const Row = styled.tr`
position: relative;
display: flex;
width: 100%;
height: 45px;
border-bottom: 0.5px solid ${({ theme }) => theme.colors.secondary};
border-bottom: 0.5px solid #5C5C5C;
z-index: 1;
:last-child:not(:first-child) {
@ -218,6 +213,27 @@ export const Row = styled.tr`
}
`
export const Header = styled.thead`
position: sticky;
left: 0;
top: 0;
z-index: 2;
${Row} {
border-bottom-color: ${({ theme }) => theme.colors.secondary};
}
${CellContainer} {
background-color: #292929;
color: ${({ theme }) => theme.colors.white};
cursor: pointer;
}
${CellContainer}:first-child {
cursor: unset;
}
`
export const Arrow = styled(ArrowBase)`
width: 10px;
height: 10px;

@ -1,7 +1,4 @@
import { TCircleAnimation } from 'features/CircleAnimationBar'
export type PlayersTableProps = {
circleAnimation?: TCircleAnimation,
teamId: number,
}

@ -8,7 +8,6 @@ import size from 'lodash/size'
import { T9n } from 'features/T9n'
import type { PlaylistOption } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
import type { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar'
import { CircleAnimationBar } from 'features/CircleAnimationBar'
import type { MatchInfo } from 'requests'
@ -30,25 +29,20 @@ import {
} from './styled'
type Props = {
circleAnimation?: TCircleAnimation,
onSelect: (option: PlaylistOption) => void,
profile: MatchInfo,
selectedPlaylist?: PlaylistOption,
setCircleAnimation?: TSetCircleAnimation,
}
export const TabEvents = ({
circleAnimation,
onSelect,
profile,
selectedPlaylist,
setCircleAnimation,
}: Props) => {
const {
activeStatus,
countOfFilters,
disablePlayingEpisodes,
filteredEvents,
isEmptyFilters,
isLiveMatch,
likeImage,
@ -56,6 +50,7 @@ export const TabEvents = ({
plaingOrder,
playEpisodes,
reversedGroupEvents,
setCircleAnimation,
setReversed,
setUnreversed,
setWatchAllEpisodesTimer,
@ -64,12 +59,10 @@ export const TabEvents = ({
} = useMatchPageStore()
useEffect(() => {
if (setCircleAnimation) {
setCircleAnimation((state) => ({
...state,
plaingOrder,
}))
}
}, [setCircleAnimation, plaingOrder])
if (!profile) return null
@ -101,15 +94,11 @@ export const TabEvents = ({
<EpisodesCount>
{size(flatten(reversedGroupEvents))} <T9n t='episodes_selected' />
</EpisodesCount>
<WatchButton onClick={playEpisodes}>
<WatchButton onClick={() => playEpisodes()}>
<T9n t='watch_all' />
</WatchButton>
{watchAllEpisodesTimer && (
<CircleAnimationBar
filteredEvents={filteredEvents}
circleAnimation={circleAnimation}
setWatchAllEpisodesTimer={setWatchAllEpisodesTimer}
/>
<CircleAnimationBar />
)}
</SelectedEpisodes>
)}

@ -10,7 +10,6 @@ import { useModalRoot } from 'hooks'
import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Name } from 'features/Name'
import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar'
import { useLexicsStore } from 'features/LexicsStore'
import { Tabs } from './config'
@ -38,12 +37,7 @@ const tabPanes = {
[Tabs.TEAM2]: (props: ComponentProps<typeof PlayersTable>) => <PlayersTable {...props} />,
}
type Props = {
circleAnimation?: TCircleAnimation,
setCircleAnimation?: TSetCircleAnimation,
}
export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => {
export const TabStats = () => {
const {
isFinalStatsType,
isTooltipShown,
@ -157,8 +151,6 @@ export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => {
</Header>
<TabPane
teamId={selectedTab === Tabs.TEAM1 ? team1.id : team2.id}
circleAnimation={circleAnimation}
setCircleAnimation={setCircleAnimation}
/>
{isTooltipShown && modalRoot.current && createPortal(
<Tooltip style={tooltipStyle}>

@ -0,0 +1,163 @@
import { Fragment, useRef } from 'react'
import isNumber from 'lodash/isNumber'
import { KEYBOARD_KEYS } from 'config'
import type { Param, TeamStatItem } from 'requests'
import { getStatsEvents } from 'requests'
import { usePageParams, useEventListener } from 'hooks'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import { useMatchPageStore } from 'features/MatchPage/store'
import { StatsType } from '../TabStats/config'
import { CircleAnimationBar } from '../CircleAnimationBar'
import {
CellContainer,
ParamValueContainer,
ParamValue,
Divider,
} from './styled'
type CellProps = {
teamId: number,
teamStatItem: TeamStatItem | null,
}
export const Cell = ({
teamId,
teamStatItem,
}: CellProps) => {
const paramValueContainerRef = useRef(null)
const { profileId, sportType } = usePageParams()
const {
playingData,
playingProgress,
playStatsEpisodes,
profile,
setIsPlayingFiltersEpisodes,
setPlayingData,
setWatchAllEpisodesTimer,
statsType,
watchAllEpisodesTimer,
} = useMatchPageStore()
const isClickable = (param: Param) => (
Boolean(param.val) && param.clickable
)
const getDisplayedValue = (val: number | null) => (
isNumber(val) ? String(val) : '-'
)
const onParamClick = async (param: Param) => {
if (!isClickable(param)) return
setWatchAllEpisodesTimer(false)
setIsPlayingFiltersEpisodes(false)
setPlayingData({
player: {
id: null,
paramId: null,
},
team: {
id: teamId,
paramId: param.id,
},
})
try {
const events = await getStatsEvents({
matchId: profileId,
paramId: param.id,
sportType,
teamId,
...(statsType === StatsType.CURRENT_STATS && profile?.video_bounds && (
getHalfTime(profile.video_bounds, playingProgress)
)),
})
playStatsEpisodes(events)
// eslint-disable-next-line no-empty
} catch (e) {}
}
useEventListener({
callback: (e) => {
if (e.key !== KEYBOARD_KEYS.Enter || !teamStatItem) return
const paramId = Number((e.target as HTMLElement).dataset.paramId)
const param = paramId && (teamStatItem.param1.id === paramId
? teamStatItem.param1
: teamStatItem.param2)
param && onParamClick(param)
},
event: 'keydown',
target: paramValueContainerRef,
})
if (!teamStatItem) return null
return (
<CellContainer>
<ParamValueContainer ref={paramValueContainerRef}>
{watchAllEpisodesTimer
&& playingData.team.paramId === teamStatItem.param1.id
&& playingData.team.id === teamId
? (
<ParamValue>
<CircleAnimationBar
text={getDisplayedValue(teamStatItem.param1.val)}
size={20}
/>
</ParamValue>
)
: (
<ParamValue
clickable={isClickable(teamStatItem.param1)}
onClick={() => onParamClick(teamStatItem.param1)}
data-param-id={teamStatItem.param1.id}
>
{getDisplayedValue(teamStatItem.param1.val)}
</ParamValue>
)}
{teamStatItem.param2 && (
<Fragment>
{watchAllEpisodesTimer
&& playingData.team.paramId === teamStatItem.param2.id
&& playingData.team.id === teamId
? (
<ParamValue>
<CircleAnimationBar
text={getDisplayedValue(teamStatItem.param2.val)}
size={20}
/>
</ParamValue>
)
: (
<Fragment>
<Divider>/</Divider>
<ParamValue
clickable={isClickable(teamStatItem.param2)}
onClick={() => onParamClick(teamStatItem.param2!)}
data-param-id={teamStatItem.param2.id}
>
{getDisplayedValue(teamStatItem.param2.val)}
</ParamValue>
</Fragment>
)}
</Fragment>
)}
</ParamValueContainer>
</CellContainer>
)
}

@ -1,17 +1,16 @@
import isNumber from 'lodash/isNumber'
import find from 'lodash/find'
import round from 'lodash/round'
import { useEffect } from 'react'
import type { Param } from 'requests'
import find from 'lodash/find'
import { useMatchPageStore } from 'features/MatchPage/store'
export const useTeamsStatsTable = () => {
const { profile, teamsStats } = useMatchPageStore()
const getDisplayedValue = (val: any) => (
isNumber(val) ? round(val, 2) : '-'
)
const {
plaingOrder,
profile,
setCircleAnimation,
teamsStats,
} = useMatchPageStore()
const getStatItemById = (paramId: number) => {
if (!profile) return null
@ -19,13 +18,14 @@ export const useTeamsStatsTable = () => {
return find(teamsStats[profile?.team2.id], ({ param1 }) => param1.id === paramId) || null
}
const isClickable = (param: Param) => (
Boolean(param.val) && param.clickable
)
useEffect(() => {
setCircleAnimation((state) => ({
...state,
plaingOrder,
}))
}, [setCircleAnimation, plaingOrder])
return {
getDisplayedValue,
getStatItemById,
isClickable,
}
}

@ -1,5 +1,3 @@
import { Fragment } from 'react'
import map from 'lodash/map'
import { useMatchPageStore } from 'features/MatchPage/store'
@ -7,34 +5,27 @@ import { useLexicsStore } from 'features/LexicsStore'
import { Loader } from 'features/Loader'
import { defaultTheme } from 'features/Theme/config'
import { Props } from './types'
import { useTeamsStatsTable } from './hooks'
import { Cell } from './Cell'
import {
Container,
TableWrapper,
Table,
Header,
Row,
Cell,
CellContainer,
TeamShortName,
ParamValueContainer,
ParamValue,
StatItemTitle,
Divider,
} from './styled'
export const TeamsStatsTable = (props: Props) => {
export const TeamsStatsTable = () => {
const {
isTeamsStatsFetching,
profile,
teamsStats,
} = useMatchPageStore()
const {
getDisplayedValue,
getStatItemById,
// isClickable,
} = useTeamsStatsTable()
const { getStatItemById } = useTeamsStatsTable()
const { shortSuffix } = useLexicsStore()
@ -52,19 +43,19 @@ export const TeamsStatsTable = (props: Props) => {
<Table role='marquee' aria-live='off'>
<Header>
<Row>
<Cell as='th'>
<CellContainer as='th'>
<TeamShortName
nameObj={profile.team1}
prefix='abbrev_'
/>
</Cell>
<Cell as='th' />
<Cell as='th'>
</CellContainer>
<CellContainer as='th' />
<CellContainer as='th'>
<TeamShortName
nameObj={profile.team2}
prefix='abbrev_'
/>
</Cell>
</CellContainer>
</Row>
</Header>
@ -75,55 +66,19 @@ export const TeamsStatsTable = (props: Props) => {
return (
<Row key={team1StatItem.param1.id}>
<Cell>
<ParamValueContainer>
<ParamValue
// clickable={isClickable(team1StatItem.param1)}
clickable={false}
>
{getDisplayedValue(team1StatItem.param1.val)}
</ParamValue>
{team1StatItem.param2 && (
<Fragment>
<Divider>/</Divider>
<ParamValue
// clickable={isClickable(team1StatItem.param2)}
clickable={false}
>
{getDisplayedValue(team1StatItem.param2.val)}
</ParamValue>
</Fragment>
)}
</ParamValueContainer>
</Cell>
<Cell
teamStatItem={team1StatItem}
teamId={profile.team1.id}
/>
<Cell>
<CellContainer>
<StatItemTitle>{statItemTitle}</StatItemTitle>
</Cell>
</CellContainer>
<Cell>
{team2StatItem && (
<ParamValueContainer>
<ParamValue
// clickable={isClickable(team2StatItem.param1)}
clickable={false}
>
{getDisplayedValue(team2StatItem.param1.val)}
</ParamValue>
{team2StatItem.param2 && (
<Fragment>
<Divider>/</Divider>
<ParamValue
// clickable={isClickable(team2StatItem.param2)}
clickable={false}
>
{getDisplayedValue(team2StatItem.param2.val)}
</ParamValue>
</Fragment>
)}
</ParamValueContainer>
)}
</Cell>
<Cell
teamStatItem={team2StatItem}
teamId={profile.team2.id}
/>
</Row>
)
})}

@ -1,5 +1,7 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { Name } from 'features/Name'
import { customScrollbar } from 'features/Common'
@ -7,12 +9,17 @@ export const Container = styled.div``
export const TableWrapper = styled.div`
width: 100%;
max-height: calc(100vh - 203px);
overflow: auto;
font-size: 11px;
border-radius: 5px;
clip-path: inset(0 0 0 0 round 5px);
background-color: #333333;
${isMobileDevice
? ''
: css`
max-height: calc(100vh - 203px);
`};
${customScrollbar}
`
@ -29,12 +36,11 @@ export const TeamShortName = styled(Name)`
letter-spacing: -0.078px;
text-transform: uppercase;
font-weight: 600;
opacity: 0.5;
`
export const Cell = styled.td`
export const CellContainer = styled.td`
height: 45px;
border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
border-bottom: 0.5px solid #5C5C5C;
background-color: #333333;
:nth-child(2) {
@ -57,7 +63,7 @@ export const Cell = styled.td`
export const Row = styled.tr`
:last-child:not(:first-child) {
${Cell} {
${CellContainer} {
border-bottom: none;
}
}
@ -68,8 +74,9 @@ export const Header = styled.thead`
top: 0;
z-index: 1;
${Cell} {
${CellContainer} {
background-color: #292929;
border-bottom-color: ${({ theme }) => theme.colors.secondary};
}
`
@ -82,6 +89,11 @@ type TParamValue = {
export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({
...clickable && { tabIndex: 0 },
}))<TParamValue>`
display: inline-block;
width: 15px;
height: 15px;
text-align: center;
position: relative;
font-weight: 600;
color: ${({ clickable, theme }) => (clickable ? '#5EB2FF' : theme.colors.white)};

@ -1,6 +0,0 @@
import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar'
export type Props = {
circleAnimation?: TCircleAnimation,
setCircleAnimation?: TSetCircleAnimation,
}

@ -1,8 +1,4 @@
import {
useEffect,
useMemo,
useState,
} from 'react'
import { useEffect, useMemo } from 'react'
import reduce from 'lodash/reduce'
import isEmpty from 'lodash/isEmpty'
@ -19,10 +15,11 @@ export const useMatchSidePlaylists = () => {
isEmptyPlayersStats,
matchPlaylists: playlists,
profile: matchProfile,
selectedTab,
setSelectedTab,
teamsStats,
tournamentData,
} = useMatchPageStore()
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH)
const playListFilter = useMemo(() => reduce(
playlists.match,
@ -84,6 +81,7 @@ export const useMatchSidePlaylists = () => {
isPlayersTabVisible,
isStatsTabVisible,
isWatchTabVisible,
setSelectedTab,
])
useEffect(() => {

@ -4,7 +4,6 @@ import {
useState,
} from 'react'
import type { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar'
import type { PlaylistOption } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
@ -36,22 +35,19 @@ const tabPanes = {
}
type Props = {
circleAnimation?: TCircleAnimation,
onSelect: (option: PlaylistOption) => void,
selectedPlaylist?: PlaylistOption,
setCircleAnimation?: TSetCircleAnimation,
}
export const MatchSidePlaylists = ({
circleAnimation,
onSelect,
selectedPlaylist,
setCircleAnimation,
}: Props) => {
const {
hideProfileCard,
matchPlaylists: playlists,
profile,
selectedTab,
showProfileCard,
tournamentData,
} = useMatchPageStore()
@ -64,7 +60,6 @@ export const MatchSidePlaylists = ({
isWatchTabVisible,
onTabClick,
playListFilter,
selectedTab,
} = useMatchSidePlaylists()
const TabPane = tabPanes[selectedTab]
@ -152,8 +147,6 @@ export const MatchSidePlaylists = ({
forWatchTab={selectedTab === Tabs.WATCH}
>
<TabPane
setCircleAnimation={setCircleAnimation}
circleAnimation={circleAnimation}
tournamentData={tournamentData}
onSelect={onSelect}
playlists={playlists}

@ -1,7 +1,10 @@
import styled, { css } from 'styled-components/macro'
import { devices } from 'config/devices'
import { isMobileDevice } from 'config/userAgent'
import {
isIOS,
isMobileDevice,
devices,
} from 'config'
import { customScrollbar } from 'features/Common'
import { T9n } from 'features/T9n'
@ -29,7 +32,7 @@ type TabsGroupProps = {
export const TabsGroup = styled.div.attrs({ role: 'tablist' })<TabsGroupProps>`
display: flex;
justify-content: center;
gap: 20px;
gap: ${isMobileDevice ? 30 : 20}px;
${({ hasLessThanFourTabs }) => (hasLessThanFourTabs
? css`
@ -125,7 +128,7 @@ export const Container = styled.div<TContainer>`
${isMobileDevice
? css`
padding: 0 5px;
padding-bottom: 20px;
padding-bottom: ${isIOS ? 60 : 78}px;
overflow-y: hidden;
max-height: initial;

@ -7,7 +7,6 @@ import {
import size from 'lodash/size'
import type { TSetCircleAnimation } from 'features/CircleAnimationBar'
import { useControlsVisibility } from 'features/StreamPlayer/hooks/useControlsVisibility'
import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
@ -57,22 +56,20 @@ export type Props = {
chapters: Chapters,
isOpenPopup?: boolean,
onError?: () => void,
onPlayerProgressChange?: (ms: number) => void,
onPlayingChange: (playing: boolean) => void,
profile: MatchInfo,
setCircleAnimation: TSetCircleAnimation,
}
export const useMultiSourcePlayer = ({
chapters,
onError,
onPlayerProgressChange,
onPlayingChange,
setCircleAnimation,
}: Props) => {
const {
isPlayFilterEpisodes,
playNextEpisode,
setCircleAnimation,
setPlayingProgress,
} = useMatchPageStore()
const { profileId, sportType } = usePageParams()
@ -204,7 +201,7 @@ export const useMultiSourcePlayer = ({
timeForStatistics.current = (value + chapter.startMs) / 1000
setPlayerState({ playedProgress: value })
onPlayerProgressChange?.(playedMs + chapter.startMs)
setPlayingProgress(Math.floor(value / 1000))
}
const onEnded = () => {

@ -19,12 +19,12 @@ import {
useInterval,
} from 'hooks'
import type { TSetCircleAnimation } from 'features/CircleAnimationBar'
import type { Chapters } from 'features/StreamPlayer/types'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
import { useNoNetworkPopupStore } from 'features/NoNetworkPopup'
import { useLiveMatch } from 'features/MatchPage/components/LiveMatch/hooks'
import { useLexicsStore } from 'features/LexicsStore'
import { useMatchPageStore } from 'features/MatchPage/store'
import { VIEW_INTERVAL_MS, saveMatchStats } from 'requests'
@ -62,7 +62,6 @@ export type Props = {
onPlayingChange: (playing: boolean) => void,
onProgressChange: (seconds: number) => void,
resumeFrom?: number,
setCircleAnimation: TSetCircleAnimation,
url?: string,
}
@ -73,7 +72,6 @@ export const useVideoPlayer = ({
onPlayingChange,
onProgressChange: progressChangeCallback,
resumeFrom,
setCircleAnimation,
}: Props) => {
const [{
activeChapterIndex,
@ -88,14 +86,16 @@ export const useVideoPlayer = ({
seeking,
}, setPlayerState] = useObjectState({ ...initialState, chapters: chaptersProps })
const { onPlaylistSelect } = useLiveMatch()
const { lang } = useLexicsStore()
const { profileId, sportType } = usePageParams()
const {
isPlayFilterEpisodes,
onPlaylistSelect,
playNextEpisode,
selectedPlaylist,
} = useLiveMatch()
const { lang } = useLexicsStore()
const { profileId, sportType } = usePageParams()
setCircleAnimation,
setPlayingProgress,
} = useMatchPageStore()
/** время для сохранения статистики просмотра матча */
const timeForStatistics = useRef(0)
@ -187,7 +187,7 @@ export const useVideoPlayer = ({
}
const onWaiting = () => {
setPlayerState({ buffering: true })
setPlayerState({ buffering: !ready })
}
const onPlaying = () => {
@ -225,6 +225,7 @@ export const useVideoPlayer = ({
setPlayerState({ playedProgress: value })
timeForStatistics.current = (value + chapter.startMs) / 1000
setPlayingProgress(Math.floor(value / 1000))
progressChangeCallback(value / 1000)
}
@ -425,7 +426,6 @@ export const useVideoPlayer = ({
}, [ready, videoRef])
useEffect(() => {
if (!setCircleAnimation) return
setCircleAnimation((state) => ({
...state,
playedProgress,

@ -37,7 +37,11 @@ const tournamentsWithWatermark = {
* HLS плеер, применяется на лайв и завершенных матчах
*/
export const StreamPlayer = (props: Props) => {
const { isOpenFiltersPopup, profile } = useMatchPageStore()
const {
access,
isOpenFiltersPopup,
profile,
} = useMatchPageStore()
const { user } = useAuthStore()
const {

@ -25,7 +25,7 @@ export type VideoBound = {
s: string,
}
type VideoBounds = Array<VideoBound>
export type VideoBounds = Array<VideoBound>
export type MatchInfo = {
access?: boolean,

@ -24,6 +24,7 @@ export type Player = {
nickname_eng: string | null,
nickname_rus: string | null,
num: number | null,
ord: number,
weight: number | null,
}
@ -42,12 +43,14 @@ type Response = {
type GetMatchParticipantsArgs = {
matchId: number,
period?: number,
second?: number,
sportType: SportTypes,
}
export const getMatchParticipants = async ({
matchId,
period,
second,
sportType,
}: GetMatchParticipantsArgs) => {
@ -57,7 +60,7 @@ export const getMatchParticipants = async ({
const response: Response = await callApi({
config,
url: `${STATS_API_URL}/ask/participants?sport_id=${sportType}&match_id=${matchId}${isUndefined(second) ? '' : `&second=${second}`}`,
url: `${STATS_API_URL}/ask/participants?sport_id=${sportType}&match_id=${matchId}${isUndefined(second) ? '' : `&second=${second}&half=${period}`}`,
})
if (response.error) Promise.reject(response)

@ -30,6 +30,7 @@ type Response = {
type GetPlayersStatsArgs = {
matchId: number,
period?: number,
second?: number,
sportName: string,
teamId: number,
@ -37,6 +38,7 @@ type GetPlayersStatsArgs = {
export const getPlayersStats = async ({
matchId,
period,
second,
sportName,
teamId,
@ -47,7 +49,7 @@ export const getPlayersStats = async ({
const response: Response = await callApi({
config,
url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/${teamId}/players/stats${isUndefined(second) ? '' : `?second=${second}`}`,
url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/${teamId}/players/stats${isUndefined(second) ? '' : `?second=${second}&half=${period}`}`,
})
if (response.error) Promise.reject(response)

@ -0,0 +1,57 @@
import { SportTypes, STATS_API_URL } from 'config'
import { callApi } from 'helpers'
import { Episodes } from './getMatchPlaylists'
type Response = {
data?: Episodes,
error?: {
code: string,
message: string,
},
}
type GetStatsEventsArgs = {
matchId: number,
paramId: number,
period?: number,
playerId?: number,
second?: number,
sportType: SportTypes,
teamId: number,
}
export const getStatsEvents = async ({
matchId,
paramId,
period,
playerId,
second,
sportType,
teamId,
}: GetStatsEventsArgs) => {
const config = {
body: {
half: period,
match_id: matchId,
match_second: second,
offset_end: 6,
offset_start: 6,
option_id: 0,
param_id: paramId,
player_id: playerId,
sport_id: sportType,
team_id: teamId,
},
}
const response: Response = await callApi({
config,
url: `${STATS_API_URL}/video`,
})
if (response.error) Promise.reject(response)
return Promise.resolve(response.data || [])
}

@ -34,12 +34,14 @@ type Response = {
type GetTeamsStatsArgs = {
matchId: number,
period?: number,
second?: number,
sportName: string,
}
export const getTeamsStats = async ({
matchId,
period,
second,
sportName,
}: GetTeamsStatsArgs) => {
@ -49,7 +51,7 @@ export const getTeamsStats = async ({
const response: Response = await callApi({
config,
url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/stats?group_num=0${isUndefined(second) ? '' : `&second=${second}`}`,
url: `${STATS_API_URL}/${sportName}/matches/${matchId}/teams/stats?group_num=0${isUndefined(second) ? '' : `&second=${second}&half=${period}`}`,
})
if (response.error) Promise.reject(response)

@ -30,3 +30,4 @@ export * from './getTokenVirtualUser'
export * from './getTeamsStats'
export * from './getPlayersStats'
export * from './getMatchParticipants'
export * from './getStatsEvents'

Loading…
Cancel
Save