From 7d89984812608f312cb2b8e3ddb60cf6d2b5c0a0 Mon Sep 17 00:00:00 2001 From: Ruslan Khayrullin Date: Mon, 9 Jan 2023 16:38:04 +0500 Subject: [PATCH] feat(in-141): players stats --- public/images/sortUp.svg | 3 + src/config/routes.tsx | 7 ++ .../components/PlayersTable/hooks/index.tsx | 12 +- .../components/PlayersTable/types.tsx | 4 + .../components/TabPlayers/index.tsx | 22 ++-- .../components/TabStats/hooks.tsx | 27 +++-- .../components/TabStats/index.tsx | 108 ++++++++++++++---- .../components/TabStats/styled.tsx | 54 ++++----- .../components/TeamsStatsTable/styled.tsx | 58 ++++++++-- .../components/TeamsStatsTable/types.tsx | 6 + src/features/MatchSidePlaylists/hooks.tsx | 10 +- src/features/Name/index.tsx | 11 +- src/features/T9n/index.tsx | 3 + src/features/Tooltip/index.tsx | 5 +- src/hooks/index.tsx | 2 + src/requests/getMatchParticipants.tsx | 5 +- src/requests/getPlayersStats.tsx | 4 +- 17 files changed, 248 insertions(+), 93 deletions(-) create mode 100644 public/images/sortUp.svg create mode 100644 src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx diff --git a/public/images/sortUp.svg b/public/images/sortUp.svg new file mode 100644 index 00000000..0f51e40c --- /dev/null +++ b/public/images/sortUp.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/config/routes.tsx b/src/config/routes.tsx index dd866e19..220b0cd5 100644 --- a/src/config/routes.tsx +++ b/src/config/routes.tsx @@ -23,6 +23,12 @@ const VIEWS_APIS = { staging: 'https://views.test.insports.tv', } +const STATS_APIS = { + preproduction: 'https://statistic.insports.tv', + production: 'https://statistic.insports.tv', + staging: 'https://statistic-stage.insports.tv', +} + const env = isProduction ? ENV : readSelectedApi() ?? ENV export const VIEWS_API = VIEWS_APIS[env] @@ -30,3 +36,4 @@ export const AUTH_SERVICE = APIS[env].auth export const API_ROOT = APIS[env].api export const DATA_URL = `${API_ROOT}/data` export const URL_AWS = 'https://cf-aws.insports.tv' +export const STATS_API_URL = STATS_APIS[env] diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx index 537e480a..554afdef 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx @@ -5,17 +5,20 @@ import { usePlayers } from './usePlayers' import { useTable } from './useTable' export const usePlayersTable = ({ teamId }: PlayersTableProps) => { - const [sortCondition, setSortCondition] = useState({ dir: 'asc', paramId: null }) + const [sortCondition, setSortCondition] = useState({ + clicksCount: 0, + dir: 'asc', + paramId: null, + }) const { - getFullName, + getPlayerName, getPlayerParams, players, } = usePlayers({ sortCondition, teamId }) const { containerRef, - firstColumnWidth, getDisplayedValue, handleScroll, handleSortClick, @@ -36,9 +39,8 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => { return { containerRef, - firstColumnWidth, getDisplayedValue, - getFullName, + getPlayerName, getPlayerParams, handleScroll, handleSortClick, diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx index 85b9d054..7eae9005 100644 --- a/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx +++ b/src/features/MatchSidePlaylists/components/PlayersTable/types.tsx @@ -1,8 +1,12 @@ +import { TCircleAnimation } from 'features/CircleAnimationBar' + export type PlayersTableProps = { + circleAnimation?: TCircleAnimation, teamId: number, } export type SortCondition = { + clicksCount: number, dir: 'asc' | 'desc', paramId: number | null, } diff --git a/src/features/MatchSidePlaylists/components/TabPlayers/index.tsx b/src/features/MatchSidePlaylists/components/TabPlayers/index.tsx index bd8244ac..f65c4b88 100644 --- a/src/features/MatchSidePlaylists/components/TabPlayers/index.tsx +++ b/src/features/MatchSidePlaylists/components/TabPlayers/index.tsx @@ -1,5 +1,3 @@ -import isEmpty from 'lodash/isEmpty' - import type { Playlists, PlaylistOption } from 'features/MatchPage/types' import type { MatchInfo } from 'requests' @@ -17,15 +15,11 @@ export const TabPlayers = ({ playlists, profile, selectedPlaylist, -}: Props) => { - if (isEmpty(playlists.players.team1)) return null - - return ( - - ) -} +}: Props) => ( + +) diff --git a/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx b/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx index 1d621ce3..ea8df099 100644 --- a/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx +++ b/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from 'react' import isEmpty from 'lodash/isEmpty' +import { useTooltip } from 'hooks' + import { useMatchPageStore } from 'features/MatchPage/store' import { StatsType, Tabs } from './config' @@ -12,15 +14,23 @@ export const useTabStats = () => { const { isEmptyPlayersStats, profile: matchProfile, - setStatsType, statsType, teamsStats, + toggleStatsType, } = useMatchPageStore() + const { + isTooltipShown, + onMouseLeave, + onMouseOver, + tooltipStyle, + tooltipText, + } = useTooltip() + 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 switchButtonTooltipLexic = isFinalStatsType ? 'display_all_stats' : 'display_stats_according_to_video' const isVisibleTeamsTab = !isEmpty(teamsStats) const isVisibleTeam1PlayersTab = Boolean( @@ -30,12 +40,6 @@ export const useTabStats = () => { matchProfile && !isEmptyPlayersStats(matchProfile.team2.id), ) - const toggleStatsType = () => { - const newStatsType = isFinalStatsType ? StatsType.CURRENT_STATS : StatsType.FINAL_STATS - - setStatsType(newStatsType) - } - useEffect(() => { switch (true) { case isVisibleTeamsTab: @@ -56,13 +60,18 @@ export const useTabStats = () => { return { isFinalStatsType, + isTooltipShown, isVisibleTeam1PlayersTab, isVisibleTeam2PlayersTab, isVisibleTeamsTab, + onMouseLeave, + onMouseOver, selectedTab, setSelectedTab, + switchButtonTooltipLexic, switchTitleLexic, toggleStatsType, - tooltipLexic, + tooltipStyle, + tooltipText, } } diff --git a/src/features/MatchSidePlaylists/components/TabStats/index.tsx b/src/features/MatchSidePlaylists/components/TabStats/index.tsx index d5b20d74..290139a6 100644 --- a/src/features/MatchSidePlaylists/components/TabStats/index.tsx +++ b/src/features/MatchSidePlaylists/components/TabStats/index.tsx @@ -1,11 +1,17 @@ -import { isMobileDevice } from 'config/userAgent' +import type { ComponentProps } from 'react' +import { createPortal } from 'react-dom' + +import { isMobileDevice } from 'config' import { getTeamAbbr } from 'helpers' -import { Tooltip } from 'features/Tooltip' +import { useModalRoot } from 'hooks' + import { T9n } from 'features/T9n' import { useMatchPageStore } from 'features/MatchPage/store' import { Name } from 'features/Name' +import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar' +import { useLexicsStore } from 'features/LexicsStore' import { Tabs } from './config' import { useTabStats } from './hooks' @@ -20,27 +26,44 @@ import { Switch, SwitchTitle, SwitchButton, + Tooltip, + TabTitle, } from './styled' const tabPanes = { [Tabs.TEAMS]: TeamsStatsTable, - [Tabs.TEAM1]: PlayersTable, - [Tabs.TEAM2]: PlayersTable, + // eslint-disable-next-line react/jsx-props-no-spreading + [Tabs.TEAM1]: (props: ComponentProps) => , + // eslint-disable-next-line react/jsx-props-no-spreading + [Tabs.TEAM2]: (props: ComponentProps) => , } -export const TabStats = () => { +type Props = { + circleAnimation?: TCircleAnimation, + setCircleAnimation?: TSetCircleAnimation, +} + +export const TabStats = ({ circleAnimation, setCircleAnimation }: Props) => { const { isFinalStatsType, + isTooltipShown, isVisibleTeam1PlayersTab, isVisibleTeam2PlayersTab, isVisibleTeamsTab, + onMouseLeave, + onMouseOver, selectedTab, setSelectedTab, + switchButtonTooltipLexic, switchTitleLexic, toggleStatsType, - tooltipLexic, + tooltipStyle, + tooltipText, } = useTabStats() const { profile: matchProfile } = useMatchPageStore() + const { suffix, translate } = useLexicsStore() + + const modalRoot = useModalRoot() const TabPane = tabPanes[selectedTab] @@ -57,7 +80,9 @@ export const TabStats = () => { aria-pressed={selectedTab === Tabs.TEAMS} onClick={() => setSelectedTab(Tabs.TEAMS)} > - + + + )} {isVisibleTeam1PlayersTab && ( @@ -65,11 +90,25 @@ export const TabStats = () => { aria-pressed={selectedTab === Tabs.TEAM1} onClick={() => setSelectedTab(Tabs.TEAM1)} > - + + + )} {isVisibleTeam2PlayersTab && ( @@ -77,27 +116,56 @@ export const TabStats = () => { aria-pressed={selectedTab === Tabs.TEAM2} onClick={() => setSelectedTab(Tabs.TEAM2)} > - + + + )} - {!isMobileDevice && } - + onMouseOver={isMobileDevice + ? undefined + : onMouseOver({ + anchorId: 'switchButton', + horizontalPosition: 'right', + tooltipText: translate(switchButtonTooltipLexic), + })} + onMouseLeave={isMobileDevice ? undefined : onMouseLeave} + /> + {isTooltipShown && modalRoot.current && createPortal( + + {tooltipText} + , + modalRoot.current, + )} ) } diff --git a/src/features/MatchSidePlaylists/components/TabStats/styled.tsx b/src/features/MatchSidePlaylists/components/TabStats/styled.tsx index 13318a33..94355c8d 100644 --- a/src/features/MatchSidePlaylists/components/TabStats/styled.tsx +++ b/src/features/MatchSidePlaylists/components/TabStats/styled.tsx @@ -8,29 +8,51 @@ export const Container = styled.div`` export const Header = styled.div` display: flex; justify-content: space-between; - margin-bottom: 23px; + margin-bottom: 6px; ` export const TabList = styled.div.attrs({ role: 'tablist' })` display: flex; ` +export const Tooltip = styled(TooltipWrapper)` + display: block; + padding: 2px 10px; + border-radius: 6px; + transform: none; + font-size: 11px; + line-height: 1; + color: ${({ theme }) => theme.colors.black}; + z-index: 999; + + ::before { + display: none; + } +` + +export const TabTitle = styled.span` + color: ${({ theme }) => theme.colors.white}; + opacity: 0.4; +` + export const Tab = styled.button.attrs({ role: 'tab' })` + position: relative; display: flex; justify-content: space-between; align-items: center; padding: 0 10px 10px; font-size: 12px; - color: ${({ theme }) => theme.colors.white}; - opacity: 0.4; cursor: pointer; border: none; background: none; border-bottom: 2px solid transparent; &[aria-pressed="true"] { - opacity: 1; - border-color: currentColor; + border-color: ${({ theme }) => theme.colors.white}; + + ${TabTitle} { + opacity: 1; + } } ` @@ -49,7 +71,6 @@ type SwitchButtonProps = { } export const SwitchButton = styled.button` - position: relative; width: 20px; height: 7px; margin-left: 5px; @@ -59,27 +80,6 @@ export const SwitchButton = styled.button` border: 1px solid ${({ theme }) => theme.colors.white}; cursor: pointer; - ${TooltipWrapper} { - left: auto; - right: 0; - top: 15px; - padding: 2px 10px; - border-radius: 6px; - transform: none; - font-size: 11px; - line-height: 1; - - ::before { - display: none; - } - } - - :hover { - ${TooltipWrapper} { - display: block; - } - } - ${({ isFinalStatsType, theme }) => (!isFinalStatsType ? css` background-image: linear-gradient( diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx index 39b3a3c7..9aa59d15 100644 --- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx @@ -1,13 +1,27 @@ import styled, { css } from 'styled-components/macro' import { Name } from 'features/Name' +import { customScrollbar } from 'features/Common' -export const Container = styled.div` +export const Container = styled.div`` + +export const TableWrapper = styled.div` width: 100%; + max-height: calc(100vh - 203px); + overflow: auto; font-size: 11px; - overflow: hidden; border-radius: 5px; background-color: #333333; + + ${customScrollbar} +` + +export const Table = styled.table` + width: 100%; + border-spacing: 0; + border-collapse: collapse; + letter-spacing: -0.078px; + table-layout: fixed; ` export const TeamShortName = styled(Name)` @@ -18,16 +32,44 @@ export const TeamShortName = styled(Name)` opacity: 0.5; ` -export const Row = styled.div` - display: flex; - justify-content: space-between; - align-items: center; +export const Cell = styled.td` height: 45px; - padding: 0 12px; border-bottom: 0.5px solid rgba(255, 255, 255, 0.5); + background-color: #333333; + + :nth-child(2) { + text-align: center; + } + + :first-child, :last-child { + width: 32px; + } + + :first-child { + padding-left: 12px; + } :last-child { - border-bottom: none; + text-align: right; + padding-right: 12px; + } +` + +export const Row = styled.tr` + :last-child:not(:first-child) { + ${Cell} { + border-bottom: none; + } + } +` + +export const Header = styled.thead` + position: sticky; + top: 0; + z-index: 1; + + ${Cell} { + background-color: #292929; } ` diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx new file mode 100644 index 00000000..7bfc8c82 --- /dev/null +++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/types.tsx @@ -0,0 +1,6 @@ +import { TCircleAnimation, TSetCircleAnimation } from 'features/CircleAnimationBar' + +export type Props = { + circleAnimation?: TCircleAnimation, + setCircleAnimation?: TSetCircleAnimation, +} diff --git a/src/features/MatchSidePlaylists/hooks.tsx b/src/features/MatchSidePlaylists/hooks.tsx index 17e889c5..72296124 100644 --- a/src/features/MatchSidePlaylists/hooks.tsx +++ b/src/features/MatchSidePlaylists/hooks.tsx @@ -61,7 +61,7 @@ export const useMatchSidePlaylists = () => { isWatchTabVisible, isEventTabVisible, isPlayersTabVisible, - // isStatsTabVisible, + isStatsTabVisible, ]).length < 4 useEffect(() => { @@ -75,14 +75,14 @@ export const useMatchSidePlaylists = () => { case isPlayersTabVisible: setSelectedTab(Tabs.PLAYERS) break - // case isStatsTabVisible: - // setSelectedTab(Tabs.STATS) - // break + case isStatsTabVisible: + setSelectedTab(Tabs.STATS) + break } }, [ isEventTabVisible, isPlayersTabVisible, - // isStatsTabVisible, + isStatsTabVisible, isWatchTabVisible, ]) diff --git a/src/features/Name/index.tsx b/src/features/Name/index.tsx index a2099b82..5f22a50f 100644 --- a/src/features/Name/index.tsx +++ b/src/features/Name/index.tsx @@ -12,6 +12,7 @@ export type ObjectWithName = { type Props = { className?: string, + id?: string, nameObj: ObjectWithName, prefix?: string, } @@ -44,9 +45,17 @@ export const useName = ( export const Name = ({ className, + id, nameObj, prefix, }: Props) => { const name = useName(nameObj, prefix) - return {name} + return ( + + {name} + + ) } diff --git a/src/features/T9n/index.tsx b/src/features/T9n/index.tsx index 85140c4f..35c3ea48 100644 --- a/src/features/T9n/index.tsx +++ b/src/features/T9n/index.tsx @@ -7,6 +7,7 @@ const Text = styled.span`` type Props = { className?: string, + id?: string, onClick?: () => void, t: LexicsId, values?: Values, @@ -14,6 +15,7 @@ type Props = { export const T9n = ({ className, + id, onClick, t, values, @@ -22,6 +24,7 @@ export const T9n = ({ return ( diff --git a/src/features/Tooltip/index.tsx b/src/features/Tooltip/index.tsx index a436eb2d..b599709d 100644 --- a/src/features/Tooltip/index.tsx +++ b/src/features/Tooltip/index.tsx @@ -34,7 +34,10 @@ export const Tooltip = ({ lexic, }: Props) => ( - + {children} diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx index 8219ac0e..6e2f4ec7 100644 --- a/src/hooks/index.tsx +++ b/src/hooks/index.tsx @@ -5,3 +5,5 @@ export * from './useInterval' export * from './useEventListener' export * from './useObjectState' export * from './usePageParams' +export * from './useTooltip' +export * from './useModalRoot' diff --git a/src/requests/getMatchParticipants.tsx b/src/requests/getMatchParticipants.tsx index 645c3c03..2869470f 100644 --- a/src/requests/getMatchParticipants.tsx +++ b/src/requests/getMatchParticipants.tsx @@ -1,6 +1,6 @@ import isUndefined from 'lodash/isUndefined' -import { SportTypes } from 'config' +import { SportTypes, STATS_API_URL } from 'config' import { callApi } from 'helpers' @@ -23,6 +23,7 @@ export type Player = { national_shirt_num: number, nickname_eng: string | null, nickname_rus: string | null, + num: number | null, weight: number | null, } @@ -56,7 +57,7 @@ export const getMatchParticipants = async ({ 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}`}`, + url: `${STATS_API_URL}/ask/participants?sport_id=${sportType}&match_id=${matchId}${isUndefined(second) ? '' : `&second=${second}`}`, }) if (response.error) Promise.reject(response) diff --git a/src/requests/getPlayersStats.tsx b/src/requests/getPlayersStats.tsx index 3fb5d93f..7bae6058 100644 --- a/src/requests/getPlayersStats.tsx +++ b/src/requests/getPlayersStats.tsx @@ -2,6 +2,8 @@ import isUndefined from 'lodash/isUndefined' import { callApi } from 'helpers' +import { STATS_API_URL } from 'config' + export type PlayerParam = { clickable: boolean, data_type: string, @@ -45,7 +47,7 @@ export const getPlayersStats = async ({ const response: Response = await callApi({ config, - url: `http://136.243.17.103:8888/${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}`}`, }) if (response.error) Promise.reject(response)