feat(in-142): players stats

pull/6/head
Ruslan Khayrullin 3 years ago
parent f4f5b38791
commit c53c2b1d55
  1. 10
      src/features/MatchPage/store/hooks/index.tsx
  2. 24
      src/features/MatchPage/store/hooks/useMatchData.tsx
  3. 159
      src/features/MatchPage/store/hooks/usePlayersStats.tsx
  4. 12
      src/features/MatchPage/store/hooks/useStatsTab.tsx
  5. 15
      src/features/MatchPage/store/hooks/useTeamsStats.tsx
  6. 7
      src/features/MatchSidePlaylists/components/PlayersTable/config.tsx
  7. 61
      src/features/MatchSidePlaylists/components/PlayersTable/hooks.tsx
  8. 58
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx
  9. 83
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/usePlayers.tsx
  10. 171
      src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
  11. 1812
      src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
  12. 204
      src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
  13. 8
      src/features/MatchSidePlaylists/components/PlayersTable/types.tsx
  14. 41
      src/features/MatchSidePlaylists/components/TabStats/hooks.tsx
  15. 42
      src/features/MatchSidePlaylists/components/TabStats/index.tsx
  16. 3
      src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
  17. 29
      src/features/MatchSidePlaylists/hooks.tsx
  18. 25
      src/helpers/getTeamAbbr/index.tsx
  19. 1
      src/helpers/index.tsx
  20. 1
      src/hooks/index.tsx
  21. 65
      src/requests/getMatchParticipants.tsx
  22. 54
      src/requests/getPlayersStats.tsx
  23. 2
      src/requests/getTeamsStats.tsx
  24. 2
      src/requests/index.tsx

