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

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

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

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

@ -16,12 +16,9 @@ import { usePlaylistLogger } from './usePlaylistLogger'
export const useLiveMatch = () => { export const useLiveMatch = () => {
const { const {
handlePlaylistClick, handlePlaylistClick,
isPlayFilterEpisodes,
playNextEpisode,
profile, profile,
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
setPlayingProgress,
} = useMatchPageStore() } = useMatchPageStore()
const { profileId: matchId, sportType } = usePageParams() const { profileId: matchId, sportType } = usePageParams()
const resume = useResumeUrlParam() const resume = useResumeUrlParam()
@ -46,7 +43,7 @@ export const useLiveMatch = () => {
} = usePlaylistLogger() } = usePlaylistLogger()
const { const {
onPlayerProgressChange: playerProgressChange, onPlayerProgressChange,
onPlayingChange: notifyProgressLogger, onPlayingChange: notifyProgressLogger,
} = usePlayerProgressReporter() } = usePlayerProgressReporter()
@ -66,22 +63,13 @@ export const useLiveMatch = () => {
} }
handlePlaylistClick(playlist, e) handlePlaylistClick(playlist, e)
} }
const onPlayerProgressChange = (seconds: number, period = 0) => {
playerProgressChange(seconds, period)
setPlayingProgress(seconds * 1000)
}
return { return {
chapters, chapters,
isPlayFilterEpisodes,
onDurationChange, onDurationChange,
onPlayerProgressChange, onPlayerProgressChange,
onPlayingChange, onPlayingChange,
onPlaylistSelect, onPlaylistSelect,
playNextEpisode,
resume: resume ?? fromStartIfStreamPaused, resume: resume ?? fromStartIfStreamPaused,
selectedPlaylist,
streamUrl: `${API_ROOT}/video/stream/${sportType}/${matchId}.m3u8`, 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 isEmpty from 'lodash/isEmpty'
import type { TCircleAnimation } from 'features/CircleAnimationBar'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { StreamPlayer } from 'features/StreamPlayer' import { StreamPlayer } from 'features/StreamPlayer'
import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer' import { YoutubePlayer } from 'features/StreamPlayer/components/YoutubePlayer'
@ -15,8 +13,6 @@ import { useLiveMatch } from './hooks'
import { MatchDescription } from '../MatchDescription' import { MatchDescription } from '../MatchDescription'
export const LiveMatch = () => { export const LiveMatch = () => {
const [circleAnimation, setCircleAnimation] = useState<TCircleAnimation>(initialCircleAnimation)
const { const {
profile, profile,
selectedPlaylist, selectedPlaylist,
@ -43,12 +39,10 @@ export const LiveMatch = () => {
onProgressChange={onPlayerProgressChange} onProgressChange={onPlayerProgressChange}
resumeFrom={resume} resumeFrom={resume}
url={streamUrl} url={streamUrl}
setCircleAnimation={setCircleAnimation}
/> />
) : ( ) : (
!isEmpty(chapters) && ( !isEmpty(chapters) && (
<StreamPlayer <StreamPlayer
setCircleAnimation={setCircleAnimation}
onDurationChange={onDurationChange} onDurationChange={onDurationChange}
onPlayingChange={onPlayingChange} onPlayingChange={onPlayingChange}
onProgressChange={onPlayerProgressChange} onProgressChange={onPlayerProgressChange}
@ -62,8 +56,6 @@ export const LiveMatch = () => {
</Container> </Container>
<MatchSidePlaylists <MatchSidePlaylists
setCircleAnimation={setCircleAnimation}
circleAnimation={circleAnimation}
onSelect={onPlaylistSelect} onSelect={onPlaylistSelect}
selectedPlaylist={selectedPlaylist} 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 { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { useAuthStore } from 'features/AuthStore' 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 type { MatchInfo } from 'requests/getMatchInfo'
import { getMatchInfo } from 'requests/getMatchInfo' import { getMatchInfo } from 'requests/getMatchInfo'
import { getViewMatchDuration } from 'requests/getViewMatchDuration' import { getViewMatchDuration } from 'requests/getViewMatchDuration'
import { usePageParams } from 'hooks/usePageParams' import { usePageParams, useToggle } from 'hooks'
import { useToggle } from 'hooks/useToggle'
import { parseDate } from 'helpers/parseDate' import { parseDate } from 'helpers/parseDate'
@ -24,6 +26,31 @@ import { useTournamentData } from './useTournamentData'
import { useMatchData } from './useMatchData' import { useMatchData } from './useMatchData'
import { useFiltersPopup } from './useFitersPopup' import { useFiltersPopup } from './useFitersPopup'
import { useTabEvents } from './useTabEvents' 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 const ACCESS_TIME = 60
@ -31,6 +58,15 @@ export const useMatchPage = () => {
const [matchProfile, setMatchProfile] = useState<MatchInfo>(null) const [matchProfile, setMatchProfile] = useState<MatchInfo>(null)
const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false) const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false)
const [access, setAccess] = useState(true) 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 { profileId: matchId, sportType } = usePageParams()
const { user, userInfo } = useAuthStore() const { user, userInfo } = useAuthStore()
@ -47,6 +83,7 @@ export const useMatchPage = () => {
matchPlaylists, matchPlaylists,
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
setSelectedPlaylist,
} = useMatchData(matchProfile) } = useMatchData(matchProfile)
const profile = matchProfile const profile = matchProfile
@ -63,9 +100,9 @@ export const useMatchPage = () => {
filters, filters,
isAllActionsChecked, isAllActionsChecked,
isEmptyFilters, isEmptyFilters,
isFirstTeamPlayersChecked,
isOpen: isOpenFiltersPopup, isOpen: isOpenFiltersPopup,
resetEvents, isSecondTeamPlayersChecked,
resetPlayers,
toggle: togglePopup, toggle: togglePopup,
toggleActiveEvents, toggleActiveEvents,
toggleActivePlayers, toggleActivePlayers,
@ -131,24 +168,56 @@ export const useMatchPage = () => {
return () => clearInterval(getIntervalMatch) return () => clearInterval(getIntervalMatch)
}) })
const disablePlayingEpisodes = () => {
setIsPlayingFiltersEpisodes(false)
setWatchAllEpisodesTimer(false)
setCircleAnimation(initialCircleAnimation)
}
const { const {
events, circleAnimation: statsCircleAnimation,
handlePlaylistClick, filteredEvents: statsFilteredEvents,
isEmptyPlayersStats,
isPlayersStatsFetching, isPlayersStatsFetching,
isPlayFilterEpisodes: isStatsPlayFilterEpisodes,
isTeamsStatsFetching, 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, playersData,
playersStats, playersStats,
} = usePlayersStats({
matchProfile,
playingProgress,
selectedPlaylist, selectedPlaylist,
setFullMatchPlaylistDuration, setIsPlayersStatsFetching,
setPlayingProgress,
statsType, statsType,
teamsStats, })
toggleStatsType,
} = useMatchData(matchProfile)
const profile = matchProfile
const isStarted = useMemo(() => ( const isStarted = useMemo(() => (
profile?.date profile?.date
@ -171,9 +240,6 @@ export const useMatchPage = () => {
} }
}, [events, filters]) }, [events, filters])
const [plaingOrder, setPlaingOrder] = useState(0)
const [isPlayFilterEpisodes, setIsPlayinFiltersEpisodes] = useState(false)
const { const {
activeStatus, activeStatus,
episodesToPlay, episodesToPlay,
@ -185,16 +251,12 @@ export const useMatchPage = () => {
setUnreversed, setUnreversed,
} = useTabEvents({ events: filteredEvents, profile }) } = useTabEvents({ events: filteredEvents, profile })
useEffect(() => {
if (plaingOrder > episodesToPlay.length) setPlaingOrder(0)
}, [plaingOrder, episodesToPlay])
const playNextEpisode = (order?: number) => { const playNextEpisode = (order?: number) => {
const isLastEpisode = plaingOrder === episodesToPlay.length const isLastEpisode = plaingOrder === episodesToPlay.length
const currentOrder = order === 0 ? order : plaingOrder const currentOrder = order === 0 ? order : plaingOrder
if (isLastEpisode) { if (isLastEpisode) {
setIsPlayinFiltersEpisodes(false) setIsPlayingFiltersEpisodes(false)
return return
} }
@ -202,11 +264,16 @@ export const useMatchPage = () => {
setPlaingOrder(currentOrder + 1) setPlaingOrder(currentOrder + 1)
} }
const playEpisodes = () => { const playEpisodes = () => {
setPlayingData(initPlayingData)
setStatsWatchAllEpisodesTimer(true)
setStatsIsPlayinFiltersEpisodes(false)
setStatsCircleAnimation(initialCircleAnimation)
if (!watchAllEpisodesTimer) { if (!watchAllEpisodesTimer) {
setWatchAllEpisodesTimer(true) setWatchAllEpisodesTimer(true)
} }
setIsPlayinFiltersEpisodes(true) setIsPlayingFiltersEpisodes(true)
if (matchProfile?.live) { if (matchProfile?.live) {
handlePlaylistClick({ handlePlaylistClick({
@ -219,10 +286,6 @@ export const useMatchPage = () => {
} }
} }
const disablePlayingEpisodes = () => {
setIsPlayinFiltersEpisodes(false)
}
return { return {
access, access,
activeEvents, activeEvents,
@ -232,41 +295,53 @@ export const useMatchPage = () => {
allActionsToggle, allActionsToggle,
allPlayersToggle, allPlayersToggle,
applyFilters, applyFilters,
circleAnimation: isStatsTab ? statsCircleAnimation : circleAnimation,
closePopup, closePopup,
countOfFilters, countOfFilters,
disablePlayingEpisodes, disablePlayingEpisodes,
events, events,
filteredEvents, filteredEvents: isStatsTab ? statsFilteredEvents : filteredEvents,
handlePlaylistClick, handlePlaylistClick,
hideProfileCard, hideProfileCard,
isAllActionsChecked, isAllActionsChecked,
isEmptyFilters, isEmptyFilters,
isEmptyPlayersStats, isEmptyPlayersStats,
isFirstTeamPlayersChecked,
isLiveMatch, isLiveMatch,
isOpenFiltersPopup, isOpenFiltersPopup,
isPlayFilterEpisodes, isPlayFilterEpisodes: isStatsTab ? isStatsPlayFilterEpisodes : isPlayFilterEpisodes,
isPlayersStatsFetching, isPlayersStatsFetching,
isSecondTeamPlayersChecked,
isStarted, isStarted,
isTeamsStatsFetching, isTeamsStatsFetching,
likeImage, likeImage,
likeToggle, likeToggle,
matchPlaylists, matchPlaylists,
plaingOrder, plaingOrder: isStatsTab ? statsPlaingOrder : plaingOrder,
playEpisodes, playEpisodes,
playNextEpisode, playNextEpisode: isStatsTab ? playStatsNextEpisode : playNextEpisode,
playStatsEpisodes,
playersData, playersData,
playersStats, playersStats,
playingData,
playingProgress,
profile, profile,
profileCardShown, profileCardShown,
reversedGroupEvents, reversedGroupEvents,
selectedPlaylist, selectedPlaylist,
selectedTab,
setCircleAnimation: isStatsTab ? setStatsCircleAnimation : setCircleAnimation,
setFullMatchPlaylistDuration, setFullMatchPlaylistDuration,
setIsPlayinFiltersEpisodes, setIsPlayingFiltersEpisodes: isStatsTab
setPlaingOrder, ? setStatsIsPlayinFiltersEpisodes
: setIsPlayersStatsFetching,
setPlaingOrder: isStatsTab ? setStatsPlaingOrder : setPlaingOrder,
setPlayingData,
setPlayingProgress, setPlayingProgress,
setReversed, setReversed,
setSelectedTab,
setUnreversed, setUnreversed,
setWatchAllEpisodesTimer, setWatchAllEpisodesTimer: isStatsTab ? setStatsWatchAllEpisodesTimer : setWatchAllEpisodesTimer,
showProfileCard, showProfileCard,
statsType, statsType,
teamsStats, teamsStats,
@ -277,6 +352,6 @@ export const useMatchPage = () => {
tournamentData, tournamentData,
uniqEvents, uniqEvents,
user, user,
watchAllEpisodesTimer, watchAllEpisodesTimer: isStatsTab ? statsWatchAllEpisodesTimer : watchAllEpisodesTimer,
} }
} }

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

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

@ -1,11 +1,62 @@
import type { Dispatch, SetStateAction } from 'react'
import { useState } 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' 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 [statsType, setStatsType] = useState<StatsType>(StatsType.FINAL_STATS)
const [isPlayersStatsFetching, setIsPlayersStatsFetching] = useState(false) const [isPlayersStatsFetching, setIsPlayersStatsFetching] = useState(false)
const [isTeamsStatsFetching, setIsTeamsStatsFetching] = 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 const isFinalStatsType = statsType === StatsType.FINAL_STATS
@ -17,12 +68,84 @@ export const useStatsTab = () => {
setIsPlayersStatsFetching(true) 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 { return {
circleAnimation,
filteredEvents,
isPlayFilterEpisodes,
isPlayersStatsFetching, isPlayersStatsFetching,
isTeamsStatsFetching, isTeamsStatsFetching,
plaingOrder,
playEpisodes,
playNextEpisode,
setCircleAnimation,
setIsPlayersStatsFetching, setIsPlayersStatsFetching,
setIsPlayingFiltersEpisodes,
setIsTeamsStatsFetching, setIsTeamsStatsFetching,
setPlaingOrder,
setWatchAllEpisodesTimer,
statsType, statsType,
toggleStatsType, toggleStatsType,
watchAllEpisodesTimer,
} }
} }

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

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

@ -1,10 +1,14 @@
import type { PropsWithChildren, HTMLProps } from 'react' import type { PropsWithChildren, HTMLProps } from 'react'
import { memo } from 'react' import { memo, useRef } from 'react'
import { createPortal } from 'react-dom' 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' import { Tooltip, CellContainer } from './styled'
@ -27,6 +31,8 @@ const CellFC = ({
sorted, sorted,
tooltipText, tooltipText,
}: PropsWithChildren<CellProps>) => { }: PropsWithChildren<CellProps>) => {
const cellRef = useRef<HTMLTableCellElement | null>(null)
const { const {
isTooltipShown, isTooltipShown,
onMouseLeave, onMouseLeave,
@ -36,8 +42,20 @@ const CellFC = ({
const modalRoot = useModalRoot() const modalRoot = useModalRoot()
useEventListener({
callback: (e) => {
if (e.key !== KEYBOARD_KEYS.Enter) return
// @ts-expect-error
onClick()
},
event: 'keydown',
target: cellRef,
})
return ( return (
<CellContainer <CellContainer
ref={cellRef}
as={as} as={as}
onClick={onClick} onClick={onClick}
clickable={clickable} 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 type { SortCondition, PlayersTableProps } from '../types'
import { usePlayers } from './usePlayers' import { usePlayers } from './usePlayers'
@ -11,6 +13,8 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
paramId: null, paramId: null,
}) })
const { plaingOrder, setCircleAnimation } = useMatchPageStore()
const { const {
getPlayerName, getPlayerName,
getPlayerParams, getPlayerParams,
@ -20,6 +24,7 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
const { const {
containerRef, containerRef,
getDisplayedValue, getDisplayedValue,
handleParamClick,
handleScroll, handleScroll,
handleSortClick, handleSortClick,
isExpanded, isExpanded,
@ -37,11 +42,19 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
teamId, teamId,
}) })
useEffect(() => {
setCircleAnimation((state) => ({
...state,
plaingOrder,
}))
}, [setCircleAnimation, plaingOrder])
return { return {
containerRef, containerRef,
getDisplayedValue, getDisplayedValue,
getPlayerName, getPlayerName,
getPlayerParams, getPlayerParams,
handleParamClick,
handleScroll, handleScroll,
handleSortClick, handleSortClick,
isExpanded, isExpanded,

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

@ -17,17 +17,18 @@ import isNil from 'lodash/isNil'
import reduce from 'lodash/reduce' import reduce from 'lodash/reduce'
import forEach from 'lodash/forEach' import forEach from 'lodash/forEach'
import values from 'lodash/values' import values from 'lodash/values'
import round from 'lodash/round'
import map from 'lodash/map' import map from 'lodash/map'
import { isMobileDevice } from 'config' import { isMobileDevice } from 'config'
import type { PlayerParam, PlayersStats } from 'requests' 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 { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsConfig } from 'features/LexicsStore' import { useLexicsConfig } from 'features/LexicsStore'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import type { SortCondition } from '../types' import type { SortCondition } from '../types'
import { import {
@ -36,6 +37,7 @@ import {
DISPLAYED_PARAMS_COLUMNS, DISPLAYED_PARAMS_COLUMNS,
SCROLLBAR_WIDTH, SCROLLBAR_WIDTH,
} from '../config' } from '../config'
import { StatsType } from '../../TabStats/config'
type UseTableArgs = { type UseTableArgs = {
setSortCondition: Dispatch<SetStateAction<SortCondition>>, setSortCondition: Dispatch<SetStateAction<SortCondition>>,
@ -52,7 +54,7 @@ export const useTable = ({
const tableWrapperRef = useRef<HTMLDivElement>(null) const tableWrapperRef = useRef<HTMLDivElement>(null)
const [showLeftArrow, setShowLeftArrow] = useState(false) const [showLeftArrow, setShowLeftArrow] = useState(false)
const [showRightArrow, setShowRightArrow] = useState(false) const [showRightArrow, setShowRightArrow] = useState(true)
const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT) const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT)
const { const {
@ -60,7 +62,17 @@ export const useTable = ({
isOpen: isExpanded, isOpen: isExpanded,
toggle: toggleIsExpanded, toggle: toggleIsExpanded,
} = useToggle() } = useToggle()
const { playersStats } = useMatchPageStore() const {
playersStats,
playingProgress,
playStatsEpisodes,
profile,
setIsPlayingFiltersEpisodes,
setPlayingData,
setWatchAllEpisodesTimer,
statsType,
} = useMatchPageStore()
const { profileId, sportType } = usePageParams()
const params = useMemo(() => ( const params = useMemo(() => (
reduce<PlayersStats, Record<string, HeaderParam>>( reduce<PlayersStats, Record<string, HeaderParam>>(
@ -135,7 +147,7 @@ export const useTable = ({
tableWrapperRef.current?.scrollBy(paramColumnWidth, 0) 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 handleScroll = (e: SyntheticEvent<HTMLDivElement>) => {
const { 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(() => { useLayoutEffect(() => {
const { const {
clientWidth = 0, clientWidth = 0,
@ -175,11 +223,7 @@ export const useTable = ({
const scrollRight = scrollWidth - (scrollLeft + clientWidth) const scrollRight = scrollWidth - (scrollLeft + clientWidth)
setShowRightArrow(scrollRight > 0) setShowRightArrow(scrollRight > 0)
}, [isExpanded, tableWrapperRef.current?.clientWidth, paramsCount]) }, [isExpanded])
useLayoutEffect(() => {
setParamColumnWidth(getParamColumnWidth())
}, [getParamColumnWidth, tableWrapperRef.current?.clientWidth])
useEffect(() => { useEffect(() => {
if (isExpanded && paramsCount <= DISPLAYED_PARAMS_COLUMNS) { if (isExpanded && paramsCount <= DISPLAYED_PARAMS_COLUMNS) {
@ -190,6 +234,7 @@ export const useTable = ({
return { return {
containerRef, containerRef,
getDisplayedValue, getDisplayedValue,
handleParamClick,
handleScroll, handleScroll,
handleSortClick, handleSortClick,
isExpanded, isExpanded,

@ -1,7 +1,7 @@
import { Fragment } from 'react' import { Fragment } from 'react'
import map from 'lodash/map' import map from 'lodash/map'
// import includes from 'lodash/includes' import includes from 'lodash/includes'
import { PlayerParam } from 'requests' import { PlayerParam } from 'requests'
@ -30,6 +30,7 @@ import {
Arrow, Arrow,
ExpandButton, ExpandButton,
} from './styled' } from './styled'
import { CircleAnimationBar } from '../CircleAnimationBar'
export const PlayersTable = (props: PlayersTableProps) => { export const PlayersTable = (props: PlayersTableProps) => {
const { const {
@ -37,6 +38,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
getDisplayedValue, getDisplayedValue,
getPlayerName, getPlayerName,
getPlayerParams, getPlayerParams,
handleParamClick,
handleScroll, handleScroll,
handleSortClick, handleSortClick,
isExpanded, isExpanded,
@ -54,13 +56,11 @@ export const PlayersTable = (props: PlayersTableProps) => {
} = usePlayersTable(props) } = usePlayersTable(props)
const { translate } = useLexicsStore() const { translate } = useLexicsStore()
const { sportName } = usePageParams() const { sportName } = usePageParams()
const { isPlayersStatsFetching } = useMatchPageStore() const {
isPlayersStatsFetching,
if (isPlayersStatsFetching) { playingData,
return ( watchAllEpisodesTimer,
<Loader color={defaultTheme.colors.white} /> } = useMatchPageStore()
)
}
const firstColumnWidth = isExpanded ? FIRST_COLUMN_WIDTH_EXPANDED : FIRST_COLUMN_WIDTH_DEFAULT const firstColumnWidth = isExpanded ? FIRST_COLUMN_WIDTH_EXPANDED : FIRST_COLUMN_WIDTH_DEFAULT
@ -69,114 +69,132 @@ export const PlayersTable = (props: PlayersTableProps) => {
ref={containerRef} ref={containerRef}
isExpanded={isExpanded} isExpanded={isExpanded}
> >
<TableWrapper {isPlayersStatsFetching
ref={tableWrapperRef} ? <Loader color={defaultTheme.colors.white} />
isExpanded={isExpanded} : (
onScroll={handleScroll} <TableWrapper
> ref={tableWrapperRef}
{!isExpanded && ( isExpanded={isExpanded}
<Fragment> onScroll={handleScroll}
{showRightArrow && ( >
<ArrowButtonRight {!isExpanded && (
aria-label='Scroll to right' <Fragment>
onClick={slideRight} {showRightArrow && (
> <ArrowButtonRight
<Arrow direction='right' /> aria-label='Scroll to right'
</ArrowButtonRight> onClick={slideRight}
)}
</Fragment>
)}
<Table role='marquee' aria-live='off'>
<Header>
<Row>
<Cell
as='th'
columnWidth={firstColumnWidth}
>
{showLeftArrow && (
<ArrowButtonLeft
aria-label='Scroll to left'
onClick={slideLeft}
> >
<Arrow direction='left' /> <Arrow direction='right' />
</ArrowButtonLeft> </ArrowButtonRight>
)} )}
{showExpandButton && ( </Fragment>
<ExpandButton )}
isExpanded={isExpanded} <Table role='marquee' aria-live='off'>
aria-label={isExpanded ? 'Reduce' : 'Expand'} <Header>
onClick={toggleIsExpanded} <Row>
<Cell
as='th'
columnWidth={firstColumnWidth}
> >
<Arrow direction={isExpanded ? 'right' : 'left'} /> {showLeftArrow && (
<Arrow direction={isExpanded ? 'right' : 'left'} /> <ArrowButtonLeft
</ExpandButton> aria-label='Scroll to left'
)} onClick={slideLeft}
</Cell> >
{map(params, ({ <Arrow direction='left' />
id, </ArrowButtonLeft>
lexic, )}
lexica_short, {showExpandButton && (
}) => ( <ExpandButton
<Cell isExpanded={isExpanded}
as='th' aria-label={isExpanded ? 'Reduce' : 'Expand'}
key={id} onClick={toggleIsExpanded}
columnWidth={paramColumnWidth} >
onClick={handleSortClick(id)} <Arrow direction={isExpanded ? 'right' : 'left'} />
sorted={sortCondition.paramId === id} <Arrow direction={isExpanded ? 'right' : 'left'} />
tooltipText={translate(lexic)} </ExpandButton>
anchorId={`param_${id}`} )}
>
<ParamShortTitle
id={`param_${id}`}
t={lexica_short || ''}
sorted={sortCondition.paramId === id}
sortDirection={sortCondition.dir}
showLeftArrow={showLeftArrow}
/>
</Cell>
))}
</Row>
</Header>
<tbody>
{map(players, (player) => {
const playerName = getPlayerName(player)
const playerNum = player.num ?? player.club_shirt_num
const playerProfileUrl = `/${sportName}/players/${player.id}`
return (
<Row key={player.id}>
<Cell columnWidth={firstColumnWidth}>
<PlayerNum>{playerNum}</PlayerNum>{' '}
<PlayerName to={playerProfileUrl}>
{playerName}
</PlayerName>
</Cell> </Cell>
{map(params, ({ id }) => { {map(params, ({
const playerParam = getPlayerParams(player.id)[id] as PlayerParam | undefined id,
const value = playerParam ? getDisplayedValue(playerParam) : '-' lexic,
// eslint-disable-next-line max-len lexica_short,
// const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value) }) => (
const sorted = sortCondition.paramId === id <Cell
as='th'
key={id}
columnWidth={paramColumnWidth}
onClick={handleSortClick(id)}
sorted={sortCondition.paramId === id}
tooltipText={translate(lexic)}
anchorId={`param_${id}`}
>
<ParamShortTitle
id={`param_${id}`}
t={lexica_short || ''}
sorted={sortCondition.paramId === id}
sortDirection={sortCondition.dir}
showLeftArrow={showLeftArrow}
/>
</Cell>
))}
</Row>
</Header>
return ( <tbody>
<Cell {map(players, (player) => {
columnWidth={paramColumnWidth} const playerName = getPlayerName(player)
key={id} const playerNum = player.num ?? player.club_shirt_num
// clickable={clickable} const playerProfileUrl = `/${sportName}/players/${player.id}`
clickable={false}
sorted={sorted} return (
> <Row key={player.id}>
{value} <Cell columnWidth={firstColumnWidth}>
<PlayerNum>{playerNum}</PlayerNum>{' '}
<PlayerName to={playerProfileUrl}>
{playerName}
</PlayerName>
</Cell> </Cell>
) {map(params, (param) => {
})} const playerParam = getPlayerParams(player.id)[
</Row> param.id
) ] as PlayerParam | undefined
})} const value = playerParam ? getDisplayedValue(playerParam) : '-'
</tbody> // eslint-disable-next-line max-len
</Table> const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value)
</TableWrapper> const sorted = sortCondition.paramId === param.id
const onClick = () => {
clickable && handleParamClick(param.id, player.id)
}
return (
<Cell
columnWidth={paramColumnWidth}
key={param.id}
clickable={clickable}
sorted={sorted}
onClick={onClick}
>
{watchAllEpisodesTimer
&& param.id === playingData.player.paramId
&& player.id === playingData.player.id
? (
<CircleAnimationBar
text={value}
size={20}
/>
)
: value}
</Cell>
)
})}
</Row>
)
})}
</tbody>
</Table>
</TableWrapper>
)}
</Container> </Container>
) )
} }

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

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

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

@ -10,7 +10,6 @@ import { useModalRoot } from 'hooks'
import { T9n } from 'features/T9n' import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
import { Name } from 'features/Name' import { Name } from 'features/Name'
import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar'
import { useLexicsStore } from 'features/LexicsStore' import { useLexicsStore } from 'features/LexicsStore'
import { Tabs } from './config' import { Tabs } from './config'
@ -38,12 +37,7 @@ const tabPanes = {
[Tabs.TEAM2]: (props: ComponentProps<typeof PlayersTable>) => <PlayersTable {...props} />, [Tabs.TEAM2]: (props: ComponentProps<typeof PlayersTable>) => <PlayersTable {...props} />,
} }
type Props = { export const TabStats = () => {
circleAnimation?: TCircleAnimation,
setCircleAnimation?: TSetCircleAnimation,
}
export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => {
const { const {
isFinalStatsType, isFinalStatsType,
isTooltipShown, isTooltipShown,
@ -157,8 +151,6 @@ export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => {
</Header> </Header>
<TabPane <TabPane
teamId={selectedTab === Tabs.TEAM1 ? team1.id : team2.id} teamId={selectedTab === Tabs.TEAM1 ? team1.id : team2.id}
circleAnimation={circleAnimation}
setCircleAnimation={setCircleAnimation}
/> />
{isTooltipShown && modalRoot.current && createPortal( {isTooltipShown && modalRoot.current && createPortal(
<Tooltip style={tooltipStyle}> <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 { useEffect } from 'react'
import find from 'lodash/find'
import round from 'lodash/round'
import type { Param } from 'requests' import find from 'lodash/find'
import { useMatchPageStore } from 'features/MatchPage/store' import { useMatchPageStore } from 'features/MatchPage/store'
export const useTeamsStatsTable = () => { export const useTeamsStatsTable = () => {
const { profile, teamsStats } = useMatchPageStore() const {
plaingOrder,
const getDisplayedValue = (val: any) => ( profile,
isNumber(val) ? round(val, 2) : '-' setCircleAnimation,
) teamsStats,
} = useMatchPageStore()
const getStatItemById = (paramId: number) => { const getStatItemById = (paramId: number) => {
if (!profile) return null if (!profile) return null
@ -19,13 +18,14 @@ export const useTeamsStatsTable = () => {
return find(teamsStats[profile?.team2.id], ({ param1 }) => param1.id === paramId) || null return find(teamsStats[profile?.team2.id], ({ param1 }) => param1.id === paramId) || null
} }
const isClickable = (param: Param) => ( useEffect(() => {
Boolean(param.val) && param.clickable setCircleAnimation((state) => ({
) ...state,
plaingOrder,
}))
}, [setCircleAnimation, plaingOrder])
return { return {
getDisplayedValue,
getStatItemById, getStatItemById,
isClickable,
} }
} }

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

@ -1,5 +1,7 @@
import styled, { css } from 'styled-components/macro' import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config'
import { Name } from 'features/Name' import { Name } from 'features/Name'
import { customScrollbar } from 'features/Common' import { customScrollbar } from 'features/Common'
@ -7,12 +9,17 @@ export const Container = styled.div``
export const TableWrapper = styled.div` export const TableWrapper = styled.div`
width: 100%; width: 100%;
max-height: calc(100vh - 203px);
overflow: auto; overflow: auto;
font-size: 11px; font-size: 11px;
border-radius: 5px; clip-path: inset(0 0 0 0 round 5px);
background-color: #333333; background-color: #333333;
${isMobileDevice
? ''
: css`
max-height: calc(100vh - 203px);
`};
${customScrollbar} ${customScrollbar}
` `
@ -29,12 +36,11 @@ export const TeamShortName = styled(Name)`
letter-spacing: -0.078px; letter-spacing: -0.078px;
text-transform: uppercase; text-transform: uppercase;
font-weight: 600; font-weight: 600;
opacity: 0.5;
` `
export const Cell = styled.td` export const CellContainer = styled.td`
height: 45px; height: 45px;
border-bottom: 0.5px solid rgba(255, 255, 255, 0.5); border-bottom: 0.5px solid #5C5C5C;
background-color: #333333; background-color: #333333;
:nth-child(2) { :nth-child(2) {
@ -57,7 +63,7 @@ export const Cell = styled.td`
export const Row = styled.tr` export const Row = styled.tr`
:last-child:not(:first-child) { :last-child:not(:first-child) {
${Cell} { ${CellContainer} {
border-bottom: none; border-bottom: none;
} }
} }
@ -68,8 +74,9 @@ export const Header = styled.thead`
top: 0; top: 0;
z-index: 1; z-index: 1;
${Cell} { ${CellContainer} {
background-color: #292929; background-color: #292929;
border-bottom-color: ${({ theme }) => theme.colors.secondary};
} }
` `
@ -82,6 +89,11 @@ type TParamValue = {
export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({ export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({
...clickable && { tabIndex: 0 }, ...clickable && { tabIndex: 0 },
}))<TParamValue>` }))<TParamValue>`
display: inline-block;
width: 15px;
height: 15px;
text-align: center;
position: relative;
font-weight: 600; font-weight: 600;
color: ${({ clickable, theme }) => (clickable ? '#5EB2FF' : theme.colors.white)}; 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 { import { useEffect, useMemo } from 'react'
useEffect,
useMemo,
useState,
} from 'react'
import reduce from 'lodash/reduce' import reduce from 'lodash/reduce'
import isEmpty from 'lodash/isEmpty' import isEmpty from 'lodash/isEmpty'
@ -19,10 +15,11 @@ export const useMatchSidePlaylists = () => {
isEmptyPlayersStats, isEmptyPlayersStats,
matchPlaylists: playlists, matchPlaylists: playlists,
profile: matchProfile, profile: matchProfile,
selectedTab,
setSelectedTab,
teamsStats, teamsStats,
tournamentData, tournamentData,
} = useMatchPageStore() } = useMatchPageStore()
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.WATCH)
const playListFilter = useMemo(() => reduce( const playListFilter = useMemo(() => reduce(
playlists.match, playlists.match,
@ -84,6 +81,7 @@ export const useMatchSidePlaylists = () => {
isPlayersTabVisible, isPlayersTabVisible,
isStatsTabVisible, isStatsTabVisible,
isWatchTabVisible, isWatchTabVisible,
setSelectedTab,
]) ])
useEffect(() => { useEffect(() => {

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

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

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

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

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

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

@ -24,6 +24,7 @@ export type Player = {
nickname_eng: string | null, nickname_eng: string | null,
nickname_rus: string | null, nickname_rus: string | null,
num: number | null, num: number | null,
ord: number,
weight: number | null, weight: number | null,
} }
@ -42,12 +43,14 @@ type Response = {
type GetMatchParticipantsArgs = { type GetMatchParticipantsArgs = {
matchId: number, matchId: number,
period?: number,
second?: number, second?: number,
sportType: SportTypes, sportType: SportTypes,
} }
export const getMatchParticipants = async ({ export const getMatchParticipants = async ({
matchId, matchId,
period,
second, second,
sportType, sportType,
}: GetMatchParticipantsArgs) => { }: GetMatchParticipantsArgs) => {
@ -57,7 +60,7 @@ export const getMatchParticipants = async ({
const response: Response = await callApi({ const response: Response = await callApi({
config, 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) if (response.error) Promise.reject(response)

@ -30,6 +30,7 @@ type Response = {
type GetPlayersStatsArgs = { type GetPlayersStatsArgs = {
matchId: number, matchId: number,
period?: number,
second?: number, second?: number,
sportName: string, sportName: string,
teamId: number, teamId: number,
@ -37,6 +38,7 @@ type GetPlayersStatsArgs = {
export const getPlayersStats = async ({ export const getPlayersStats = async ({
matchId, matchId,
period,
second, second,
sportName, sportName,
teamId, teamId,
@ -47,7 +49,7 @@ export const getPlayersStats = async ({
const response: Response = await callApi({ const response: Response = await callApi({
config, 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) 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 = { type GetTeamsStatsArgs = {
matchId: number, matchId: number,
period?: number,
second?: number, second?: number,
sportName: string, sportName: string,
} }
export const getTeamsStats = async ({ export const getTeamsStats = async ({
matchId, matchId,
period,
second, second,
sportName, sportName,
}: GetTeamsStatsArgs) => { }: GetTeamsStatsArgs) => {
@ -49,7 +51,7 @@ export const getTeamsStats = async ({
const response: Response = await callApi({ const response: Response = await callApi({
config, 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) if (response.error) Promise.reject(response)

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

Loading…
Cancel
Save