diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx
index c6958ee4..a82ef7cd 100644
--- a/src/config/lexics/indexLexics.tsx
+++ b/src/config/lexics/indexLexics.tsx
@@ -15,6 +15,7 @@ const matchPopupLexics = {
display_stats_according_to_video: 19931,
episode_duration: 13410,
events: 1020,
+ final_stats: 19591,
from_end_match: 15396,
from_price: 3992,
from_start_match: 15395,
@@ -168,6 +169,7 @@ export const indexLexics = {
no_match_access_body: 13419,
no_match_access_title: 13418,
player: 14975,
+ players: 164,
players_video: 13032,
privacy_policy_and_statement: 15404,
round_highilights: 13050,
diff --git a/src/features/MatchPage/components/FinishedMatch/index.tsx b/src/features/MatchPage/components/FinishedMatch/index.tsx
index decd92e5..16aa7883 100644
--- a/src/features/MatchPage/components/FinishedMatch/index.tsx
+++ b/src/features/MatchPage/components/FinishedMatch/index.tsx
@@ -21,6 +21,7 @@ export const FinishedMatch = () => {
access,
isOpenFiltersPopup,
profile,
+ setPlayingProgress,
} = useMatchPageStore()
const {
chapters,
@@ -56,6 +57,7 @@ export const FinishedMatch = () => {
isOpenPopup={isOpenFiltersPopup}
chapters={chapters}
onPlayingChange={onPlayingChange}
+ onPlayerProgressChange={setPlayingProgress}
profile={profile}
/>
diff --git a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx
index e4240ad0..eab6f4b1 100644
--- a/src/features/MatchPage/components/LiveMatch/hooks/index.tsx
+++ b/src/features/MatchPage/components/LiveMatch/hooks/index.tsx
@@ -21,6 +21,7 @@ export const useLiveMatch = () => {
profile,
selectedPlaylist,
setFullMatchPlaylistDuration,
+ setPlayingProgress,
} = useMatchPageStore()
const { profileId: matchId, sportType } = usePageParams()
const resume = useResumeUrlParam()
@@ -45,7 +46,7 @@ export const useLiveMatch = () => {
} = usePlaylistLogger()
const {
- onPlayerProgressChange,
+ onPlayerProgressChange: playerProgressChange,
onPlayingChange: notifyProgressLogger,
} = usePlayerProgressReporter()
@@ -66,6 +67,11 @@ export const useLiveMatch = () => {
handlePlaylistClick(playlist, e)
}
+ const onPlayerProgressChange = (seconds: number, period = 0) => {
+ playerProgressChange(seconds, period)
+ setPlayingProgress(seconds * 1000)
+ }
+
return {
chapters,
isPlayFilterEpisodes,
diff --git a/src/features/MatchPage/store/hooks/index.tsx b/src/features/MatchPage/store/hooks/index.tsx
index 852e0566..382044a8 100644
--- a/src/features/MatchPage/store/hooks/index.tsx
+++ b/src/features/MatchPage/store/hooks/index.tsx
@@ -63,9 +63,9 @@ export const useMatchPage = () => {
filters,
isAllActionsChecked,
isEmptyFilters,
- isFirstTeamPlayersChecked,
isOpen: isOpenFiltersPopup,
- isSecondTeamPlayersChecked,
+ resetEvents,
+ resetPlayers,
toggle: togglePopup,
toggleActiveEvents,
toggleActivePlayers,
@@ -131,6 +131,23 @@ export const useMatchPage = () => {
return () => clearInterval(getIntervalMatch)
})
+ const {
+ events,
+ handlePlaylistClick,
+ isEmptyPlayersStats,
+ matchPlaylists,
+ playersData,
+ playersStats,
+ selectedPlaylist,
+ setFullMatchPlaylistDuration,
+ setPlayingProgress,
+ setStatsType,
+ statsType,
+ teamsStats,
+ } = useMatchData(matchProfile)
+
+ const profile = matchProfile
+
const isStarted = useMemo(() => (
profile?.date
? parseDate(profile.date) < new Date()
@@ -222,7 +239,7 @@ export const useMatchPage = () => {
hideProfileCard,
isAllActionsChecked,
isEmptyFilters,
- isFirstTeamPlayersChecked,
+ isEmptyPlayersStats,
isLiveMatch,
isOpenFiltersPopup,
isPlayFilterEpisodes,
@@ -234,6 +251,8 @@ export const useMatchPage = () => {
plaingOrder,
playEpisodes,
playNextEpisode,
+ playersData,
+ playersStats,
profile,
profileCardShown,
reversedGroupEvents,
@@ -241,10 +260,14 @@ export const useMatchPage = () => {
setFullMatchPlaylistDuration,
setIsPlayinFiltersEpisodes,
setPlaingOrder,
+ setPlayingProgress,
setReversed,
+ setStatsType,
setUnreversed,
setWatchAllEpisodesTimer,
showProfileCard,
+ statsType,
+ teamsStats,
toggleActiveEvents,
toggleActivePlayers,
togglePopup,
diff --git a/src/features/MatchPage/store/hooks/useMatchData.tsx b/src/features/MatchPage/store/hooks/useMatchData.tsx
index 4cee0488..39fbf3b5 100644
--- a/src/features/MatchPage/store/hooks/useMatchData.tsx
+++ b/src/features/MatchPage/store/hooks/useMatchData.tsx
@@ -6,7 +6,7 @@ import {
import debounce from 'lodash/debounce'
-import { MatchInfo } from 'requests/getMatchInfo'
+import type { MatchInfo } from 'requests/getMatchInfo'
import { usePageParams } from 'hooks/usePageParams'
import { useInterval } from 'hooks/useInterval'
@@ -16,6 +16,9 @@ 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
@@ -24,6 +27,7 @@ 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,
@@ -33,6 +37,21 @@ export const useMatchData = (profile: MatchInfo) => {
setSelectedPlaylist,
} = useMatchPlaylists(profile)
const { events, fetchMatchEvents } = useEvents()
+ const { setStatsType, statsType } = useStatsTab()
+ const { teamsStats } = useTeamsStats({
+ matchProfile: profile,
+ playingProgress,
+ statsType,
+ })
+ const {
+ isEmptyPlayersStats,
+ playersData,
+ playersStats,
+ } = usePlayersStats({
+ matchProfile: profile,
+ playingProgress,
+ statsType,
+ })
const fetchPlaylistsDebounced = useMemo(
() => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY),
@@ -93,8 +112,15 @@ export const useMatchData = (profile: MatchInfo) => {
return {
events,
handlePlaylistClick,
+ isEmptyPlayersStats,
matchPlaylists,
+ playersData,
+ playersStats,
selectedPlaylist,
setFullMatchPlaylistDuration,
+ setPlayingProgress,
+ setStatsType,
+ statsType,
+ teamsStats,
}
}
diff --git a/src/features/MatchPage/store/hooks/usePlayersStats.tsx b/src/features/MatchPage/store/hooks/usePlayersStats.tsx
new file mode 100644
index 00000000..326d4f51
--- /dev/null
+++ b/src/features/MatchPage/store/hooks/usePlayersStats.tsx
@@ -0,0 +1,159 @@
+import {
+ useMemo,
+ useEffect,
+ useState,
+} from 'react'
+
+import throttle from 'lodash/throttle'
+import isEmpty from 'lodash/isEmpty'
+import every from 'lodash/every'
+import find from 'lodash/find'
+
+import type {
+ MatchInfo,
+ PlayersStats,
+ Player,
+} from 'requests'
+import { getPlayersStats, getMatchParticipants } from 'requests'
+
+import { useObjectState, usePageParams } from 'hooks'
+
+import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
+
+const REQUEST_DELAY = 3000
+const STATS_POLL_INTERVAL = 30000
+
+type UsePlayersStatsArgs = {
+ matchProfile: MatchInfo,
+ playingProgress: number,
+ statsType: StatsType,
+}
+
+type PlayersData = {
+ team1: Array,
+ team2: Array,
+}
+
+export const usePlayersStats = ({
+ matchProfile,
+ playingProgress,
+ statsType,
+}: UsePlayersStatsArgs) => {
+ const [playersStats, setPlayersStats] = useObjectState>({})
+ const [playersData, setPlayersData] = useState({ team1: [], team2: [] })
+
+ const {
+ profileId: matchId,
+ sportName,
+ sportType,
+ } = usePageParams()
+
+ const isCurrentStats = statsType === StatsType.CURRENT_STATS
+
+ const progressSec = Math.floor(playingProgress / 1000)
+
+ const isEmptyPlayersStats = (teamId: number) => (
+ isEmpty(playersStats[teamId])
+ || every(playersStats[teamId], isEmpty)
+ || isEmpty(playersData[matchProfile?.team1.id === teamId ? 'team1' : 'team2'])
+ )
+
+ const fetchPlayers = useMemo(() => throttle((second?: number) => {
+ if (!matchProfile?.team1.id || !matchProfile?.team1.id) return
+
+ try {
+ getMatchParticipants({
+ matchId,
+ second,
+ sportType,
+ }).then((data) => {
+ const team1Players = find(data, { team_id: matchProfile.team1.id })?.players || []
+ const team2Players = find(data, { team_id: matchProfile.team2.id })?.players || []
+
+ setPlayersData({
+ team1: team1Players,
+ team2: team2Players,
+ })
+ })
+
+ // eslint-disable-next-line no-empty
+ } catch (e) {}
+ }, REQUEST_DELAY), [
+ matchId,
+ matchProfile?.team1.id,
+ matchProfile?.team2.id,
+ sportType,
+ ])
+
+ const fetchPlayersStats = useMemo(() => throttle((second?: number) => {
+ if (!sportName || !matchProfile?.team1.id || !matchProfile?.team2.id) return
+
+ try {
+ getPlayersStats({
+ matchId,
+ second,
+ sportName,
+ teamId: matchProfile.team1.id,
+ }).then((data) => setPlayersStats({ [matchProfile.team1.id]: data }))
+
+ getPlayersStats({
+ matchId,
+ second,
+ sportName,
+ teamId: matchProfile.team2.id,
+ }).then((data) => setPlayersStats({ [matchProfile?.team2.id]: data }))
+ // eslint-disable-next-line no-empty
+ } catch (e) {}
+ }, REQUEST_DELAY), [
+ matchId,
+ matchProfile?.team1.id,
+ matchProfile?.team2.id,
+ setPlayersStats,
+ sportName,
+ ])
+
+ useEffect(() => {
+ let interval: NodeJS.Timeout
+
+ fetchPlayers()
+
+ if (!isCurrentStats) {
+ fetchPlayersStats()
+ }
+
+ if (matchProfile?.live) {
+ interval = setInterval(() => {
+ if (isCurrentStats) return
+
+ fetchPlayersStats()
+ fetchPlayers()
+ }, STATS_POLL_INTERVAL)
+ }
+
+ return () => clearInterval(interval)
+ }, [
+ fetchPlayersStats,
+ fetchPlayers,
+ isCurrentStats,
+ matchProfile?.live,
+ ])
+
+ useEffect(() => {
+ if (isCurrentStats) {
+ fetchPlayersStats(progressSec)
+ fetchPlayers(progressSec)
+ }
+ }, [
+ fetchPlayersStats,
+ fetchPlayers,
+ progressSec,
+ isCurrentStats,
+ matchProfile?.live,
+ ])
+
+ return {
+ isEmptyPlayersStats,
+ playersData,
+ playersStats,
+ }
+}
diff --git a/src/features/MatchPage/store/hooks/useStatsTab.tsx b/src/features/MatchPage/store/hooks/useStatsTab.tsx
new file mode 100644
index 00000000..9a2a18f1
--- /dev/null
+++ b/src/features/MatchPage/store/hooks/useStatsTab.tsx
@@ -0,0 +1,12 @@
+import { useState } from 'react'
+
+import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
+
+export const useStatsTab = () => {
+ const [statsType, setStatsType] = useState(StatsType.FINAL_STATS)
+
+ return {
+ setStatsType,
+ statsType,
+ }
+}
diff --git a/src/features/MatchPage/store/hooks/useTeamsStats.tsx b/src/features/MatchPage/store/hooks/useTeamsStats.tsx
new file mode 100644
index 00000000..f9f6ea58
--- /dev/null
+++ b/src/features/MatchPage/store/hooks/useTeamsStats.tsx
@@ -0,0 +1,78 @@
+import {
+ useEffect,
+ useState,
+ useMemo,
+} from 'react'
+
+import throttle from 'lodash/throttle'
+
+import type { MatchInfo } from 'requests'
+import { getTeamsStats, TeamStatItem } from 'requests'
+
+import { usePageParams } from 'hooks/usePageParams'
+
+import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
+
+const REQUEST_DELAY = 3000
+const STATS_POLL_INTERVAL = 30000
+
+type UseTeamsStatsArgs = {
+ matchProfile: MatchInfo,
+ playingProgress: number,
+ statsType: StatsType,
+}
+
+export const useTeamsStats = ({
+ matchProfile,
+ playingProgress,
+ statsType,
+}: UseTeamsStatsArgs) => {
+ const [teamsStats, setTeamsStats] = useState<{
+ [teamId: string]: Array,
+ }>({})
+
+ const { profileId: matchId, sportName } = usePageParams()
+
+ const progressSec = Math.floor(playingProgress / 1000)
+
+ const isCurrentStats = statsType === StatsType.CURRENT_STATS
+
+ const fetchTeamsStats = useMemo(() => throttle((second?: number) => {
+ if (!sportName) return
+
+ getTeamsStats({
+ matchId,
+ second,
+ sportName,
+ }).then(setTeamsStats)
+ }, REQUEST_DELAY), [matchId, sportName])
+
+ useEffect(() => {
+ let timer: ReturnType
+
+ if (!isCurrentStats) {
+ fetchTeamsStats()
+ }
+
+ if (matchProfile?.live) {
+ timer = setInterval(() => {
+ if (isCurrentStats) return
+
+ fetchTeamsStats()
+ }, STATS_POLL_INTERVAL)
+ }
+
+ return () => clearInterval(timer)
+ }, [fetchTeamsStats, matchProfile?.live, isCurrentStats])
+
+ useEffect(() => {
+ if (isCurrentStats) {
+ fetchTeamsStats(progressSec)
+ }
+ }, [fetchTeamsStats, progressSec, isCurrentStats])
+
+ return {
+ statsType,
+ teamsStats,
+ }
+}
diff --git a/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx b/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx
index e0e49fb9..a0245220 100644
--- a/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx
+++ b/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx
@@ -1,8 +1,8 @@
-import { useMemo } from 'react'
+import type { ForwardedRef } from 'react'
+import { forwardRef } from 'react'
import styled, { css } from 'styled-components/macro'
-import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
@@ -15,6 +15,8 @@ import { T9n } from 'features/T9n'
import { PlayButton } from '../PlayButton'
+export const LIST_ITEM_INDENT = 12
+
type Props = {
live?: boolean,
onSelect?: (selectedMathPlaylist: PlaylistOption) => void,
@@ -25,7 +27,7 @@ type Props = {
const List = styled.ul``
const Item = styled.li`
- margin-bottom: 12px;
+ margin-bottom: ${LIST_ITEM_INDENT}px;
width: 100%;
height: 36px;
${isMobileDevice
@@ -36,24 +38,17 @@ const Item = styled.li`
: ''};
`
-export const MatchPlaylists = ({
- live,
- onSelect,
- playlists,
- selectedMathPlaylist,
-}: Props) => {
- const filteredPlayListByDuration = useMemo(() => (
- filter(playlists, (playlist) => (
- live
- ? Boolean(playlist.duration) || (playlist.id === 'full_game')
- : Boolean(playlist.duration)
- ))
- ), [playlists, live])
-
- return (
-
+export const MatchPlaylists = forwardRef(
+ ({
+ live,
+ onSelect,
+ playlists,
+ selectedMathPlaylist,
+ }: Props,
+ ref: ForwardedRef) => (
+
{
- map(filteredPlayListByDuration, (playlist) => (
+ map(playlists, (playlist) => (
-
- )
-}
+ ),
+)
diff --git a/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/index.tsx b/src/features/MatchSidePlaylists/components/Matches/components/VideoDate/index.tsx
similarity index 100%
rename from src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/index.tsx
rename to src/features/MatchSidePlaylists/components/Matches/components/VideoDate/index.tsx
diff --git a/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx b/src/features/MatchSidePlaylists/components/Matches/components/VideoDate/styled.tsx
similarity index 100%
rename from src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx
rename to src/features/MatchSidePlaylists/components/Matches/components/VideoDate/styled.tsx
diff --git a/src/features/MatchSidePlaylists/components/TabVideo/index.tsx b/src/features/MatchSidePlaylists/components/Matches/index.tsx
similarity index 90%
rename from src/features/MatchSidePlaylists/components/TabVideo/index.tsx
rename to src/features/MatchSidePlaylists/components/Matches/index.tsx
index 2a8740d6..11d9d7de 100644
--- a/src/features/MatchSidePlaylists/components/TabVideo/index.tsx
+++ b/src/features/MatchSidePlaylists/components/Matches/index.tsx
@@ -23,13 +23,15 @@ import { VideoDate } from './components/VideoDate'
import { MatchesWrapper } from './styled'
type Props = {
+ additionalScrollHeight: number,
profile: MatchInfo,
tournamentData: TournamentData,
}
const formatDate = (date: Date) => format(date, 'yyyy-MM-dd')
-export const TabVideo = ({
+export const Matches = ({
+ additionalScrollHeight,
profile,
tournamentData,
}: Props) => {
@@ -75,7 +77,7 @@ export const TabVideo = ({
const hasScroll = scrollHeight > clientHeight
setOverflow(hasScroll)
- }, [ref, selectedDate])
+ }, [ref.current?.clientHeight, selectedDate])
if (tournamentData.matches.length <= 1) return null
@@ -88,7 +90,11 @@ export const TabVideo = ({
profileDate={profileDate}
onDateClick={setSelectedDate}
/>
-
+
{
map(sortBy(matches, ({ live }) => !live), (match) => (
`
+type MatchesWrapperProps = {
+ additionalScrollHeight: number,
+ hasScroll?: boolean,
+}
+
+export const MatchesWrapper = styled.div`
overflow-y: auto;
- max-height: calc(100vh - 170px);
+ max-height: calc(100vh - 165px - ${({ additionalScrollHeight }) => additionalScrollHeight}px);
padding-right: ${({ hasScroll }) => (hasScroll ? '10px' : '')};
> * {
diff --git a/src/features/MatchSidePlaylists/components/PlayersPlaylists/index.tsx b/src/features/MatchSidePlaylists/components/PlayersPlaylists/index.tsx
index d05ac36e..22b69298 100644
--- a/src/features/MatchSidePlaylists/components/PlayersPlaylists/index.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersPlaylists/index.tsx
@@ -15,12 +15,10 @@ import type {
} from 'features/MatchPage/types'
import { Name } from 'features/Name'
-import { T9n } from 'features/T9n'
import { isEqual } from '../../helpers'
import { PlayButton } from '../PlayButton'
-import { BlockTitle } from '../../styled'
import {
Wrapper,
List,
@@ -58,9 +56,6 @@ export const PlayersPlaylists = ({
return (
-
-
-
{
+ const [sortCondition, setSortCondition] = useState({ dir: 'asc', paramId: null })
+
+ const {
+ getFullName,
+ getPlayerParams,
+ players,
+ } = usePlayers({ sortCondition, teamId })
+
+ const {
+ containerRef,
+ firstColumnWidth,
+ getDisplayedValue,
+ handleScroll,
+ handleSortClick,
+ isExpanded,
+ paramColumnWidth,
+ params,
+ showExpandButton,
+ showLeftArrow,
+ showRightArrow,
+ slideLeft,
+ slideRight,
+ tableWrapperRef,
+ toggleIsExpanded,
+ } = useTable({
+ setSortCondition,
+ teamId,
+ })
+
+ return {
+ containerRef,
+ firstColumnWidth,
+ getDisplayedValue,
+ getFullName,
+ getPlayerParams,
+ handleScroll,
+ handleSortClick,
+ isExpanded,
+ paramColumnWidth,
+ params,
+ players,
+ showExpandButton,
+ showLeftArrow,
+ showRightArrow,
+ slideLeft,
+ slideRight,
+ sortCondition,
+ tableWrapperRef,
+ toggleIsExpanded,
+ }
+}
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx
new file mode 100644
index 00000000..ff2b8e3a
--- /dev/null
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx
@@ -0,0 +1,83 @@
+import { useMemo, useCallback } from 'react'
+
+import orderBy from 'lodash/orderBy'
+import isNil from 'lodash/isNil'
+import trim from 'lodash/trim'
+
+import type { Player, PlayerParam } from 'requests'
+
+import { useToggle } from 'hooks'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+import { useLexicsStore } from 'features/LexicsStore'
+
+import type { SortCondition } from '../types'
+
+type UsePlayersArgs = {
+ sortCondition: SortCondition,
+ teamId: number,
+}
+
+export const usePlayers = ({ sortCondition, teamId }: UsePlayersArgs) => {
+ const { isOpen: isExpanded, toggle: toggleIsExpanded } = useToggle()
+ const {
+ playersData,
+ playersStats,
+ profile: matchProfile,
+ } = useMatchPageStore()
+ const { suffix } = useLexicsStore()
+
+ const getPlayerParams = useCallback(
+ (playerId: number) => playersStats[teamId][playerId] || {},
+ [playersStats, teamId],
+ )
+
+ const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : val)
+
+ const getFullName = useCallback((player: Player) => (
+ trim(`${player[`firstname_${suffix}`]} ${player[`lastname_${suffix}`]}`)
+ ), [suffix])
+
+ const getParamValue = useCallback((playerId: number, paramId: number) => {
+ const playerParams = getPlayerParams(playerId)
+ const { val } = playerParams[paramId] || {}
+
+ return val
+ }, [getPlayerParams])
+
+ const sortedPlayers = useMemo(() => {
+ const players = playersData[matchProfile?.team1.id === teamId ? 'team1' : 'team2']
+
+ return isNil(sortCondition.paramId)
+ ? orderBy(players, getFullName)
+ : orderBy(
+ players,
+ [
+ (player) => {
+ const paramValue = getParamValue(player.id, sortCondition.paramId!)
+
+ return isNil(paramValue) ? -1 : paramValue
+ },
+ getFullName,
+ ],
+ sortCondition.dir,
+ )
+ }, [
+ getFullName,
+ getParamValue,
+ playersData,
+ matchProfile?.team1.id,
+ sortCondition.dir,
+ sortCondition.paramId,
+ teamId,
+ ])
+
+ return {
+ getDisplayedValue,
+ getFullName,
+ getPlayerParams,
+ isExpanded,
+ players: sortedPlayers,
+ toggleIsExpanded,
+ }
+}
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
new file mode 100644
index 00000000..5bc2f55c
--- /dev/null
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
@@ -0,0 +1,171 @@
+import type {
+ SyntheticEvent,
+ Dispatch,
+ SetStateAction,
+} from 'react'
+import {
+ useRef,
+ useState,
+ useEffect,
+ useMemo,
+} from 'react'
+
+import size from 'lodash/size'
+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 { useToggle } from 'hooks'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+import { useLexicsConfig } from 'features/LexicsStore'
+
+import type { SortCondition } from '../types'
+import {
+ PARAM_COLUMN_WIDTH,
+ DISPLAYED_PARAMS_COLUMNS,
+ FIRST_COLUMN_WIDTH_DEFAULT,
+ SCROLLBAR_WIDTH,
+} from '../config'
+
+type UseTableArgs = {
+ setSortCondition: Dispatch>,
+ teamId: number,
+}
+
+type HeaderParam = Pick
+
+export const useTable = ({
+ setSortCondition,
+ teamId,
+}: UseTableArgs) => {
+ const containerRef = useRef(null)
+ const tableWrapperRef = useRef(null)
+
+ const [showLeftArrow, setShowLeftArrow] = useState(false)
+ const [showRightArrow, setShowRightArrow] = useState(false)
+
+ const { isOpen: isExpanded, toggle: toggleIsExpanded } = useToggle()
+ const { playersStats } = useMatchPageStore()
+
+ const params = useMemo(() => (
+ reduce>(
+ playersStats[teamId],
+ (acc, curr) => {
+ forEach(values(curr), ({
+ id,
+ lexic,
+ lexica_short,
+ }) => {
+ acc[id] = acc[id] || {
+ id,
+ lexic,
+ lexica_short,
+ }
+ })
+
+ return acc
+ },
+ {},
+ )
+ ), [playersStats, teamId])
+
+ const lexics = useMemo(() => (
+ reduce>(
+ values(params),
+ (acc, { lexic, lexica_short }) => {
+ if (lexic) acc.push(lexic)
+ if (lexica_short) acc.push(lexica_short)
+
+ return acc
+ },
+ [],
+ )
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ ), [map(params, 'id').sort().join('')])
+
+ useLexicsConfig(lexics)
+
+ const paramsCount = size(params)
+
+ const getParamColumnWidth = () => {
+ const rest = (
+ (containerRef.current?.clientWidth || 0) - FIRST_COLUMN_WIDTH_DEFAULT - SCROLLBAR_WIDTH
+ )
+ const desktopWith = PARAM_COLUMN_WIDTH
+ const mobileWidth = paramsCount < DISPLAYED_PARAMS_COLUMNS ? 0 : rest / DISPLAYED_PARAMS_COLUMNS
+
+ return isMobileDevice ? mobileWidth : desktopWith
+ }
+
+ const getFirstColumnWidth = () => {
+ if (isExpanded) return 0
+
+ return paramsCount < DISPLAYED_PARAMS_COLUMNS ? 0 : FIRST_COLUMN_WIDTH_DEFAULT
+ }
+
+ const paramColumnWidth = getParamColumnWidth()
+ const firstColumnWidth = getFirstColumnWidth()
+
+ const slideLeft = () => tableWrapperRef.current?.scrollBy(-paramColumnWidth, 0)
+ const slideRight = () => tableWrapperRef.current?.scrollBy(paramColumnWidth, 0)
+
+ const getDisplayedValue = ({ val }: PlayerParam) => (isNil(val) ? '-' : round(val, 2))
+
+ const handleScroll = (e: SyntheticEvent) => {
+ const {
+ clientWidth,
+ scrollLeft,
+ scrollWidth,
+ } = e.currentTarget
+
+ const scrollRight = scrollWidth - (scrollLeft + clientWidth)
+
+ setShowLeftArrow(scrollLeft > 0)
+ setShowRightArrow(scrollRight > 0)
+ }
+
+ const handleSortClick = (paramId: number) => () => {
+ setSortCondition((curr) => ({
+ dir: curr.dir === 'asc' || curr.paramId !== paramId ? 'desc' : 'asc',
+ paramId,
+ }))
+ }
+
+ useEffect(() => {
+ const {
+ clientWidth = 0,
+ scrollLeft = 0,
+ scrollWidth = 0,
+ } = tableWrapperRef.current || {}
+
+ const scrollRight = scrollWidth - (scrollLeft + clientWidth)
+
+ setShowRightArrow(scrollRight > 0)
+ }, [isExpanded])
+
+ return {
+ containerRef,
+ firstColumnWidth,
+ getDisplayedValue,
+ handleScroll,
+ handleSortClick,
+ isExpanded,
+ paramColumnWidth,
+ params,
+ showExpandButton: !isMobileDevice && paramsCount > DISPLAYED_PARAMS_COLUMNS,
+ showLeftArrow,
+ showRightArrow,
+ slideLeft,
+ slideRight,
+ tableWrapperRef,
+ toggleIsExpanded,
+ }
+}
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
index 46520e1b..9e5ce602 100644
--- a/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
@@ -1,243 +1,166 @@
+import { Fragment } from 'react'
+
+import map from 'lodash/map'
+import includes from 'lodash/includes'
+
+import { PlayerParam } from 'requests'
+
+import { T9n } from 'features/T9n'
+
+import type { PlayersTableProps } from './types'
+import { usePlayersTable } from './hooks'
import {
+ Container,
+ TableWrapper,
Table,
- Thead,
- Th,
- Tbody,
- Tr,
- Td,
+ FirstColumn,
+ Cell,
+ Row,
PlayerNum,
+ PlayerNameWrapper,
+ PlayerName,
ParamShortTitle,
+ ArrowButtonRight,
+ ArrowButtonLeft,
+ Arrow,
+ ExpandButton,
+ Tooltip,
} from './styled'
-export const PlayersTable = () => (
-
-
- |
-
- Min
- |
-
- Pt
- |
-
- Reb
- |
-
- Ass
- |
-
- To
- |
-
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
- | 57 Selikhov |
- 97 |
- 12 |
- 2 |
- 1 |
- 4 |
-
-
-
-)
+export const PlayersTable = (props: PlayersTableProps) => {
+ const {
+ containerRef,
+ firstColumnWidth,
+ getDisplayedValue,
+ getFullName,
+ getPlayerParams,
+ handleScroll,
+ handleSortClick,
+ isExpanded,
+ paramColumnWidth,
+ params,
+ players,
+ showExpandButton,
+ showLeftArrow,
+ showRightArrow,
+ slideLeft,
+ slideRight,
+ sortCondition,
+ tableWrapperRef,
+ toggleIsExpanded,
+ } = usePlayersTable(props)
+
+ return (
+
+
+ {!isExpanded && (
+
+ {showLeftArrow && (
+
+
+
+ )}
+ {showRightArrow && (
+
+
+
+ )}
+
+ )}
+
+
+ |
+ {showExpandButton && (
+
+
+
+
+ )}
+ |
+
+ {map(players, (player) => {
+ const fullName = getFullName(player)
+
+ return (
+
+ |
+
+ {player.club_shirt_num}
+ {' '}
+
+
+ {fullName}
+
+
+ {fullName}
+
+
+ |
+
+ )
+ })}
+
+
+
+ {map(params, ({
+ id,
+ lexic,
+ lexica_short,
+ }) => (
+ |
+
+
+
+
+ |
+ ))}
+
+
+ {map(players, (player) => (
+
+ {map(params, ({ id }) => {
+ const playerParam = getPlayerParams(player.id)[id] as PlayerParam | undefined
+ const value = playerParam ? getDisplayedValue(playerParam) : '-'
+ const clickable = Boolean(playerParam?.clickable) && !includes([0, '-'], value)
+ const sorted = sortCondition.paramId === id
+
+ return (
+ |
+ {value}
+ |
+ )
+ })}
+
+ ))}
+
+
+
+ )
+}
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
index 3d88c1f6..a0d9f33d 100644
--- a/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
@@ -1,72 +1,133 @@
import styled, { css } from 'styled-components/macro'
+import { isMobileDevice } from 'config'
+
import { customScrollbar } from 'features/Common'
+import { TooltipWrapper } from 'features/Tooltip'
+import {
+ ArrowButton as ArrowButtonBase,
+ Arrow as ArrowBase,
+} from 'features/HeaderFilters/components/DateFilter/styled'
+import { T9n } from 'features/T9n'
+
+type ContainerProps = {
+ isExpanded?: boolean,
+}
-export const Table = styled.table`
- width: 100%;
+export const Container = styled.div`
+ ${({ isExpanded }) => (isExpanded
+ ? ''
+ : css`
+ position: relative;
+ `)}
+`
+
+type TableWrapperProps = {
+ isExpanded?: boolean,
+}
+
+export const TableWrapper = styled.div`
+ display: flex;
+ max-width: 100%;
+ max-height: calc(100vh - 235px);
border-radius: 5px;
- border-collapse: collapse;
- letter-spacing: -0.078px;
+ overflow-x: auto;
+ scroll-behavior: smooth;
background-color: #333333;
- table-layout: fixed;
-
+ z-index: 50;
${customScrollbar}
-`
-export const Thead = styled.thead`
- height: 45px;
- border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
+ ${({ isExpanded }) => (isExpanded
+ ? css`
+ position: absolute;
+ right: 14px;
+ `
+ : '')}
`
-type ThProps = {
- sorted?: boolean,
-}
+export const Table = styled.div`
+ flex-grow: 1;
+ border-radius: 5px;
+ border-collapse: collapse;
+ letter-spacing: -0.078px;
+`
-export const Th = styled.th`
+export const Tooltip = styled(TooltipWrapper)`
+ left: auto;
+ padding: 2px 10px;
+ border-radius: 6px;
+ transform: none;
font-size: 11px;
- color: ${({ theme }) => theme.colors.white};
- text-transform: uppercase;
+ line-height: 1;
+ color: ${({ theme }) => theme.colors.black};
- :first-child {
- width: 115px;
+ ::before {
+ display: none;
}
-
- ${({ sorted }) => (sorted
- ? ''
- : css`
- opacity: 0.5;
- `)}
`
-export const ParamShortTitle = styled.span``
-
-export const Tbody = styled.tbody``
+export const ParamShortTitle = styled(T9n)`
+ text-transform: uppercase;
+`
-export const Tr = styled.tr`
+export const Row = styled.div`
+ display: flex;
+ width: 100%;
height: 45px;
border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
- :last-child {
- border-bottom: none;
+ :first-child {
+ position: sticky;
+ left: 0;
+ top: 0;
+ z-index: 1;
}
`
type TdProps = {
clickable?: boolean,
+ columnWidth?: number,
+ headerCell?: boolean,
sorted?: boolean,
}
-export const Td = styled.td`
+export const Cell = styled.div.attrs(({ clickable }: TdProps) => ({
+ ...clickable && { tabIndex: 0 },
+}))`
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-shrink: 0;
+ width: ${({ columnWidth }) => (columnWidth ? `${columnWidth}px` : 'auto')};
font-size: 11px;
- text-align: center;
- color: ${({ clickable, theme }) => (clickable ? '#5EB2FF' : theme.colors.white)};
+ color: ${({
+ clickable,
+ headerCell,
+ theme,
+ }) => (clickable && !headerCell ? '#5EB2FF' : theme.colors.white)};
+ white-space: nowrap;
+ background-color: #333333;
- :first-child {
- padding-left: 13px;
- text-align: left;
- color: ${({ theme }) => theme.colors.white};
+ ${Tooltip} {
+ top: 35px;
+ }
+
+ :hover {
+ ${Tooltip} {
+ display: block;
+ }
}
+ ${({ headerCell }) => (headerCell
+ ? ''
+ : css`
+ :first-child {
+ justify-content: unset;
+ padding-left: 13px;
+ color: ${({ theme }) => theme.colors.white};
+ }
+ `)}
${({ sorted }) => (sorted
? css`
@@ -74,13 +135,107 @@ export const Td = styled.td`
`
: '')}
- ${({ clickable }) => (clickable
+ ${({ clickable, headerCell }) => (clickable || headerCell
? css`
cursor: pointer;
`
: '')}
`
+type FirstColumnProps = {
+ columnWidth?: number,
+}
+
+export const FirstColumn = styled.div`
+ position: sticky;
+ left: 0;
+ width: ${({ columnWidth }) => (columnWidth ? `${columnWidth}px` : 'auto')};
+`
+
export const PlayerNum = styled.span`
+ display: inline-block;
+ width: 20px;
+ flex-shrink: 0;
+ text-align: center;
color: rgba(255, 255, 255, 0.5);
`
+
+type PlayerNameProps = {
+ columnWidth?: number,
+}
+
+export const PlayerName = styled.span`
+ display: inline-block;
+ margin-top: 2px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+
+ ${({ columnWidth }) => (columnWidth
+ ? css`
+ max-width: calc(${columnWidth}px - 31px);
+ `
+ : css`
+ max-width: 110px;
+ `)}
+`
+
+export const PlayerNameWrapper = styled.span`
+ position: relative;
+
+ ${Tooltip} {
+ top: 15px;
+ }
+
+ :hover {
+ ${Tooltip} {
+ display: block;
+ }
+ }
+`
+
+const ArrowButton = styled(ArrowButtonBase)`
+ position: absolute;
+ width: 17px;
+ margin-top: 2px;
+ background-color: #333333;
+ z-index: 3;
+
+ ${isMobileDevice
+ ? css`
+ height: 45px;
+ margin-top: 0;
+ `
+ : ''};
+`
+
+export const ArrowButtonRight = styled(ArrowButton)`
+ right: 0;
+`
+
+export const ArrowButtonLeft = styled(ArrowButton)`
+ left: 75px;
+`
+
+export const Arrow = styled(ArrowBase)`
+ width: 10px;
+ height: 10px;
+
+ ${isMobileDevice
+ ? css`
+ border-color: ${({ theme }) => theme.colors.white};
+ `
+ : ''};
+`
+
+export const ExpandButton = styled(ArrowButton)`
+ left: 20px;
+ top: 0;
+
+ ${Arrow} {
+ left: 0;
+
+ :last-child {
+ margin-left: 7px;
+ }
+ }
+`
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx
new file mode 100644
index 00000000..85b9d054
--- /dev/null
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx
@@ -0,0 +1,8 @@
+export type PlayersTableProps = {
+ teamId: number,
+}
+
+export type SortCondition = {
+ dir: 'asc' | 'desc',
+ paramId: number | null,
+}
diff --git a/src/features/MatchSidePlaylists/components/TabStats/config.tsx b/src/features/MatchSidePlaylists/components/TabStats/config.tsx
index 099a117f..3c14e7ff 100644
--- a/src/features/MatchSidePlaylists/components/TabStats/config.tsx
+++ b/src/features/MatchSidePlaylists/components/TabStats/config.tsx
@@ -3,3 +3,8 @@ export enum Tabs {
TEAM1,
TEAM2,
}
+
+export enum StatsType {
+ FINAL_STATS,
+ CURRENT_STATS,
+}
diff --git a/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx b/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx
new file mode 100644
index 00000000..1d621ce3
--- /dev/null
+++ b/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx
@@ -0,0 +1,68 @@
+import { useEffect, useState } from 'react'
+
+import isEmpty from 'lodash/isEmpty'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+
+import { StatsType, Tabs } from './config'
+
+export const useTabStats = () => {
+ const [selectedTab, setSelectedTab] = useState(Tabs.TEAMS)
+
+ const {
+ isEmptyPlayersStats,
+ profile: matchProfile,
+ setStatsType,
+ statsType,
+ teamsStats,
+ } = useMatchPageStore()
+
+ const isFinalStatsType = statsType === StatsType.FINAL_STATS
+
+ const switchTitleLexic = isFinalStatsType ? 'final_stats' : 'current_stats'
+ const tooltipLexic = isFinalStatsType ? 'display_all_stats' : 'display_stats_according_to_video'
+
+ const isVisibleTeamsTab = !isEmpty(teamsStats)
+ const isVisibleTeam1PlayersTab = Boolean(
+ matchProfile && !isEmptyPlayersStats(matchProfile.team1.id),
+ )
+ const isVisibleTeam2PlayersTab = Boolean(
+ matchProfile && !isEmptyPlayersStats(matchProfile.team2.id),
+ )
+
+ const toggleStatsType = () => {
+ const newStatsType = isFinalStatsType ? StatsType.CURRENT_STATS : StatsType.FINAL_STATS
+
+ setStatsType(newStatsType)
+ }
+
+ useEffect(() => {
+ switch (true) {
+ case isVisibleTeamsTab:
+ setSelectedTab(Tabs.TEAMS)
+ break
+
+ case isVisibleTeam1PlayersTab:
+ setSelectedTab(Tabs.TEAM1)
+ break
+
+ case isVisibleTeam2PlayersTab:
+ setSelectedTab(Tabs.TEAM2)
+ break
+
+ default:
+ }
+ }, [isVisibleTeam1PlayersTab, isVisibleTeam2PlayersTab, isVisibleTeamsTab])
+
+ return {
+ isFinalStatsType,
+ isVisibleTeam1PlayersTab,
+ isVisibleTeam2PlayersTab,
+ isVisibleTeamsTab,
+ selectedTab,
+ setSelectedTab,
+ switchTitleLexic,
+ toggleStatsType,
+ tooltipLexic,
+ }
+}
diff --git a/src/features/MatchSidePlaylists/components/TabStats/index.tsx b/src/features/MatchSidePlaylists/components/TabStats/index.tsx
index c43592bb..d5b20d74 100644
--- a/src/features/MatchSidePlaylists/components/TabStats/index.tsx
+++ b/src/features/MatchSidePlaylists/components/TabStats/index.tsx
@@ -1,7 +1,16 @@
+import { isMobileDevice } from 'config/userAgent'
+
+import { getTeamAbbr } from 'helpers'
+
import { Tooltip } from 'features/Tooltip'
+import { T9n } from 'features/T9n'
+import { useMatchPageStore } from 'features/MatchPage/store'
+import { Name } from 'features/Name'
+import { Tabs } from './config'
+import { useTabStats } from './hooks'
import { PlayersTable } from '../PlayersTable'
-import { TeamsStats } from '../TeamsStats'
+import { TeamsStatsTable } from '../TeamsStatsTable'
import {
Container,
@@ -13,22 +22,82 @@ import {
SwitchButton,
} from './styled'
-export const TabStats = () => (
-
-
-
- Teams
- DIN
- SPA
-
-
- Final Stats
-
-
-
-
-
-
- {/* */}
-
-)
+const tabPanes = {
+ [Tabs.TEAMS]: TeamsStatsTable,
+ [Tabs.TEAM1]: PlayersTable,
+ [Tabs.TEAM2]: PlayersTable,
+}
+
+export const TabStats = () => {
+ const {
+ isFinalStatsType,
+ isVisibleTeam1PlayersTab,
+ isVisibleTeam2PlayersTab,
+ isVisibleTeamsTab,
+ selectedTab,
+ setSelectedTab,
+ switchTitleLexic,
+ toggleStatsType,
+ tooltipLexic,
+ } = useTabStats()
+ const { profile: matchProfile } = useMatchPageStore()
+
+ const TabPane = tabPanes[selectedTab]
+
+ if (!matchProfile) return null
+
+ const { team1, team2 } = matchProfile
+
+ return (
+
+
+
+ {isVisibleTeamsTab && (
+ setSelectedTab(Tabs.TEAMS)}
+ >
+
+
+ )}
+ {isVisibleTeam1PlayersTab && (
+ setSelectedTab(Tabs.TEAM1)}
+ >
+
+
+ )}
+ {isVisibleTeam2PlayersTab && (
+ setSelectedTab(Tabs.TEAM2)}
+ >
+
+
+ )}
+
+
+
+
+ {!isMobileDevice && }
+
+
+
+
+
+ )
+}
diff --git a/src/features/MatchSidePlaylists/components/TabStats/styled.tsx b/src/features/MatchSidePlaylists/components/TabStats/styled.tsx
index f2f58203..13318a33 100644
--- a/src/features/MatchSidePlaylists/components/TabStats/styled.tsx
+++ b/src/features/MatchSidePlaylists/components/TabStats/styled.tsx
@@ -1,6 +1,7 @@
import styled, { css } from 'styled-components/macro'
import { TooltipWrapper } from 'features/Tooltip'
+import { T9n } from 'features/T9n'
export const Container = styled.div``
@@ -14,42 +15,40 @@ export const TabList = styled.div.attrs({ role: 'tablist' })`
display: flex;
`
-type TabProps = {
- selected?: boolean,
-}
-
-export const Tab = styled.div.attrs(({ selected }: TabProps) => ({
- 'aria-pressed': selected,
- role: 'tab',
-}))`
+export const Tab = styled.button.attrs({ role: 'tab' })`
display: flex;
justify-content: space-between;
align-items: center;
- padding: 0 15px 10px;
+ padding: 0 10px 10px;
font-size: 12px;
color: ${({ theme }) => theme.colors.white};
- opacity: ${({ selected }) => (selected ? '1' : '0.4')};
+ opacity: 0.4;
cursor: pointer;
+ border: none;
+ background: none;
border-bottom: 2px solid transparent;
-
- ${({ selected, theme }) => (selected
- ? css`
- border-color: ${theme.colors.white};
- `
- : '')}
+ &[aria-pressed="true"] {
+ opacity: 1;
+ border-color: currentColor;
+ }
`
export const Switch = styled.div`
display: flex;
`
-export const SwitchTitle = styled.span`
+export const SwitchTitle = styled(T9n)`
font-size: 12px;
color: ${({ theme }) => theme.colors.white};
+ white-space: nowrap;
`
-export const SwitchButton = styled.button`
+type SwitchButtonProps = {
+ isFinalStatsType: boolean,
+}
+
+export const SwitchButton = styled.button`
position: relative;
width: 20px;
height: 7px;
@@ -63,9 +62,12 @@ export const SwitchButton = styled.button`
${TooltipWrapper} {
left: auto;
right: 0;
+ top: 15px;
padding: 2px 10px;
border-radius: 6px;
transform: none;
+ font-size: 11px;
+ line-height: 1;
::before {
display: none;
@@ -78,7 +80,7 @@ export const SwitchButton = styled.button`
}
}
- ${({ theme }) => (true // Позже будет добавлен пропс
+ ${({ isFinalStatsType, theme }) => (!isFinalStatsType
? css`
background-image: linear-gradient(
to right,
diff --git a/src/features/MatchSidePlaylists/components/TabWatch/index.tsx b/src/features/MatchSidePlaylists/components/TabWatch/index.tsx
index 3c1acf33..368e957e 100644
--- a/src/features/MatchSidePlaylists/components/TabWatch/index.tsx
+++ b/src/features/MatchSidePlaylists/components/TabWatch/index.tsx
@@ -1,6 +1,11 @@
-import { Fragment } from 'react'
+import {
+ Fragment,
+ useMemo,
+ useRef,
+} from 'react'
import size from 'lodash/size'
+import filter from 'lodash/filter'
import type {
PlaylistOption,
@@ -10,12 +15,13 @@ import type {
import type { MatchInfo } from 'requests'
import { DropdownSection } from '../DropdownSection'
-import { MatchPlaylists } from '../MatchPlaylists'
+import { MatchPlaylists, LIST_ITEM_INDENT } from '../MatchPlaylists'
import { SideInterviews } from '../SideInterviews'
-import { TabVideo } from '../../components/TabVideo'
+import { Matches } from '../Matches'
type Props = {
onSelect: (option: PlaylistOption) => void,
+ playListFilter: number,
playlists: Playlists,
profile: MatchInfo,
selectedPlaylist?: PlaylistOption,
@@ -24,31 +30,50 @@ type Props = {
export const TabWatch = ({
onSelect,
+ playListFilter,
playlists,
profile,
selectedPlaylist,
tournamentData,
-}: Props) => (
-
-
-
- {
+ const matchPlaylistsRef = useRef(null)
+
+ const additionalScrollHeight = (matchPlaylistsRef.current?.clientHeight || 0) + LIST_ITEM_INDENT
+
+ const filteredPlayListByDuration = useMemo(() => (
+ filter(playlists.match, (playlist) => (
+ profile?.live
+ ? Boolean(playlist.duration) || (playlist.id === 'full_game')
+ : Boolean(playlist.duration)
+ ))
+ ), [playlists.match, profile?.live])
+
+ return (
+
+ {playListFilter > 1 && (
+
+ )}
+
+
+
+
-
-
-
-)
+
+ )
+}
diff --git a/src/features/MatchSidePlaylists/components/TeamsStats/index.tsx b/src/features/MatchSidePlaylists/components/TeamsStats/index.tsx
deleted file mode 100644
index 17a1dad5..00000000
--- a/src/features/MatchSidePlaylists/components/TeamsStats/index.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-import {
- Container,
- Row,
- TeamShortName,
- ParamValueContainer,
- ParamValue,
- ParamTitle,
-} from './styled'
-
-export const TeamsStats = () => (
-
-
- DIN
- SPA
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-
- 90
-
- Points
- 123
-
-
-)
diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
new file mode 100644
index 00000000..d4698aea
--- /dev/null
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
@@ -0,0 +1,31 @@
+import isNumber from 'lodash/isNumber'
+import find from 'lodash/find'
+import round from 'lodash/round'
+
+import type { Param } from 'requests'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+
+export const useTeamsStatsTable = () => {
+ const { profile, teamsStats } = useMatchPageStore()
+
+ const getDisplayedValue = (val: any) => (
+ isNumber(val) ? round(val, 2) : '-'
+ )
+
+ const getStatItemById = (paramId: number) => {
+ if (!profile) return null
+
+ return find(teamsStats[profile?.team2.id], ({ param1 }) => param1.id === paramId) || null
+ }
+
+ const isClickable = (param: Param) => (
+ Boolean(param.val) && param.clickable
+ )
+
+ return {
+ getDisplayedValue,
+ getStatItemById,
+ isClickable,
+ }
+}
diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
new file mode 100644
index 00000000..610d2c8c
--- /dev/null
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
@@ -0,0 +1,93 @@
+import { Fragment } from 'react'
+
+import map from 'lodash/map'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+import { useLexicsStore } from 'features/LexicsStore'
+
+import { useTeamsStatsTable } from './hooks'
+import {
+ Container,
+ Row,
+ TeamShortName,
+ ParamValueContainer,
+ ParamValue,
+ StatItemTitle,
+ Divider,
+} from './styled'
+
+export const TeamsStatsTable = () => {
+ const { profile, teamsStats } = useMatchPageStore()
+ const {
+ getDisplayedValue,
+ getStatItemById,
+ isClickable,
+ } = useTeamsStatsTable()
+ const { lang } = useLexicsStore()
+
+ if (!profile) return null
+
+ return (
+
+
+
+
+
+
+ {map(teamsStats[profile.team1.id], (team1StatItem) => {
+ const team2StatItem = getStatItemById(team1StatItem.param1.id)
+ const statItemTitle = team1StatItem[`name_${lang === 'ru' ? 'ru' : 'en'}`]
+
+ return (
+
+
+
+ {getDisplayedValue(team1StatItem.param1.val)}
+
+ {team1StatItem.param2 && (
+
+ /
+
+ {getDisplayedValue(team1StatItem.param2.val)}
+
+
+ )}
+
+
+ {statItemTitle}
+
+ {team2StatItem && (
+
+
+ {getDisplayedValue(team2StatItem.param1.val)}
+
+ {team2StatItem.param2 && (
+
+ /
+
+ {getDisplayedValue(team2StatItem.param2.val)}
+
+
+ )}
+
+ )}
+
+ )
+ })}
+
+ )
+}
diff --git a/src/features/MatchSidePlaylists/components/TeamsStats/styled.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
similarity index 68%
rename from src/features/MatchSidePlaylists/components/TeamsStats/styled.tsx
rename to src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
index 222b9918..39b3a3c7 100644
--- a/src/features/MatchSidePlaylists/components/TeamsStats/styled.tsx
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
@@ -1,16 +1,16 @@
import styled, { css } from 'styled-components/macro'
-import { customScrollbar } from 'features/Common'
+import { Name } from 'features/Name'
export const Container = styled.div`
width: 100%;
font-size: 11px;
+ overflow: hidden;
+ border-radius: 5px;
background-color: #333333;
-
- ${customScrollbar}
`
-export const TeamShortName = styled.span`
+export const TeamShortName = styled(Name)`
color: ${({ theme }) => theme.colors.white};
letter-spacing: -0.078px;
text-transform: uppercase;
@@ -37,7 +37,10 @@ type TParamValue = {
clickable?: boolean,
}
-export const ParamValue = styled.span`
+export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({
+ ...clickable && { tabIndex: 0 },
+}))`
+ font-weight: 600;
color: ${({ clickable, theme }) => (clickable ? '#5EB2FF' : theme.colors.white)};
${({ clickable }) => (clickable
@@ -47,10 +50,16 @@ export const ParamValue = styled.span`
: '')}
`
-export const ParamTitle = styled.span`
+export const StatItemTitle = styled.span`
color: ${({ theme }) => theme.colors.white};
letter-spacing: -0.078px;
text-transform: uppercase;
font-weight: 600;
opacity: 0.5;
`
+
+export const Divider = styled.span`
+ color: ${({ theme }) => theme.colors.white};
+ opacity: 0.5;
+ font-weight: 600;
+`
diff --git a/src/features/MatchSidePlaylists/hooks.tsx b/src/features/MatchSidePlaylists/hooks.tsx
index 689c6fe5..17e889c5 100644
--- a/src/features/MatchSidePlaylists/hooks.tsx
+++ b/src/features/MatchSidePlaylists/hooks.tsx
@@ -5,6 +5,8 @@ import {
} from 'react'
import reduce from 'lodash/reduce'
+import isEmpty from 'lodash/isEmpty'
+import compact from 'lodash/compact'
import { useMatchPageStore } from 'features/MatchPage/store'
@@ -14,26 +16,54 @@ export const useMatchSidePlaylists = () => {
const {
closePopup,
events,
+ isEmptyPlayersStats,
matchPlaylists: playlists,
+ profile: matchProfile,
+ teamsStats,
+ tournamentData,
} = useMatchPageStore()
const [selectedTab, setSelectedTab] = useState(Tabs.WATCH)
- const isWatchTabVisible = useMemo(() => {
- const playListFilter = reduce(
- playlists.match,
- (acc, item) => {
- let result = acc
- if (item.duration) result++
- return result
- },
- 0,
- )
- return playListFilter > 1
- }, [playlists])
+
+ const playListFilter = useMemo(() => reduce(
+ playlists.match,
+ (acc, item) => {
+ let result = acc
+ if (item.duration) result++
+ return result
+ },
+ 0,
+ ), [playlists.match])
+
+ const isWatchTabVisible = useMemo(() => (
+ playListFilter > 1 || tournamentData.matchDates.length > 1
+ ), [playListFilter, tournamentData.matchDates.length])
const isEventTabVisible = useMemo(() => (
events.length > 0
), [events])
+ const isPlayersTabVisible = useMemo(() => (
+ !isEmpty(playlists.players.team1)
+ ), [playlists.players.team1])
+
+ const isStatsTabVisible = useMemo(() => (
+ !isEmpty(teamsStats)
+ || (matchProfile?.team1.id && !isEmptyPlayersStats(matchProfile.team1.id))
+ || (matchProfile?.team2.id && !isEmptyPlayersStats(matchProfile.team2.id))
+ ), [
+ isEmptyPlayersStats,
+ matchProfile?.team1.id,
+ matchProfile?.team2.id,
+ teamsStats,
+ ])
+
+ const hasLessThanFourTabs = compact([
+ isWatchTabVisible,
+ isEventTabVisible,
+ isPlayersTabVisible,
+ // isStatsTabVisible,
+ ]).length < 4
+
useEffect(() => {
switch (true) {
case isWatchTabVisible:
@@ -42,18 +72,32 @@ export const useMatchSidePlaylists = () => {
case isEventTabVisible:
setSelectedTab(Tabs.EVENTS)
break
+ case isPlayersTabVisible:
+ setSelectedTab(Tabs.PLAYERS)
+ break
+ // case isStatsTabVisible:
+ // setSelectedTab(Tabs.STATS)
+ // break
}
- }, [isEventTabVisible, isWatchTabVisible])
+ }, [
+ isEventTabVisible,
+ isPlayersTabVisible,
+ // isStatsTabVisible,
+ isWatchTabVisible,
+ ])
useEffect(() => {
if (selectedTab !== Tabs.EVENTS) closePopup()
}, [selectedTab, closePopup])
return {
+ hasLessThanFourTabs,
isEventTabVisible,
- isStatsTabVisible: true,
+ isPlayersTabVisible,
+ isStatsTabVisible,
isWatchTabVisible,
onTabClick: setSelectedTab,
+ playListFilter,
selectedTab,
}
}
diff --git a/src/features/MatchSidePlaylists/index.tsx b/src/features/MatchSidePlaylists/index.tsx
index 25b08a12..e50cafe2 100644
--- a/src/features/MatchSidePlaylists/index.tsx
+++ b/src/features/MatchSidePlaylists/index.tsx
@@ -42,8 +42,6 @@ type Props = {
setCircleAnimation?: TSetCircleAnimation,
}
-const hasLessThanFourTabs = false
-
export const MatchSidePlaylists = ({
circleAnimation,
onSelect,
@@ -59,10 +57,13 @@ export const MatchSidePlaylists = ({
} = useMatchPageStore()
const {
+ hasLessThanFourTabs,
isEventTabVisible,
- isStatsTabVisible,
+ isPlayersTabVisible,
+ // isStatsTabVisible,
isWatchTabVisible,
onTabClick,
+ playListFilter,
selectedTab,
} = useMatchSidePlaylists()
@@ -108,7 +109,7 @@ export const MatchSidePlaylists = ({
{isWatchTabVisible ? (
onTabClick(Tabs.WATCH)}
>
@@ -117,37 +118,38 @@ export const MatchSidePlaylists = ({
) : null}
{isEventTabVisible ? (
onTabClick(Tabs.EVENTS)}
>
) : null}
- {isStatsTabVisible ? (
+ {isPlayersTabVisible ? (
onTabClick(Tabs.PLAYERS)}
>
-
+
) : null}
- {isStatsTabVisible ? (
+ {/* {isStatsTabVisible ? (
onTabClick(Tabs.STATS)}
>
- ) : null}
+ ) : null} */}
diff --git a/src/features/MatchSidePlaylists/styled.tsx b/src/features/MatchSidePlaylists/styled.tsx
index 1a8a9e36..0ba4d49a 100644
--- a/src/features/MatchSidePlaylists/styled.tsx
+++ b/src/features/MatchSidePlaylists/styled.tsx
@@ -43,7 +43,7 @@ export const TabsGroup = styled.div.attrs({ role: 'tablist' })`
height: 40px;
${Tab} {
- justify-content: initial;
+ justify-content: center;
flex-direction: row;
gap: 5px;
}
@@ -56,24 +56,30 @@ export const TabsGroup = styled.div.attrs({ role: 'tablist' })`
: ''};
`
-type TabProps = {
- selected?: boolean,
-}
+export const TabTitle = styled(T9n)`
+ font-size: 10px;
+ font-weight: 500;
+ text-transform: uppercase;
+ color: ${({ theme }) => theme.colors.white};
+`
-export const Tab = styled.div.attrs(({ selected }: TabProps) => ({
- 'aria-pressed': selected,
- role: 'tab',
-}))`
+export const Tab = styled.button.attrs({ role: 'tab' })`
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
flex: 1;
- opacity: ${({ selected }) => (selected ? '1' : '0.4')};
+ opacity: 0.4;
cursor: pointer;
+ border: none;
+ background: none;
- :hover {
+ &[aria-pressed="true"], :hover {
opacity: 1;
+
+ ${TabTitle} {
+ font-weight: 600;
+ }
}
`
@@ -82,8 +88,8 @@ type TabIconProps = {
}
export const TabIcon = styled.div`
- width: 20px;
- height: 20px;
+ width: 22px;
+ height: 22px;
background-image: url(/images/matchTabs/${({ icon }) => `${icon}.svg`});
background-repeat: no-repeat;
background-position: center;
@@ -96,28 +102,22 @@ export const TabIcon = styled.div`
: '')}
`
-export const TabTitle = styled(T9n)`
- font-size: 8px;
- text-transform: uppercase;
- color: ${({ theme }) => theme.colors.white};
-`
-
type TContainer = {
+ forWatchTab?: boolean,
hasScroll: boolean,
}
export const Container = styled.div`
width: 320px;
- margin-top: 23px;
+ margin-top: 14px;
max-height: calc(100vh - 130px);
- overflow-y: ${({ forVideoTab }) => (forVideoTab ? 'hidden' : 'auto')};
- padding-right: ${({ forVideoTab }) => (forVideoTab ? '0' : '')};
+ overflow-y: ${({ forWatchTab }) => (forWatchTab ? 'hidden' : 'auto')};
+ padding-right: ${({ forWatchTab }) => (forWatchTab ? '0' : '')};
padding-left: 14px;
padding-right: ${({ hasScroll }) => (hasScroll ? '10px' : '')};
${customScrollbar}
-
@media ${devices.tablet} {
margin-top: 15px;
}
diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx
index 37097c1d..5d8f42d1 100644
--- a/src/features/MultiSourcePlayer/hooks/index.tsx
+++ b/src/features/MultiSourcePlayer/hooks/index.tsx
@@ -57,6 +57,7 @@ export type Props = {
chapters: Chapters,
isOpenPopup?: boolean,
onError?: () => void,
+ onPlayerProgressChange?: (ms: number) => void,
onPlayingChange: (playing: boolean) => void,
profile: MatchInfo,
setCircleAnimation: TSetCircleAnimation,
@@ -65,6 +66,7 @@ export type Props = {
export const useMultiSourcePlayer = ({
chapters,
onError,
+ onPlayerProgressChange,
onPlayingChange,
setCircleAnimation,
}: Props) => {
@@ -202,6 +204,7 @@ export const useMultiSourcePlayer = ({
timeForStatistics.current = (value + chapter.startMs) / 1000
setPlayerState({ playedProgress: value })
+ onPlayerProgressChange?.(playedMs + chapter.startMs)
}
const onEnded = () => {
diff --git a/src/features/StreamPlayer/index.tsx b/src/features/StreamPlayer/index.tsx
index 0d550ab1..b8ed65cd 100644
--- a/src/features/StreamPlayer/index.tsx
+++ b/src/features/StreamPlayer/index.tsx
@@ -35,11 +35,7 @@ const tournamentsWithWatermark = {
* HLS плеер, применяется на лайв и завершенных матчах
*/
export const StreamPlayer = (props: Props) => {
- const {
- access,
- isOpenFiltersPopup,
- profile,
- } = useMatchPageStore()
+ const { isOpenFiltersPopup, profile } = useMatchPageStore()
const { user } = useAuthStore()
const {
diff --git a/src/helpers/getTeamAbbr/index.tsx b/src/helpers/getTeamAbbr/index.tsx
new file mode 100644
index 00000000..62a1eb51
--- /dev/null
+++ b/src/helpers/getTeamAbbr/index.tsx
@@ -0,0 +1,25 @@
+import toUpper from 'lodash/toUpper'
+import split from 'lodash/split'
+import size from 'lodash/size'
+
+import pipe from 'lodash/fp/pipe'
+import take from 'lodash/fp/take'
+import join from 'lodash/fp/join'
+import map from 'lodash/fp/map'
+
+export const getTeamAbbr = (teamName: string) => {
+ const nameParts = split(teamName, ' ')
+
+ return size(nameParts) > 1
+ ? pipe(
+ map(take(1)),
+ join(''),
+ toUpper,
+ )(nameParts)
+
+ : pipe(
+ take(3),
+ join(''),
+ toUpper,
+ )(nameParts[0])
+}
diff --git a/src/helpers/index.tsx b/src/helpers/index.tsx
index 734a5130..0577a810 100644
--- a/src/helpers/index.tsx
+++ b/src/helpers/index.tsx
@@ -8,3 +8,4 @@ export * from './secondsToHms'
export * from './redirectToUrl'
export * from './getRandomString'
export * from './selectedApi'
+export * from './getTeamAbbr'
diff --git a/src/hooks/usePageParams.tsx b/src/hooks/usePageParams.tsx
index 4810663c..4d1df1c7 100644
--- a/src/hooks/usePageParams.tsx
+++ b/src/hooks/usePageParams.tsx
@@ -22,6 +22,7 @@ export const usePageParams = () => {
return {
profileId: Number(pageId),
profileType: ProfileTypes[toUpper(profileName) as keyof typeof ProfileTypes],
+ sportName,
sportType: SportTypes[toUpper(sportName) as keyof typeof SportTypes],
}
}
diff --git a/src/requests/getMatchParticipants.tsx b/src/requests/getMatchParticipants.tsx
new file mode 100644
index 00000000..645c3c03
--- /dev/null
+++ b/src/requests/getMatchParticipants.tsx
@@ -0,0 +1,65 @@
+import isUndefined from 'lodash/isUndefined'
+
+import { SportTypes } from 'config'
+
+import { callApi } from 'helpers'
+
+export type Player = {
+ birthday: string | null,
+ c_country: number,
+ c_gender: number,
+ club_f_team: number,
+ club_shirt_num: number,
+ firstname_eng: string,
+ firstname_national: string | null,
+ firstname_rus: string,
+ height: number | null,
+ id: number,
+ is_gk: boolean,
+ lastname_eng: string,
+ lastname_national: string | null,
+ lastname_rus: string,
+ national_f_team: number | null,
+ national_shirt_num: number,
+ nickname_eng: string | null,
+ nickname_rus: string | null,
+ weight: number | null,
+}
+
+type DataItem = {
+ players: Array,
+ team_id: number,
+}
+
+type Response = {
+ data?: Array,
+ error?: {
+ code: string,
+ message: string,
+ },
+}
+
+type GetMatchParticipantsArgs = {
+ matchId: number,
+ second?: number,
+ sportType: SportTypes,
+}
+
+export const getMatchParticipants = async ({
+ matchId,
+ second,
+ sportType,
+}: GetMatchParticipantsArgs) => {
+ const config = {
+ method: 'GET',
+ }
+
+ const response: Response = await callApi({
+ config,
+ url: `http://136.243.17.103:8888/ask/participants?sport_id=${sportType}&match_id=${matchId}${isUndefined(second) ? '' : `&second=${second}`}`,
+ })
+
+ if (response.error) Promise.reject(response)
+
+ return Promise.resolve(response.data || [])
+}
diff --git a/src/requests/getPlayersStats.tsx b/src/requests/getPlayersStats.tsx
new file mode 100644
index 00000000..3fb5d93f
--- /dev/null
+++ b/src/requests/getPlayersStats.tsx
@@ -0,0 +1,54 @@
+import isUndefined from 'lodash/isUndefined'
+
+import { callApi } from 'helpers'
+
+export type PlayerParam = {
+ clickable: boolean,
+ data_type: string,
+ id: number,
+ lexic: number,
+ lexica_short: number | null,
+ markers: Array | null,
+ name_en: string,
+ name_ru: string,
+ val: number | null,
+}
+
+export type PlayersStats = {
+ [playerId: string]: {
+ [paramId: string]: PlayerParam,
+ },
+}
+
+type Response = {
+ data?: PlayersStats,
+ error?: string,
+ message?: string,
+}
+
+type GetPlayersStatsArgs = {
+ matchId: number,
+ second?: number,
+ sportName: string,
+ teamId: number,
+}
+
+export const getPlayersStats = async ({
+ matchId,
+ second,
+ sportName,
+ teamId,
+}: GetPlayersStatsArgs) => {
+ const config = {
+ method: 'GET',
+ }
+
+ const response: Response = await callApi({
+ config,
+ url: `http://136.243.17.103:8888/${sportName}/matches/${matchId}/teams/${teamId}/players/stats${isUndefined(second) ? '' : `?second=${second}`}`,
+ })
+
+ if (response.error) Promise.reject(response)
+
+ return Promise.resolve(response.data || {})
+}
diff --git a/src/requests/getTeamsStats.tsx b/src/requests/getTeamsStats.tsx
new file mode 100644
index 00000000..acdf40bd
--- /dev/null
+++ b/src/requests/getTeamsStats.tsx
@@ -0,0 +1,56 @@
+import isUndefined from 'lodash/isUndefined'
+
+import { callApi } from 'helpers'
+
+export type Param = {
+ clickable: boolean,
+ data_type: string,
+ id: number,
+ lexic: number,
+ markers: Array,
+ name_en: string,
+ name_ru: string,
+ val: number | null,
+}
+
+export type TeamStatItem = {
+ lexic: number,
+ name_en: string,
+ name_ru: string,
+ order: number,
+ param1: Param,
+ param2: Param | null,
+}
+
+type Response = {
+ data?: {
+ [teamId: string]: Array,
+ },
+ error?: string,
+ message?: string,
+}
+
+type GetTeamsStatsArgs = {
+ matchId: number,
+ second?: number,
+ sportName: string,
+}
+
+export const getTeamsStats = async ({
+ matchId,
+ second,
+ sportName,
+}: GetTeamsStatsArgs) => {
+ const config = {
+ method: 'GET',
+ }
+
+ const response: Response = await callApi({
+ config,
+ url: `http://136.243.17.103:8888/${sportName}/matches/${matchId}/teams/stats?group_num=0${isUndefined(second) ? '' : `&second=${second}`}`,
+ })
+
+ if (response.error) Promise.reject(response)
+
+ return Promise.resolve(response.data || {})
+}
diff --git a/src/requests/index.tsx b/src/requests/index.tsx
index fd1e81e3..0685ea9b 100644
--- a/src/requests/index.tsx
+++ b/src/requests/index.tsx
@@ -27,3 +27,6 @@ export * from './buySubscription'
export * from './saveMatchStats'
export * from './getGeoInfo'
export * from './getTokenVirtualUser'
+export * from './getTeamsStats'
+export * from './getPlayersStats'
+export * from './getMatchParticipants'