@ -65,10 +65,11 @@ export const useMatchPage = () => {
const {
events,
fetchTeamsStats,
handlePlaylistClick,
isEmptyPlayersStats,
matchPlaylists,
playingProgress,
playersData,
playersStats,
selectedPlaylist,
setFullMatchPlaylistDuration,
setPlayingProgress,
@ -162,11 +163,11 @@ export const useMatchPage = () => {
countOfFilters,
disablePlayingEpisodes,
events,
fetchTeamsStats,
filteredEvents,
handlePlaylistClick,
hideProfileCard,
isEmptyFilters,
isEmptyPlayersStats,
isLiveMatch,
isOpenFiltersPopup,
isPlayFilterEpisodes,
@ -177,7 +178,8 @@ export const useMatchPage = () => {
plaingOrder,
playEpisodes,
playNextEpisode,
playingProgress,
playersData,
playersStats,
profile,
profileCardShown,
resetEvents,

@ -17,6 +17,8 @@ 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
@ -35,12 +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 {
fetchTeamsStats,
setStatsType,
isEmptyPlayersStats,
playersData,
playersStats,
} = usePlayersStats({
matchProfile: profile,
playingProgress,
statsType,
teamsStats,
} = useTeamsStats(profile, playingProgress)
})
const fetchPlaylistsDebounced = useMemo(
() => debounce(fetchMatchPlaylists, MATCH_PLAYLISTS_DELAY),
@ -100,10 +111,11 @@ export const useMatchData = (profile: MatchInfo) => {
return {
events,
fetchTeamsStats,
handlePlaylistClick,
isEmptyPlayersStats,
matchPlaylists,
playingProgress,
playersData,
playersStats,
selectedPlaylist,
setFullMatchPlaylistDuration,
setPlayingProgress,

@ -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<Player>,
team2: Array<Player>,
}
export const usePlayersStats = ({
matchProfile,
playingProgress,
statsType,
}: UsePlayersStatsArgs) => {
const [playersStats, setPlayersStats] = useObjectState<Record<string, PlayersStats>>({})
const [playersData, setPlayersData] = useState<PlayersData>({ 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,
}
}

@ -0,0 +1,12 @@
import { useState } from 'react'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
export const useStatsTab = () => {
const [statsType, setStatsType] = useState<StatsType>(StatsType.FINAL_STATS)
return {
setStatsType,
statsType,
}
}

@ -16,8 +16,17 @@ import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/confi
const REQUEST_DELAY = 3000
const STATS_POLL_INTERVAL = 30000
export const useTeamsStats = (matchProfile: MatchInfo, playingProgress: number) => {
const [statsType, setStatsType] = useState<StatsType>(StatsType.FINAL_STATS)
type UseTeamsStatsArgs = {
matchProfile: MatchInfo,
playingProgress: number,
statsType: StatsType,
}
export const useTeamsStats = ({
matchProfile,
playingProgress,
statsType,
}: UseTeamsStatsArgs) => {
const [teamsStats, setTeamsStats] = useState<{
[teamId: string]: Array<TeamStatItem>,
}>({})
@ -63,8 +72,6 @@ export const useTeamsStats = (matchProfile: MatchInfo, playingProgress: number)
}, [fetchTeamsStats, progressSec, isCurrentStats])
return {
fetchTeamsStats,
setStatsType,
statsType,
teamsStats,
}

@ -1 +1,6 @@
export const CELL_WIDTH = 47
export const PARAM_COLUMN_WIDTH = 50
export const REQUEST_DELAY = 3000
export const STATS_POLL_INTERVAL = 30000
export const DISPLAYED_PARAMS_COLUMNS = 4
export const FIRST_COLUMN_WIDTH_DEFAULT = 100
export const SCROLLBAR_WIDTH = 8

@ -1,61 +0,0 @@
import type { SyntheticEvent } from 'react'
import {
useRef,
useState,
useEffect,
} from 'react'
import { isMobileDevice } from 'config/userAgent'
import { CELL_WIDTH } from './config'
export const usePlayersTable = () => {
const containerRef = useRef<HTMLDivElement>(null)
const tableWrapperRef = useRef<HTMLDivElement>(null)
const [showLeftArrow, setShowLeftArrow] = useState(false)
const [showRightArrow, setShowRightArrow] = useState(false)
const cellWidth = isMobileDevice
? ((containerRef.current?.clientWidth || 0) - 98) / 4
: CELL_WIDTH
const slideLeft = () => tableWrapperRef.current!.scrollBy(-cellWidth, 0)
const slideRight = () => tableWrapperRef.current!.scrollBy(cellWidth, 0)
const handleScroll = (e: SyntheticEvent<HTMLDivElement>) => {
const {
clientWidth,
scrollLeft,
scrollWidth,
} = e.currentTarget
const scrollRight = scrollWidth - (scrollLeft + clientWidth)
setShowLeftArrow(scrollLeft > 1)
setShowRightArrow(scrollRight > 1)
}
useEffect(() => {
const {
clientWidth = 0,
scrollLeft = 0,
scrollWidth = 0,
} = tableWrapperRef.current || {}
const scrollRight = scrollWidth - (scrollLeft + clientWidth)
setShowRightArrow(scrollRight > 1)
}, [])
return {
cellWidth,
containerRef,
handleScroll,
showLeftArrow,
showRightArrow,
slideLeft,
slideRight,
tableWrapperRef,
}
}

@ -0,0 +1,58 @@
import { useState } from 'react'
import type { SortCondition, PlayersTableProps } from '../types'
import { usePlayers } from './usePlayers'
import { useTable } from './useTable'
export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
const [sortCondition, setSortCondition] = useState<SortCondition>({ 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,
}
}

@ -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,
}
}

@ -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<SetStateAction<SortCondition>>,
teamId: number,
}
type HeaderParam = Pick<PlayerParam, 'id' | 'lexica_short' | 'lexic'>
export const useTable = ({
setSortCondition,
teamId,
}: UseTableArgs) => {
const containerRef = useRef<HTMLDivElement>(null)
const tableWrapperRef = useRef<HTMLDivElement>(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, Record<string, HeaderParam>>(
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<HeaderParam, Array<number>>(
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<HTMLDivElement>) => {
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,
}
}

@ -1,109 +1,133 @@
import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
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'
import { CELL_WIDTH } from './config'
type ContainerProps = {
isExpanded?: boolean,
}
export const Container = styled.div`
export const Container = styled.div<ContainerProps>`
${({ isExpanded }) => (isExpanded
? ''
: css`
position: relative;
`)}
`
export const TableWrapper = styled.div`
type TableWrapperProps = {
isExpanded?: boolean,
}
export const TableWrapper = styled.div<TableWrapperProps>`
display: flex;
max-width: 100%;
max-height: calc(100vh - 235px);
border-radius: 5px;
overflow-x: auto;
scroll-behavior: smooth;
background-color: #333333;
z-index: 50;
${customScrollbar}
${({ isExpanded }) => (isExpanded
? css`
position: absolute;
right: 14px;
`
: '')}
`
export const Table = styled.table`
width: 100%;
export const Table = styled.div`
flex-grow: 1;
border-radius: 5px;
border-collapse: collapse;
letter-spacing: -0.078px;
table-layout: fixed;
`
export const Thead = styled.thead`
height: 45px;
border-bottom: 0.5px solid rgba(255, 255, 255, 0.5);
`
type ThProps = {
sorted?: boolean,
width?: number,
}
export const Th = styled.th.attrs({ tabIndex: 0 })<ThProps>`
position: sticky;
left: 0;
top: 0;
width: ${({ width }) => width || CELL_WIDTH}px;
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;
cursor: pointer;
background-color: #333333;
z-index: 2;
line-height: 1;
color: ${({ theme }) => theme.colors.black};
:first-child {
width: 90px;
z-index: 3;
cursor: default;
::before {
display: none;
}
${({ sorted }) => (sorted
? ''
: css`
${ParamShortTitle} {
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.attrs(({ clickable }: TdProps) => ({
export const Cell = styled.div.attrs(({ clickable }: TdProps) => ({
...clickable && { tabIndex: 0 },
}))<TdProps>`
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)};
text-overflow: ellipsis;
overflow: hidden;
color: ${({
clickable,
headerCell,
theme,
}) => (clickable && !headerCell ? '#5EB2FF' : theme.colors.white)};
white-space: nowrap;
background-color: #333333;
${Tooltip} {
top: 35px;
}
:hover {
${Tooltip} {
display: block;
}
}
${({ headerCell }) => (headerCell
? ''
: css`
:first-child {
position: sticky;
left: 0;
z-index: 1;
justify-content: unset;
padding-left: 13px;
text-align: left;
color: ${({ theme }) => theme.colors.white};
}
`)}
${({ sorted }) => (sorted
? css`
@ -111,21 +135,70 @@ export const Td = styled.td.attrs(({ clickable }: TdProps) => ({
`
: '')}
${({ clickable }) => (clickable
${({ clickable, headerCell }) => (clickable || headerCell
? css`
cursor: pointer;
`
: '')}
`
type FirstColumnProps = {
columnWidth?: number,
}
export const FirstColumn = styled.div<FirstColumnProps>`
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<PlayerNameProps>`
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;
z-index: 4;
background-color: #333333;
z-index: 3;
${isMobileDevice
? css`
@ -146,4 +219,23 @@ export const ArrowButtonLeft = styled(ArrowButton)`
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;
}
}
`

@ -0,0 +1,8 @@
export type PlayersTableProps = {
teamId: number,
}
export type SortCondition = {
dir: 'asc' | 'desc',
paramId: number | null,
}

@ -1,4 +1,6 @@
import { useState } from 'react'
import { useEffect, useState } from 'react'
import isEmpty from 'lodash/isEmpty'
import { useMatchPageStore } from 'features/MatchPage/store'
@ -7,21 +9,56 @@ import { StatsType, Tabs } from './config'
export const useTabStats = () => {
const [selectedTab, setSelectedTab] = useState<Tabs>(Tabs.TEAMS)
const { setStatsType, statsType } = useMatchPageStore()
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,

@ -1,7 +1,11 @@
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'
@ -27,37 +31,59 @@ const tabPanes = {
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 (
<Container>
<Header>
<TabList>
{isVisibleTeamsTab && (
<Tab
aria-pressed={selectedTab === Tabs.TEAMS}
onClick={() => setSelectedTab(Tabs.TEAMS)}
>
<T9n t='team' />
</Tab>
{/* <Tab
)}
{isVisibleTeam1PlayersTab && (
<Tab
aria-pressed={selectedTab === Tabs.TEAM1}
onClick={() => setSelectedTab(Tabs.TEAM1)}
>
DIN
</Tab> */}
{/* <Tab
<Name nameObj={{
name_eng: team1.abbrev_eng || getTeamAbbr(team1.name_eng),
name_rus: team1.abbrev_rus || getTeamAbbr(team1.name_rus),
}}
/>
</Tab>
)}
{isVisibleTeam2PlayersTab && (
<Tab
aria-pressed={selectedTab === Tabs.TEAM2}
onClick={() => setSelectedTab(Tabs.TEAM2)}
>
SPA
</Tab> */}
<Name nameObj={{
name_eng: team2.abbrev_eng || getTeamAbbr(team2.name_eng),
name_rus: team2.abbrev_rus || getTeamAbbr(team2.name_rus),
}}
/>
</Tab>
)}
</TabList>
<Switch>
<SwitchTitle t={switchTitleLexic} />
@ -69,7 +95,9 @@ export const TabStats = () => {
</SwitchButton>
</Switch>
</Header>
<TabPane />
<TabPane
teamId={selectedTab === Tabs.TEAM1 ? team1.id : team2.id}
/>
</Container>
)
}

@ -1,5 +1,6 @@
import isNumber from 'lodash/isNumber'
import find from 'lodash/find'
import round from 'lodash/round'
import type { Param } from 'requests'
@ -9,7 +10,7 @@ export const useTeamsStatsTable = () => {
const { profile, teamsStats } = useMatchPageStore()
const getDisplayedValue = (val: any) => (
isNumber(val) ? val : '-'
isNumber(val) ? round(val, 2) : '-'
)
const getStatItemById = (paramId: number) => {

@ -16,7 +16,9 @@ export const useMatchSidePlaylists = () => {
const {
closePopup,
events,
isEmptyPlayersStats,
matchPlaylists: playlists,
profile: matchProfile,
teamsStats,
tournamentData,
} = useMatchPageStore()
@ -46,7 +48,14 @@ export const useMatchSidePlaylists = () => {
const isStatsTabVisible = useMemo(() => (
!isEmpty(teamsStats)
), [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,
@ -57,15 +66,15 @@ export const useMatchSidePlaylists = () => {
useEffect(() => {
switch (true) {
case isWatchTabVisible:
setSelectedTab(Tabs.WATCH)
break
case isEventTabVisible:
setSelectedTab(Tabs.EVENTS)
break
case isPlayersTabVisible:
setSelectedTab(Tabs.PLAYERS)
break
// case isWatchTabVisible:
// setSelectedTab(Tabs.WATCH)
// break
// case isEventTabVisible:
// setSelectedTab(Tabs.EVENTS)
// break
// case isPlayersTabVisible:
// setSelectedTab(Tabs.PLAYERS)
// break
case isStatsTabVisible:
setSelectedTab(Tabs.STATS)
break

@ -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])
}

@ -8,3 +8,4 @@ export * from './secondsToHms'
export * from './redirectToUrl'
export * from './getRandomString'
export * from './selectedApi'
export * from './getTeamAbbr'

@ -4,3 +4,4 @@ export * from './useStorage'
export * from './useInterval'
export * from './useEventListener'
export * from './useObjectState'
export * from './usePageParams'

@ -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<Player>,
team_id: number,
}
type Response = {
data?: Array<DataItem>,
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 || [])
}

@ -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<number> | 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 || {})
}

@ -47,7 +47,7 @@ export const getTeamsStats = async ({
const response: Response = await callApi({
config,
url: `https://statistic.insports.tv/${sportName}/matches/${matchId}/teams/stats?group_num=0${isUndefined(second) ? '' : `&second=${second}`}`,
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)

@ -25,3 +25,5 @@ export * from './getPlayerPlaylists'
export * from './getSubscriptions'
export * from './buySubscription'
export * from './getTeamsStats'
export * from './getPlayersStats'
export * from './getMatchParticipants'

Loading…
Cancel
Save