diff --git a/package-lock.json b/package-lock.json
index cb4ca879..fd7b1084 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2982,6 +2982,41 @@
"react-lifecycles-compat": "^3.0.4"
}
},
+ "@reactour/mask": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@reactour/mask/-/mask-1.0.5.tgz",
+ "integrity": "sha512-SMakvPUsH83j4MAq87jBMpdzoVQ+amZTQ6rYsDBqN1Hcz+A8JN9IDgaA49UaLcRPZq+ioQrmos8mBTe8uEtPeQ==",
+ "requires": {
+ "@reactour/utils": "*"
+ }
+ },
+ "@reactour/popover": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@reactour/popover/-/popover-1.0.5.tgz",
+ "integrity": "sha512-d6BMcyXGj3RdSc2huiU6v/wG2XG1ad+lAFmjyFerlZNS1ccp/49HvLUnqxE0Td+86e7RPrdEzpZb5PKtBybPrA==",
+ "requires": {
+ "@reactour/utils": "*"
+ }
+ },
+ "@reactour/tour": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@reactour/tour/-/tour-3.3.0.tgz",
+ "integrity": "sha512-Dx/jDKEZ29fSOmnc07zCgHS6lmEKCNreyvFhhPQTI1OAG6MTTWuQJhKUmrytECncFJb+oH95zvE9mf137rSBtA==",
+ "requires": {
+ "@reactour/mask": "*",
+ "@reactour/popover": "*",
+ "@reactour/utils": "*"
+ }
+ },
+ "@reactour/utils": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/@reactour/utils/-/utils-0.4.7.tgz",
+ "integrity": "sha512-d+/Xhi2nKCc6OrDEFGg15iN8ZyWDTdOrwIKkndJXrnWiN6b+nqoS2Tb7hZvt79rqCyUsQxekUndPFPQyIW81EA==",
+ "requires": {
+ "@rooks/use-mutation-observer": "^4.11.2",
+ "resize-observer-polyfill": "^1.5.1"
+ }
+ },
"@rollup/plugin-babel": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3030,6 +3065,11 @@
}
}
},
+ "@rooks/use-mutation-observer": {
+ "version": "4.11.2",
+ "resolved": "https://registry.npmjs.org/@rooks/use-mutation-observer/-/use-mutation-observer-4.11.2.tgz",
+ "integrity": "sha512-vpsdrZdr6TkB1zZJcHx+fR1YC/pHs2BaqcuYiEGjBVbwY5xcC49+h0hAUtQKHth3oJqXfIX/Ng8S7s5HFHdM/A=="
+ },
"@rushstack/eslint-patch": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
@@ -24912,6 +24952,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
+ "resize-observer-polyfill": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
+ "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg=="
+ },
"resolve": {
"version": "1.22.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
diff --git a/package.json b/package.json
index 182a89be..058308d2 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"start": "react-scripts start",
+ "start-https": "export HTTPS=true&&SSL_CRT_FILE=cert.pem&&SSL_KEY_FILE=key.pem react-scripts start",
"build": "GENERATE_SOURCEMAP=false react-scripts build && gzipper --verbose ./build",
"test": "react-scripts test --testMatch '**/__tests__/*' --passWithNoTests --watchAll=false",
"test:watch": "react-scripts test --testMatch '**/__tests__/*'",
@@ -21,6 +22,7 @@
"insports": "REACT_APP_CLIENT=insports react-scripts start"
},
"dependencies": {
+ "@reactour/tour": "^3.3.0",
"@stripe/react-stripe-js": "^1.4.0",
"@stripe/stripe-js": "^1.13.2",
"babel-polyfill": "^6.26.0",
diff --git a/src/components/Overlay/index.tsx b/src/components/Overlay/index.tsx
new file mode 100644
index 00000000..a4bee595
--- /dev/null
+++ b/src/components/Overlay/index.tsx
@@ -0,0 +1,9 @@
+import styled from 'styled-components/macro'
+
+export const Overlay = styled.div`
+ position: fixed;
+ inset: 0;
+ opacity: 0.6;
+ background-color: ${({ theme }) => theme.colors.black};
+ z-index: 9999;
+`
diff --git a/src/config/index.tsx b/src/config/index.tsx
index e94d0b69..a5fd70ae 100644
--- a/src/config/index.tsx
+++ b/src/config/index.tsx
@@ -11,3 +11,4 @@ export * from './env'
export * from './userAgent'
export * from './queries'
export * from './keyboardKeys'
+export * from './clients'
diff --git a/src/config/keyboardKeys.tsx b/src/config/keyboardKeys.tsx
index 97d3b125..ead63a10 100644
--- a/src/config/keyboardKeys.tsx
+++ b/src/config/keyboardKeys.tsx
@@ -1,3 +1,6 @@
export enum KEYBOARD_KEYS {
+ ArrowLeft = 'ArrowLeft',
+ ArrowRight = 'ArrowRight',
Enter = 'Enter',
+ Esc = 'Escape',
}
diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx
index bf1e822a..95118ce8 100644
--- a/src/config/lexics/indexLexics.tsx
+++ b/src/config/lexics/indexLexics.tsx
@@ -7,12 +7,20 @@ import { sportsLexic } from './sportsLexic'
const matchPopupLexics = {
actions: 1020,
apply: 13491,
+ back: 696,
+ blue_stats_are_clickable: 20071,
+ check_out_the_stats: 20066,
choose_fav_team: 19776,
+ click_to_see_full_time_stats: 20074,
+ click_to_see_stats_in_real_time: 20075,
+ click_to_watch_playlist: 20072,
commentators: 15424,
+ compare_teams_across_multiple_metrics: 20070,
continue_watching: 20007,
current_stats: 19592,
display_all_stats: 19932,
display_stats_according_to_video: 19931,
+ end_tour: 20076,
episode_duration: 13410,
events: 1020,
final_stats: 19591,
@@ -24,9 +32,11 @@ const matchPopupLexics = {
go_back_to_match: 13405,
group: 7850,
half_time: 1033,
+ here_you_will_discover_tons: 20067,
languages: 15030,
match_interviews: 13031,
match_settings: 13490,
+ next_step: 15156,
no_data: 15397,
other_games: 19997,
others: 19902,
@@ -38,12 +48,18 @@ const matchPopupLexics = {
sec_60: 20006,
sec_after: 13412,
sec_before: 13411,
+ see_interactive_game_stats: 20069,
selected_player_actions: 13413,
+ show_less_stats: 20064,
+ show_more_stats: 20063,
sign_in: 20003,
sign_in_full_game: 20004,
+ skip_tour: 20065,
+ start_tour: 20062,
started_streaming_at: 16042,
stats: 18179,
streamed_live_on: 16043,
+ team_players_stats: 20073,
video: 1017,
views: 13440,
watch: 818,
@@ -51,6 +67,7 @@ const matchPopupLexics = {
watch_live_stream: 13020,
watch_players_episodes: 14052,
watching_now: 16041,
+ welcom_to_stats_tab: 20068,
}
const filterPopup = {
diff --git a/src/features/MatchPage/components/MatchDescription/index.tsx b/src/features/MatchPage/components/MatchDescription/index.tsx
index 4648d086..6f1df809 100644
--- a/src/features/MatchPage/components/MatchDescription/index.tsx
+++ b/src/features/MatchPage/components/MatchDescription/index.tsx
@@ -2,6 +2,8 @@ import { useCallback } from 'react'
import { useQuery } from 'react-query'
+import { useTour } from '@reactour/tour'
+
import { format } from 'date-fns'
import includes from 'lodash/includes'
@@ -28,6 +30,8 @@ import { usePageParams } from 'hooks/usePageParams'
import { getMatchScore } from 'requests'
+import { Steps } from 'features/MatchTour'
+
import {
Description,
DescriptionInnerBlock,
@@ -48,6 +52,7 @@ export const MatchDescription = () => {
const { isScoreHidden } = useMatchSwitchesStore()
const { suffix } = useLexicsStore()
const { profile, profileCardShown } = useMatchPageStore()
+ const { isOpen } = useTour()
const getTeamName = useCallback((team: Team) => (
isMobileDevice
@@ -68,6 +73,8 @@ export const MatchDescription = () => {
refetchInterval: 5000,
})
+ if (isOpen && !isMobileDevice) return null
+
if (!profile) return
const {
@@ -88,7 +95,9 @@ export const MatchDescription = () => {
return (
-
+
{
usePageLogger()
@@ -39,6 +45,7 @@ const MatchPageComponent = () => {
profile,
user,
} = useMatchPageStore()
+
const isFavorite = profile && userFavorites?.find((fav) => fav.id === profile?.tournament.id)
const {
@@ -46,6 +53,8 @@ const MatchPageComponent = () => {
sportType,
} = usePageParams()
+ const { isOpen } = useTour()
+
useEffect(() => {
let timer = 0
timer = window.setTimeout(() => {
@@ -91,12 +100,15 @@ const MatchPageComponent = () => {
}
return (
-
+
-
+
{playFromOTT && (
)}
@@ -118,7 +130,9 @@ const MatchPageComponent = () => {
const MatchPage = () => (
-
+
+
+
)
diff --git a/src/features/MatchPage/store/hooks/index.tsx b/src/features/MatchPage/store/hooks/index.tsx
index 3e12ba13..ecb63fea 100644
--- a/src/features/MatchPage/store/hooks/index.tsx
+++ b/src/features/MatchPage/store/hooks/index.tsx
@@ -12,6 +12,8 @@ import { useAuthStore } from 'features/AuthStore'
import { Tabs } from 'features/MatchSidePlaylists/config'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import type { TCircleAnimation } from 'features/CircleAnimationBar'
+import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
+import { TOUR_COMPLETED_STORAGE_KEY } from 'features/MatchTour'
import { PAGES } from 'config/pages'
@@ -24,6 +26,7 @@ import { usePageParams, useToggle } from 'hooks'
import { redirectToUrl } from 'helpers/redirectToUrl'
import { parseDate } from 'helpers/parseDate'
+import { setLocalStorageItem } from 'helpers/getLocalStorage'
import { useTournamentData } from './useTournamentData'
import { useMatchData } from './useMatchData'
@@ -192,19 +195,25 @@ export const useMatchPage = () => {
const {
circleAnimation: statsCircleAnimation,
filteredEvents: statsFilteredEvents,
+ isExpanded,
isPlayersStatsFetching,
isPlayFilterEpisodes: isStatsPlayFilterEpisodes,
isTeamsStatsFetching,
plaingOrder: statsPlaingOrder,
playEpisodes: playStatsEpisodes,
playNextEpisode: playStatsNextEpisode,
+ reduceTable,
+ selectedStatsTable,
setCircleAnimation: setStatsCircleAnimation,
setIsPlayersStatsFetching,
setIsPlayingFiltersEpisodes: setStatsIsPlayinFiltersEpisodes,
setIsTeamsStatsFetching,
setPlaingOrder: setStatsPlaingOrder,
+ setSelectedStatsTable,
+ setStatsType,
setWatchAllEpisodesTimer: setStatsWatchAllEpisodesTimer,
statsType,
+ toggleIsExpanded,
toggleStatsType,
watchAllEpisodesTimer: statsWatchAllEpisodesTimer,
} = useStatsTab({
@@ -214,7 +223,12 @@ export const useMatchPage = () => {
selectedPlaylist,
})
- const { teamsStats } = useTeamsStats({
+ const {
+ beforeCloseTourCallback: beforeCloseTourCallbackTeams,
+ getFirstClickableParam,
+ isClickable,
+ teamsStats,
+ } = useTeamsStats({
matchProfile,
playingProgress,
selectedPlaylist,
@@ -223,6 +237,8 @@ export const useMatchPage = () => {
})
const {
+ beforeCloseTourCallback: beforeCloseTourCallbackPlayers,
+ getParams,
isEmptyPlayersStats,
playersData,
playersStats,
@@ -234,6 +250,15 @@ export const useMatchPage = () => {
statsType,
})
+ const beforeCloseTourCallback = () => {
+ beforeCloseTourCallbackPlayers()
+ beforeCloseTourCallbackTeams()
+
+ setStatsType(profile?.live ? StatsType.CURRENT_STATS : StatsType.FINAL_STATS)
+ isExpanded && toggleIsExpanded()
+ setLocalStorageItem(TOUR_COMPLETED_STORAGE_KEY, 'true')
+ }
+
const isStarted = useMemo(() => (
profile?.date
? parseDate(profile.date) < new Date()
@@ -310,17 +335,22 @@ export const useMatchPage = () => {
allActionsToggle,
allPlayersToggle,
applyFilters,
+ beforeCloseTourCallback,
circleAnimation: isStatsTab ? statsCircleAnimation : circleAnimation,
closePopup,
countOfFilters,
disablePlayingEpisodes,
events,
filteredEvents: isStatsTab ? statsFilteredEvents : filteredEvents,
+ getFirstClickableParam,
+ getParams,
handlePlaylistClick,
hideProfileCard,
isAllActionsChecked,
+ isClickable,
isEmptyFilters,
isEmptyPlayersStats,
+ isExpanded,
isFirstTeamPlayersChecked,
isLiveMatch,
isOpenFiltersPopup,
@@ -342,8 +372,10 @@ export const useMatchPage = () => {
playingProgress,
profile,
profileCardShown,
+ reduceTable,
reversedGroupEvents,
selectedPlaylist,
+ selectedStatsTable,
selectedTab,
setCircleAnimation: isStatsTab ? setStatsCircleAnimation : setCircleAnimation,
setFullMatchPlaylistDuration,
@@ -354,7 +386,9 @@ export const useMatchPage = () => {
setPlayingData,
setPlayingProgress,
setReversed,
+ setSelectedStatsTable,
setSelectedTab,
+ setStatsType,
setUnreversed,
setWatchAllEpisodesTimer: isStatsTab ? setStatsWatchAllEpisodesTimer : setWatchAllEpisodesTimer,
showProfileCard,
@@ -362,6 +396,7 @@ export const useMatchPage = () => {
teamsStats,
toggleActiveEvents,
toggleActivePlayers,
+ toggleIsExpanded,
togglePopup,
toggleStatsType,
tournamentData,
diff --git a/src/features/MatchPage/store/hooks/useFakeData.tsx b/src/features/MatchPage/store/hooks/useFakeData.tsx
new file mode 100644
index 00000000..ac5c477e
--- /dev/null
+++ b/src/features/MatchPage/store/hooks/useFakeData.tsx
@@ -0,0 +1,3513 @@
+/* eslint-disable sort-keys */
+import { useMemo } from 'react'
+
+import type { MatchInfo } from 'requests'
+
+export const useFakeData = (matchProfile: MatchInfo) => useMemo(() => {
+ if (!matchProfile?.team1.id) {
+ return {
+ playersData: [
+ {
+ players: [],
+ },
+ {
+ players: [],
+ },
+ ],
+ playersStats: {},
+ teamsStats: {},
+ }
+ }
+
+ return {
+ playersData: [
+ {
+ team_id: matchProfile.team1.id,
+ players: [
+ {
+ id: 121186,
+ firstname_eng: 'Gianfranco',
+ firstname_rus: 'Джанфранко',
+ lastname_eng: 'Gazzaniga Farias',
+ lastname_rus: 'Газзанига Фариас',
+ nickname_eng: 'Gianfranco Gazzaniga',
+ nickname_rus: 'Gianfranco Gazzaniga',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 186,
+ weight: 85,
+ is_gk: true,
+ c_country: 12,
+ birthday: '1993-11-22',
+ firstname_national: 'Gianfranco',
+ lastname_national: 'Gazzaniga Farías',
+ club_shirt_num: 13,
+ national_shirt_num: null,
+ num: 13,
+ ord: 1,
+ },
+ {
+ id: 1206209,
+ firstname_eng: 'Brais',
+ firstname_rus: 'Брайс',
+ lastname_eng: 'Martinez Prado',
+ lastname_rus: 'Мартинез Прадо',
+ nickname_eng: 'Brais Martinez',
+ nickname_rus: 'Брейс Мартинез',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 175,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2001-12-13',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 18,
+ national_shirt_num: null,
+ num: 18,
+ ord: 2,
+ },
+ {
+ id: 202317,
+ firstname_eng: 'Quique',
+ firstname_rus: 'Энрике',
+ lastname_eng: 'Fornos Dominguez',
+ lastname_rus: 'Форнос Домингуез',
+ nickname_eng: 'Quique Fornos',
+ nickname_rus: 'Энрике Форнос',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 179,
+ weight: 69,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1997-01-01',
+ firstname_national: 'Enrique',
+ lastname_national: 'Fornos Domínguez',
+ club_shirt_num: 5,
+ national_shirt_num: 15,
+ num: 5,
+ ord: 3,
+ },
+ {
+ id: 571713,
+ firstname_eng: 'Tomas Enrique',
+ firstname_rus: 'Томас Энрике',
+ lastname_eng: 'Bourdel',
+ lastname_rus: 'Бурдель',
+ nickname_eng: 'Tomas Bourdal',
+ nickname_rus: 'Tomas Bourdal',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 192,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1998-04-27',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 2,
+ national_shirt_num: null,
+ num: 2,
+ ord: 4,
+ },
+ {
+ id: 287877,
+ firstname_eng: 'Aitor',
+ firstname_rus: 'Айтор',
+ lastname_eng: 'Pascual Sejias',
+ lastname_rus: 'Паскуаль Съежиась',
+ nickname_eng: 'Aitor Pascual',
+ nickname_rus: 'Aitor Pascual',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 179,
+ weight: 74,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1998-10-10',
+ firstname_national: 'Aitor',
+ lastname_national: 'Pascual',
+ club_shirt_num: 3,
+ national_shirt_num: null,
+ num: 3,
+ ord: 5,
+ },
+ {
+ id: 27677,
+ firstname_eng: 'Alejandro',
+ firstname_rus: 'Алекс Санчес',
+ lastname_eng: 'Lopez Sanchez',
+ lastname_rus: 'Лопез Санчез',
+ nickname_eng: 'Alex Lopez',
+ nickname_rus: 'Алекс Лопез',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 178,
+ weight: 71,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1988-01-11',
+ firstname_national: 'Alejandro',
+ lastname_national: 'López Sánchez',
+ club_shirt_num: 8,
+ national_shirt_num: null,
+ num: 8,
+ ord: 6,
+ },
+ {
+ id: 470300,
+ firstname_eng: 'Jesus Jose',
+ firstname_rus: 'Хесус Жозе',
+ lastname_eng: 'Bernal Villarig',
+ lastname_rus: 'Берналь Виллариг',
+ nickname_eng: 'Jesus Bernal',
+ nickname_rus: 'Jesus Bernal',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 183,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1996-12-25',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 6,
+ national_shirt_num: null,
+ num: 6,
+ ord: 7,
+ },
+ {
+ id: 259653,
+ firstname_eng: 'Francisco',
+ firstname_rus: 'Франсиско',
+ lastname_eng: 'Lopez de la Manzanara Delgado',
+ lastname_rus: 'Лопез Де Ла Манзанара Дельгадо',
+ nickname_eng: 'Fran Manzanara',
+ nickname_rus: 'Fran Manzanara',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 183,
+ weight: 75,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1996-09-12',
+ firstname_national: 'Francisco',
+ lastname_national: 'López de la Manzanara',
+ club_shirt_num: 16,
+ national_shirt_num: null,
+ num: 16,
+ ord: 8,
+ },
+ {
+ id: 498854,
+ firstname_eng: 'Jorge',
+ firstname_rus: 'Жорже',
+ lastname_eng: 'Padilla Soler',
+ lastname_rus: 'Падилья',
+ nickname_eng: null,
+ nickname_rus: null,
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 175,
+ weight: 66,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2001-04-23',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 19,
+ national_shirt_num: null,
+ num: 19,
+ ord: 9,
+ },
+ {
+ id: 254024,
+ firstname_eng: 'Heber',
+ firstname_rus: 'Хебер',
+ lastname_eng: 'Pena Picos',
+ lastname_rus: 'Пена Пикус',
+ nickname_eng: 'Heber Pena',
+ nickname_rus: 'Heber Pena',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 176,
+ weight: 68,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1990-01-16',
+ firstname_national: 'Héber',
+ lastname_national: 'Pena Picos',
+ club_shirt_num: 7,
+ national_shirt_num: null,
+ num: 7,
+ ord: 10,
+ },
+ {
+ id: 463488,
+ firstname_eng: 'Luis',
+ firstname_rus: 'Луис',
+ lastname_eng: 'Rodriguez Chacon',
+ lastname_rus: 'Родригес Чакон',
+ nickname_eng: 'Luis Chacon',
+ nickname_rus: 'Luis Chacon',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: null,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2000-05-30',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 11,
+ national_shirt_num: null,
+ num: 11,
+ ord: 11,
+ },
+ {
+ id: 303350,
+ firstname_eng: 'David',
+ firstname_rus: 'Давид',
+ lastname_eng: 'Del Pozo Guillen',
+ lastname_rus: 'Дел Посо Гильен',
+ nickname_eng: 'David Del Pozo',
+ nickname_rus: 'David Del Pozo',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 178,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1997-06-05',
+ firstname_national: 'David',
+ lastname_national: 'Del Pozo Guillén',
+ club_shirt_num: 14,
+ national_shirt_num: null,
+ num: 14,
+ ord: 12,
+ },
+ {
+ id: 923500,
+ firstname_eng: 'Carlos',
+ firstname_rus: 'Карлос',
+ lastname_eng: 'Vicente Robles',
+ lastname_rus: 'Висенте Роблес',
+ nickname_eng: 'Carlos Vicente',
+ nickname_rus: 'Carlos Vicente',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 179,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1999-04-23',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 23,
+ national_shirt_num: null,
+ num: 23,
+ ord: 13,
+ },
+ {
+ id: 1081850,
+ firstname_eng: 'Jaume',
+ firstname_rus: 'Жауме',
+ lastname_eng: 'Jardi Poyato',
+ lastname_rus: 'Джарди Поято',
+ nickname_eng: 'Jaume Jardi',
+ nickname_rus: 'Jaume Jardi',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 175,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2002-04-07',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 21,
+ national_shirt_num: null,
+ num: 21,
+ ord: 14,
+ },
+ {
+ id: 44982,
+ firstname_eng: 'Jose Luis',
+ firstname_rus: 'Жозе Луис',
+ lastname_eng: 'Gomez Perez',
+ lastname_rus: 'Гомес Перес',
+ nickname_eng: 'Joselu',
+ nickname_rus: 'Хоселу',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 182,
+ weight: 78,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1987-06-10',
+ firstname_national: 'José Luis',
+ lastname_national: 'Gómez Pérez',
+ club_shirt_num: 22,
+ national_shirt_num: null,
+ num: 22,
+ ord: 15,
+ },
+ {
+ id: 469074,
+ firstname_eng: 'Manuel',
+ firstname_rus: 'Мануэль',
+ lastname_eng: 'Justo Roman',
+ lastname_rus: 'Юсто Роман',
+ nickname_eng: 'Manu Justo',
+ nickname_rus: 'Manu Justo',
+ club_f_team: 6330,
+ national_f_team: null,
+ c_gender: 1,
+ height: 174,
+ weight: 77,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1996-02-09',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 9,
+ national_shirt_num: null,
+ num: 9,
+ ord: 16,
+ },
+ ],
+ },
+ {
+ team_id: matchProfile.team2.id,
+ players: [
+ {
+ id: 954413,
+ firstname_eng: 'Ernestas',
+ firstname_rus: 'Эрнестас',
+ lastname_eng: 'Juskevicius',
+ lastname_rus: 'Юшкявичюс',
+ nickname_eng: 'Ernestas Juskevicius',
+ nickname_rus: 'Эрнестас Юшкявичюс',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 188,
+ weight: null,
+ is_gk: true,
+ c_country: 77,
+ birthday: '1999-04-16',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 1,
+ national_shirt_num: null,
+ num: 1,
+ ord: 1,
+ },
+ {
+ id: 1206758,
+ firstname_eng: 'Ivan',
+ firstname_rus: 'Иван',
+ lastname_eng: 'Serrano Garcia',
+ lastname_rus: 'Серрано Гарсиа',
+ nickname_eng: 'Ivan Serrano',
+ nickname_rus: 'Иван Серрано',
+ club_f_team: 7240,
+ national_f_team: null,
+ c_gender: 1,
+ height: null,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2001-01-07',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 11,
+ national_shirt_num: null,
+ num: 3,
+ ord: 2,
+ },
+ {
+ id: 484081,
+ firstname_eng: 'Eduardo Viana',
+ firstname_rus: 'Эдуардо Виана',
+ lastname_eng: 'Viana Campuzano',
+ lastname_rus: 'Виана Кампусано',
+ nickname_eng: 'Edu Viana',
+ nickname_rus: 'Edu Viana',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 174,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2000-02-24',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 3,
+ national_shirt_num: null,
+ num: 20,
+ ord: 3,
+ },
+ {
+ id: 161842,
+ firstname_eng: 'Emmanuel',
+ firstname_rus: 'Эммануэль',
+ lastname_eng: 'Gonzalez Rodriguez',
+ lastname_rus: 'Гонзалез Родригес',
+ nickname_eng: 'Lolo Gonzalez',
+ nickname_rus: 'Лоло Гонзалез',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 186,
+ weight: 83,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1991-07-22',
+ firstname_national: 'Emmanuel',
+ lastname_national: 'González Rodríguez',
+ club_shirt_num: 5,
+ national_shirt_num: null,
+ num: 5,
+ ord: 4,
+ },
+ {
+ id: 121819,
+ firstname_eng: 'Jose Antonio',
+ firstname_rus: 'Жозе Антонио',
+ lastname_eng: 'Caro Martinez',
+ lastname_rus: 'Каро Мартинез',
+ nickname_eng: 'Jose Caro',
+ nickname_rus: 'Жозе Каро',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 183,
+ weight: 70,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1993-03-08',
+ firstname_national: 'José Antonio',
+ lastname_national: 'Caro Martínez',
+ club_shirt_num: 16,
+ national_shirt_num: null,
+ num: 16,
+ ord: 5,
+ },
+ {
+ id: 911431,
+ firstname_eng: 'Javier',
+ firstname_rus: 'Хавьер',
+ lastname_eng: 'Duarte Egea',
+ lastname_rus: 'Дуарте Егеа',
+ nickname_eng: 'Javi Duarte',
+ nickname_rus: 'Хави Дуарте',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 175,
+ weight: 64,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1997-12-22',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 15,
+ national_shirt_num: null,
+ num: 15,
+ ord: 6,
+ },
+ {
+ id: 1095160,
+ firstname_eng: 'Aitor',
+ firstname_rus: 'Айтор',
+ lastname_eng: 'Gelardo Vegara',
+ lastname_rus: 'Гелардо Вегара',
+ nickname_eng: null,
+ nickname_rus: null,
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 180,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2002-06-26',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 19,
+ national_shirt_num: 6,
+ num: 19,
+ ord: 7,
+ },
+ {
+ id: 322813,
+ firstname_eng: 'Alberto',
+ firstname_rus: 'Алберто',
+ lastname_eng: 'Rodriguez Exposito',
+ lastname_rus: 'Родригес Экспосито',
+ nickname_eng: 'Rodri',
+ nickname_rus: 'Rodri',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: null,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1989-02-28',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 8,
+ national_shirt_num: null,
+ num: 8,
+ ord: 8,
+ },
+ {
+ id: 473579,
+ firstname_eng: 'Francisco',
+ firstname_rus: 'Франсиско',
+ lastname_eng: 'Callejon Segura',
+ lastname_rus: 'Кальехон Сегура',
+ nickname_eng: 'Fran Callejon',
+ nickname_rus: 'Fran Callejon',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 182,
+ weight: 68,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1998-05-15',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 21,
+ national_shirt_num: null,
+ num: 21,
+ ord: 9,
+ },
+ {
+ id: 1410436,
+ firstname_eng: 'Fermin',
+ firstname_rus: 'Фермин',
+ lastname_eng: 'Lopez Martin',
+ lastname_rus: 'Лопез Марин',
+ nickname_eng: 'Fermin',
+ nickname_rus: 'Фермин',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: null,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2003-05-11',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 10,
+ national_shirt_num: null,
+ num: 10,
+ ord: 10,
+ },
+ {
+ id: 473112,
+ firstname_eng: 'Francisco',
+ firstname_rus: 'Франсиско',
+ lastname_eng: 'Garcia Murillo',
+ lastname_rus: 'Гарсиа Мурильо',
+ nickname_eng: null,
+ nickname_rus: null,
+ club_f_team: 35953,
+ national_f_team: null,
+ c_gender: 1,
+ height: 173,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1993-03-19',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 9,
+ national_shirt_num: null,
+ num: 9,
+ ord: 11,
+ },
+ {
+ id: 423681,
+ firstname_eng: 'Samuel',
+ firstname_rus: 'Самуэль',
+ lastname_eng: 'Corral Valero',
+ lastname_rus: 'Коррал Валеро',
+ nickname_eng: 'Samu Corral',
+ nickname_rus: 'Samu Corral',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 184,
+ weight: 81,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1992-04-03',
+ firstname_national: 'Samuel',
+ lastname_national: 'Corral Valero',
+ club_shirt_num: 14,
+ national_shirt_num: null,
+ num: 14,
+ ord: 12,
+ },
+ {
+ id: 1269294,
+ firstname_eng: 'Antonio Jesus',
+ firstname_rus: 'Антонио Хесус',
+ lastname_eng: 'Canete Vidal',
+ lastname_rus: 'Каньете Видаль',
+ nickname_eng: 'Antonio Canete',
+ nickname_rus: 'Антонио Каньете',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 197,
+ weight: null,
+ is_gk: false,
+ c_country: 77,
+ birthday: '2002-03-05',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 4,
+ national_shirt_num: null,
+ num: 4,
+ ord: 13,
+ },
+ {
+ id: 918048,
+ firstname_eng: 'Alejandro San',
+ firstname_rus: 'Александр Сан -',
+ lastname_eng: 'Cristobal Sanchez',
+ lastname_rus: 'Кристобаль Санчез',
+ nickname_eng: 'Alex Sancris',
+ nickname_rus: 'Alex Sancris',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 177,
+ weight: 72,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1997-01-18',
+ firstname_national: null,
+ lastname_national: null,
+ club_shirt_num: 22,
+ national_shirt_num: null,
+ num: 22,
+ ord: 14,
+ },
+ {
+ id: 406433,
+ firstname_eng: 'Alfonso Jesus',
+ firstname_rus: 'Альфонсо Хесус',
+ lastname_eng: 'Fernandez',
+ lastname_rus: 'Фернандез',
+ nickname_eng: 'Alfonso Fernandez',
+ nickname_rus: 'Alfonso Fernandez',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 179,
+ weight: 66,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1994-11-25',
+ firstname_national: 'Alfonso',
+ lastname_national: 'Fernández',
+ club_shirt_num: 11,
+ national_shirt_num: null,
+ num: 11,
+ ord: 15,
+ },
+ {
+ id: 121632,
+ firstname_eng: 'Antonio Luis',
+ firstname_rus: 'Антонио Луис',
+ lastname_eng: 'Diaz Sanchez',
+ lastname_rus: 'Диаз Санчез',
+ nickname_eng: 'Hugo Diaz',
+ nickname_rus: 'Хьюго Диаз',
+ club_f_team: 9114,
+ national_f_team: null,
+ c_gender: 1,
+ height: 174,
+ weight: 68,
+ is_gk: false,
+ c_country: 77,
+ birthday: '1988-02-09',
+ firstname_national: 'Antonio Luis',
+ lastname_national: 'Díaz Sánchez',
+ club_shirt_num: 7,
+ national_shirt_num: 7,
+ num: 7,
+ ord: 16,
+ },
+ ],
+ },
+ ],
+ playersStats: {
+ 27677: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 7,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 1,
+ markers: [
+ 30264092,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: null,
+ markers: null,
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: 1,
+ markers: [
+ 30264092,
+ ],
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 44982: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 71,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: 1,
+ markers: [
+ 30262274,
+ ],
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 2,
+ markers: [
+ 30262274,
+ 30262309,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: 1,
+ markers: [
+ 30262274,
+ ],
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: null,
+ markers: null,
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: 1,
+ markers: [
+ 30263391,
+ ],
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: 1,
+ markers: [
+ 30262309,
+ ],
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: 1,
+ markers: [
+ 30262274,
+ ],
+ },
+ },
+ 121186: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 94,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: null,
+ markers: null,
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 202317: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 94,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: 1,
+ markers: [
+ 30263621,
+ ],
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 2,
+ markers: [
+ 30263621,
+ 30263758,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: 1,
+ markers: [
+ 30263621,
+ ],
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 3,
+ markers: [
+ 30262523,
+ 30262835,
+ 30264010,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 2,
+ markers: [
+ 30262186,
+ 30262708,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: 1,
+ markers: [
+ 30263758,
+ ],
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: 1,
+ markers: [
+ 30263621,
+ ],
+ },
+ },
+ 254024: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 56,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 1,
+ markers: [
+ 30263304,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 3,
+ markers: [
+ 30262104,
+ 30262400,
+ 30262692,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: 1,
+ markers: [
+ 30262169,
+ ],
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 259653: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 87,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 1,
+ markers: [
+ 30262272,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: 1,
+ markers: [
+ 30262272,
+ ],
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 3,
+ markers: [
+ 30263446,
+ 30263578,
+ 30263953,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 3,
+ markers: [
+ 30262494,
+ 30262907,
+ 30263396,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: 1,
+ markers: [
+ 30263579,
+ ],
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 287877: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 94,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 1,
+ markers: [
+ 30264052,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 2,
+ markers: [
+ 30263382,
+ 30263541,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 303350: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 23,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: null,
+ markers: null,
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 463488: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 71,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 4,
+ markers: [
+ 30262551,
+ 30262855,
+ 30263387,
+ 30263413,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: 2,
+ markers: [
+ 30262551,
+ 30263387,
+ ],
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 1,
+ markers: [
+ 30263410,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: 1,
+ markers: [
+ 30263413,
+ ],
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: 1,
+ markers: [
+ 30262855,
+ ],
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 469074: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 23,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: null,
+ markers: null,
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 470300: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 94,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 2,
+ markers: [
+ 30262139,
+ 30262481,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 1,
+ markers: [
+ 30262116,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: 1,
+ markers: [
+ 30262482,
+ ],
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 498854: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 23,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 1,
+ markers: [
+ 30263877,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 3,
+ markers: [
+ 30263652,
+ 30263713,
+ 30263841,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 571713: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 94,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: 1,
+ markers: [
+ 30263613,
+ ],
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 1,
+ markers: [
+ 30263656,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: null,
+ markers: null,
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 1,
+ markers: [
+ 30262648,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: 1,
+ markers: [
+ 30263656,
+ ],
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 923500: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 71,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 1,
+ markers: [
+ 30262232,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 1,
+ markers: [
+ 30262953,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 1,
+ markers: [
+ 30262746,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: 1,
+ markers: [
+ 30262232,
+ ],
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 1081850: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 39,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: null,
+ markers: null,
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 1,
+ markers: [
+ 30263523,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 2,
+ markers: [
+ 30263750,
+ 30263985,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: null,
+ markers: null,
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ 1206209: {
+ 1: {
+ id: 1,
+ name_en: 'Minutes played',
+ name_ru: null,
+ lexic: 3172,
+ clickable: false,
+ data_type: 'num',
+ lexica_short: 3093,
+ val: 94,
+ markers: null,
+ },
+ 2: {
+ id: 2,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 3173,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3094,
+ val: null,
+ markers: null,
+ },
+ 3: {
+ id: 3,
+ name_en: 'Assists',
+ name_ru: null,
+ lexic: 3174,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3095,
+ val: null,
+ markers: null,
+ },
+ 4: {
+ id: 4,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3102,
+ val: 1,
+ markers: [
+ 30263608,
+ ],
+ },
+ 5: {
+ id: 5,
+ name_en: 'shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3103,
+ val: null,
+ markers: null,
+ },
+ 6: {
+ id: 6,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 413,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3136,
+ val: 3,
+ markers: [
+ 30261973,
+ 30262023,
+ 30262763,
+ ],
+ },
+ 7: {
+ id: 7,
+ name_en: 'Fouls suffered',
+ name_ru: null,
+ lexic: 4861,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3137,
+ val: 2,
+ markers: [
+ 30262041,
+ 30263335,
+ ],
+ },
+ 8: {
+ id: 8,
+ name_en: 'Yellow cards - К (605)',
+ name_ru: null,
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: null,
+ markers: null,
+ },
+ 10: {
+ id: 10,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 7258,
+ val: null,
+ markers: null,
+ },
+ 44: {
+ id: 44,
+ name_en: 'Shots wide',
+ name_ru: 'Удар мимо',
+ lexic: 535,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3104,
+ val: null,
+ markers: null,
+ },
+ 45: {
+ id: 45,
+ name_en: 'Shots blocked',
+ name_ru: 'Удары перехваченные',
+ lexic: 1048,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6295,
+ val: 1,
+ markers: [
+ 30263608,
+ ],
+ },
+ 51: {
+ id: 51,
+ name_en: 'Chanses',
+ name_ru: 'ГМ',
+ lexic: 3925,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 6291,
+ val: null,
+ markers: null,
+ },
+ },
+ match_calc_status: 2,
+ },
+ teamsStats: {
+ [matchProfile.team1.id]: [
+ {
+ name_en: 'Goals',
+ name_ru: 'Голы',
+ lexic: 3173,
+ order: 1,
+ param1: {
+ id: 1,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 6142,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 2,
+ markers: [
+ 30262274,
+ 30263621,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Chances',
+ name_ru: 'Опасные моменты',
+ lexic: 3925,
+ order: 2,
+ param1: {
+ id: 2,
+ name_en: 'Chances',
+ name_ru: null,
+ lexic: 11841,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 7,
+ markers: [
+ 30262234,
+ 30262278,
+ 30262308,
+ 30262680,
+ 30263620,
+ 30263757,
+ 30264006,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Chances, % of conversion',
+ name_ru: 'Команда-Реализация моментов',
+ lexic: 4135,
+ order: 3,
+ param1: {
+ id: 4,
+ name_en: 'Chances, % of conversion',
+ name_ru: null,
+ lexic: 4135,
+ clickable: false,
+ data_type: 'pct',
+ lexica_short: null,
+ val: 29,
+ markers: null,
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Shots',
+ name_ru: 'Удары',
+ lexic: 3178,
+ order: 4,
+ param1: {
+ id: 5,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 13,
+ markers: [
+ 30262232,
+ 30262272,
+ 30262274,
+ 30262309,
+ 30262551,
+ 30262855,
+ 30263387,
+ 30263413,
+ 30263608,
+ 30263621,
+ 30263656,
+ 30263758,
+ 30264092,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Shots on target',
+ name_ru: 'Удары в створ',
+ lexic: 2131,
+ order: 5,
+ param1: {
+ id: 6,
+ name_en: 'Shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 5,
+ markers: [
+ 30262272,
+ 30262274,
+ 30262551,
+ 30263387,
+ 30263621,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Shots on target, %',
+ name_ru: 'Точные удары, %',
+ lexic: 3179,
+ order: 6,
+ param1: {
+ id: 7,
+ name_en: 'Shots on target, %',
+ name_ru: null,
+ lexic: 3179,
+ clickable: false,
+ data_type: 'pct',
+ lexica_short: null,
+ val: 38,
+ markers: null,
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Corners',
+ name_ru: 'Угловые',
+ lexic: 64,
+ order: 7,
+ param1: {
+ id: 8,
+ name_en: 'Corners',
+ name_ru: null,
+ lexic: 64,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 4,
+ markers: [
+ 35604927,
+ 35604928,
+ 35604931,
+ 35604933,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Offsides',
+ name_ru: 'Офсайды',
+ lexic: 63,
+ order: 8,
+ param1: {
+ id: 9,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 2,
+ markers: [
+ 30262169,
+ 30263391,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Fouls',
+ name_ru: 'Фолы',
+ lexic: 413,
+ order: 9,
+ param1: {
+ id: 10,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 701,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 16,
+ markers: [
+ 30261973,
+ 30262023,
+ 30262139,
+ 30262481,
+ 30262523,
+ 30262763,
+ 30262835,
+ 30262953,
+ 30263304,
+ 30263446,
+ 30263523,
+ 30263578,
+ 30263877,
+ 30263953,
+ 30264010,
+ 30264052,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Yellow cards',
+ name_ru: 'ЖК',
+ lexic: 605,
+ order: 10,
+ param1: {
+ id: 36,
+ name_en: 'Yellow cards',
+ name_ru: 'ЖК',
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: 2,
+ markers: [
+ 30262482,
+ 30263579,
+ ],
+ },
+ param2: null,
+ },
+ ],
+ [matchProfile.team2.id]: [
+ {
+ name_en: 'Goals',
+ name_ru: 'Голы',
+ lexic: 3173,
+ order: 1,
+ param1: {
+ id: 1,
+ name_en: 'Goals',
+ name_ru: null,
+ lexic: 6142,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 0,
+ markers: [],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Chances',
+ name_ru: 'Опасные моменты',
+ lexic: 3925,
+ order: 2,
+ param1: {
+ id: 2,
+ name_en: 'Chances',
+ name_ru: null,
+ lexic: 11841,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 2,
+ markers: [
+ 30261991,
+ 30263264,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Chances, % of conversion',
+ name_ru: 'Команда-Реализация моментов',
+ lexic: 4135,
+ order: 3,
+ param1: {
+ id: 4,
+ name_en: 'Chances, % of conversion',
+ name_ru: null,
+ lexic: 4135,
+ clickable: false,
+ data_type: 'pct',
+ lexica_short: null,
+ val: 0,
+ markers: null,
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Shots',
+ name_ru: 'Удары',
+ lexic: 3178,
+ order: 4,
+ param1: {
+ id: 5,
+ name_en: 'Shots',
+ name_ru: null,
+ lexic: 56,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 5,
+ markers: [
+ 30261988,
+ 30262433,
+ 30263588,
+ 30263954,
+ 30264122,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Shots on target',
+ name_ru: 'Удары в створ',
+ lexic: 2131,
+ order: 5,
+ param1: {
+ id: 6,
+ name_en: 'Shots on target',
+ name_ru: null,
+ lexic: 2131,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 1,
+ markers: [
+ 30264122,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Shots on target, %',
+ name_ru: 'Точные удары, %',
+ lexic: 3179,
+ order: 6,
+ param1: {
+ id: 7,
+ name_en: 'Shots on target, %',
+ name_ru: null,
+ lexic: 3179,
+ clickable: false,
+ data_type: 'pct',
+ lexica_short: null,
+ val: 20,
+ markers: null,
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Corners',
+ name_ru: 'Угловые',
+ lexic: 64,
+ order: 7,
+ param1: {
+ id: 8,
+ name_en: 'Corners',
+ name_ru: null,
+ lexic: 64,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 5,
+ markers: [
+ 35604929,
+ 35604930,
+ 35604932,
+ 35604934,
+ 35604935,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Offsides',
+ name_ru: 'Офсайды',
+ lexic: 63,
+ order: 8,
+ param1: {
+ id: 9,
+ name_en: 'Offsides',
+ name_ru: null,
+ lexic: 63,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 3,
+ markers: [
+ 30262254,
+ 30262350,
+ 30263819,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Fouls',
+ name_ru: 'Фолы',
+ lexic: 413,
+ order: 9,
+ param1: {
+ id: 10,
+ name_en: 'Fouls',
+ name_ru: null,
+ lexic: 701,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: null,
+ val: 22,
+ markers: [
+ 30262041,
+ 30262104,
+ 30262116,
+ 30262186,
+ 30262400,
+ 30262494,
+ 30262648,
+ 30262692,
+ 30262708,
+ 30262746,
+ 30262907,
+ 30263335,
+ 30263382,
+ 30263396,
+ 30263410,
+ 30263541,
+ 30263652,
+ 30263713,
+ 30263750,
+ 30263841,
+ 30263985,
+ 30264083,
+ ],
+ },
+ param2: null,
+ },
+ {
+ name_en: 'Yellow cards',
+ name_ru: 'ЖК',
+ lexic: 605,
+ order: 10,
+ param1: {
+ id: 36,
+ name_en: 'Yellow cards',
+ name_ru: 'ЖК',
+ lexic: 605,
+ clickable: true,
+ data_type: 'num',
+ lexica_short: 3096,
+ val: 1,
+ markers: [
+ 30263986,
+ ],
+ },
+ param2: null,
+ },
+ ],
+ match_calc_status: 2,
+ last_event_id: 30264158,
+ },
+ }
+}, [matchProfile?.team1.id, matchProfile?.team2.id])
diff --git a/src/features/MatchPage/store/hooks/usePlayersStats.tsx b/src/features/MatchPage/store/hooks/usePlayersStats.tsx
index 4249f023..f8505e5b 100644
--- a/src/features/MatchPage/store/hooks/usePlayersStats.tsx
+++ b/src/features/MatchPage/store/hooks/usePlayersStats.tsx
@@ -3,6 +3,7 @@ import {
useMemo,
useEffect,
useState,
+ useCallback,
} from 'react'
import { useQueryClient } from 'react-query'
@@ -11,10 +12,14 @@ import isEmpty from 'lodash/isEmpty'
import every from 'lodash/every'
import find from 'lodash/find'
import isUndefined from 'lodash/isUndefined'
+import flatMapDepth from 'lodash/flatMapDepth'
+import uniqBy from 'lodash/uniqBy'
+import values from 'lodash/values'
+import size from 'lodash/size'
import { querieKeys } from 'config'
-import type { MatchScore } from 'requests'
+import type { MatchScore, PlayerParam } from 'requests'
import {
MatchInfo,
PlayersStats,
@@ -23,12 +28,20 @@ import {
getMatchParticipants,
} from 'requests'
+import { getLocalStorageItem } from 'helpers/getLocalStorage'
+
import { useObjectState, usePageParams } from 'hooks'
import type{ PlaylistOption } from 'features/MatchPage/types'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
+import { TOUR_COMPLETED_STORAGE_KEY } from 'features/MatchTour'
+
+import { DISPLAYED_PARAMS_COLUMNS } from 'features/MatchSidePlaylists/components/PlayersTable/config'
+import { useFakeData } from './useFakeData'
+
+type HeaderParam = Pick
const REQUEST_DELAY = 3000
const STATS_POLL_INTERVAL = 30000
@@ -62,6 +75,8 @@ export const usePlayersStats = ({
sportType,
} = usePageParams()
+ const fakeData = useFakeData(matchProfile)
+
const client = useQueryClient()
const matchScore = client.getQueryData(querieKeys.matchScore)
@@ -74,6 +89,10 @@ export const usePlayersStats = ({
|| isEmpty(playersData[matchProfile?.team1.id === teamId ? 'team1' : 'team2'])
)
+ const getParams = useCallback((stats: PlayersStats) => (
+ uniqBy(flatMapDepth(stats, values), 'id') as unknown as Record
+ ), [])
+
const fetchPlayers = useMemo(() => throttle(async (second?: number) => {
const videoBounds = matchScore?.video_bounds || matchProfile?.video_bounds
@@ -116,12 +135,11 @@ export const usePlayersStats = ({
} catch (e) {
return Promise.reject(e)
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}), [
- matchId,
+ matchScore?.video_bounds,
+ matchProfile,
sportName,
- matchProfile?.team1.id,
- matchProfile?.team2.id,
+ matchId,
])
const fetchData = useMemo(() => throttle(async (second?: number) => {
@@ -139,14 +157,29 @@ export const usePlayersStats = ({
const team1Players = find(res1, { team_id: matchProfile?.team1.id })?.players || []
const team2Players = find(res1, { team_id: matchProfile?.team2.id })?.players || []
+ const needUseFakeData = getLocalStorageItem(TOUR_COMPLETED_STORAGE_KEY) !== 'true'
+ && (
+ isEmpty(team1Players)
+ || isEmpty(res2)
+ || size(getParams(res2)) <= DISPLAYED_PARAMS_COLUMNS
+ )
+
setPlayersData({
- team1: team1Players,
- team2: team2Players,
+ team1: needUseFakeData ? fakeData.playersData[0].players : team1Players,
+ team2: needUseFakeData ? fakeData.playersData[1].players : team2Players,
})
setPlayersStats({
- ...(matchProfile?.team1.id && res2 && { [matchProfile.team1.id]: res2 }),
- ...(matchProfile?.team2.id && res3 && { [matchProfile.team2.id]: res3 }),
+ ...(matchProfile?.team1.id && res2 && {
+ [matchProfile.team1.id]: needUseFakeData
+ ? fakeData.playersStats as unknown as PlayersStats
+ : res2,
+ }),
+ ...(matchProfile?.team2.id && res3 && {
+ [matchProfile.team2.id]: needUseFakeData
+ ? fakeData.playersStats as unknown as PlayersStats
+ : res3,
+ }),
})
setIsPlayersStatsFetching(false)
@@ -160,8 +193,14 @@ export const usePlayersStats = ({
matchProfile?.live,
matchProfile?.c_match_calc_status,
setIsPlayersStatsFetching,
+ getParams,
+ fakeData,
])
+ const beforeCloseTourCallback = () => {
+ isCurrentStats ? fetchData(playingProgress) : fetchData()
+ }
+
useEffect(() => {
let interval: NodeJS.Timeout
@@ -194,6 +233,8 @@ export const usePlayersStats = ({
])
return {
+ beforeCloseTourCallback,
+ getParams,
isEmptyPlayersStats,
playersData,
playersStats,
diff --git a/src/features/MatchPage/store/hooks/useStatsTab.tsx b/src/features/MatchPage/store/hooks/useStatsTab.tsx
index 333296d8..fa6762fc 100644
--- a/src/features/MatchPage/store/hooks/useStatsTab.tsx
+++ b/src/features/MatchPage/store/hooks/useStatsTab.tsx
@@ -14,7 +14,8 @@ import type { EventPlaylistOption, PlaylistOption } from 'features/MatchPage/typ
import type { TCircleAnimation } from 'features/CircleAnimationBar'
import { initialCircleAnimation } from 'features/CircleAnimationBar'
import { PlaylistTypes } from 'features/MatchPage/types'
-import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
+import { StatsType, Tabs } from 'features/MatchSidePlaylists/components/TabStats/config'
+import { useToggle } from 'hooks'
type UseStatsTabArgs = {
disablePlayingEpisodes: () => void,
@@ -55,6 +56,13 @@ export const useStatsTab = ({
const [isPlayFilterEpisodes, setIsPlayingFiltersEpisodes] = useState(false)
const [watchAllEpisodesTimer, setWatchAllEpisodesTimer] = useState(false)
const [circleAnimation, setCircleAnimation] = useState(initialCircleAnimation)
+ const [selectedStatsTable, setSelectedStatsTable] = useState(Tabs.TEAMS)
+
+ const {
+ close: reduceTable,
+ isOpen: isExpanded,
+ toggle: toggleIsExpanded,
+ } = useToggle()
const isFinalStatsType = statsType === StatsType.FINAL_STATS
@@ -125,19 +133,25 @@ export const useStatsTab = ({
return {
circleAnimation,
filteredEvents,
+ isExpanded,
isPlayFilterEpisodes,
isPlayersStatsFetching,
isTeamsStatsFetching,
plaingOrder,
playEpisodes,
playNextEpisode,
+ reduceTable,
+ selectedStatsTable,
setCircleAnimation,
setIsPlayersStatsFetching,
setIsPlayingFiltersEpisodes,
setIsTeamsStatsFetching,
setPlaingOrder,
+ setSelectedStatsTable,
+ setStatsType,
setWatchAllEpisodesTimer,
statsType,
+ toggleIsExpanded,
toggleStatsType,
watchAllEpisodesTimer,
}
diff --git a/src/features/MatchPage/store/hooks/useTeamsStats.tsx b/src/features/MatchPage/store/hooks/useTeamsStats.tsx
index f5f6c589..8017b99d 100644
--- a/src/features/MatchPage/store/hooks/useTeamsStats.tsx
+++ b/src/features/MatchPage/store/hooks/useTeamsStats.tsx
@@ -3,23 +3,35 @@ import {
useEffect,
useState,
useMemo,
+ useCallback,
} from 'react'
import { useQueryClient } from 'react-query'
import throttle from 'lodash/throttle'
import isUndefined from 'lodash/isUndefined'
+import find from 'lodash/find'
+import isEmpty from 'lodash/isEmpty'
import { querieKeys } from 'config'
-import type { MatchInfo, MatchScore } from 'requests'
+import type {
+ MatchInfo,
+ MatchScore,
+ Param,
+} from 'requests'
import { getTeamsStats, TeamStatItem } from 'requests'
import { usePageParams } from 'hooks'
+import { getLocalStorageItem } from 'helpers/getLocalStorage'
+
import type { PlaylistOption } from 'features/MatchPage/types'
import { StatsType } from 'features/MatchSidePlaylists/components/TabStats/config'
import { FULL_GAME_KEY } from 'features/MatchPage/helpers/buildPlaylists'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
+import { TOUR_COMPLETED_STORAGE_KEY } from 'features/MatchTour'
+
+import { useFakeData } from './useFakeData'
const REQUEST_DELAY = 3000
const STATS_POLL_INTERVAL = 30000
@@ -32,6 +44,10 @@ type UseTeamsStatsArgs = {
statsType: StatsType,
}
+type TeamsStats = {
+ [teamId: number]: Array,
+}
+
export const useTeamsStats = ({
matchProfile,
playingProgress,
@@ -39,18 +55,42 @@ export const useTeamsStats = ({
setIsTeamsStatsFetching,
statsType,
}: UseTeamsStatsArgs) => {
- const [teamsStats, setTeamsStats] = useState<{
- [teamId: string]: Array,
- }>({})
+ const [teamsStats, setTeamsStats] = useState({})
const { profileId: matchId, sportName } = usePageParams()
+ const fakeData = useFakeData(matchProfile)
+
const client = useQueryClient()
const matchScore = client.getQueryData(querieKeys.matchScore)
const isCurrentStats = statsType === StatsType.CURRENT_STATS
+ const isClickable = (param: Param) => (
+ Boolean(param.val) && param.clickable
+ )
+
+ const getFirstClickableParam = useCallback((stats: TeamsStats) => {
+ if (isEmpty(stats)) return null
+
+ const statItem = (matchProfile?.team1.id && find(
+ stats[matchProfile.team1.id],
+ ({ param1, param2 }) => isClickable(param1) || Boolean(param2 && isClickable(param1)),
+ )) || (matchProfile?.team2.id && find(
+ stats[matchProfile.team2.id],
+ ({ param1, param2 }) => isClickable(param1) || Boolean(param2 && isClickable(param1)),
+ ))
+
+ if (!statItem) return null
+
+ if (isClickable(statItem.param1)) return statItem.param1
+
+ return statItem.param2 && isClickable(statItem.param2)
+ ? statItem.param2
+ : null
+ }, [matchProfile?.team1.id, matchProfile?.team2.id])
+
const fetchTeamsStats = useMemo(() => throttle(async (second?: number) => {
const videoBounds = matchScore?.video_bounds || matchProfile?.video_bounds
@@ -68,7 +108,11 @@ export const useTeamsStats = ({
...(!isUndefined(second) && getHalfTime(videoBounds, second)),
})
- setTeamsStats(data)
+ const needUseFakeData = getLocalStorageItem(TOUR_COMPLETED_STORAGE_KEY) !== 'true' && !getFirstClickableParam(data)
+
+ const stats = needUseFakeData ? fakeData.teamsStats : data
+
+ setTeamsStats(stats)
setIsTeamsStatsFetching(false)
// eslint-disable-next-line no-empty
@@ -82,8 +126,14 @@ export const useTeamsStats = ({
matchId,
setIsTeamsStatsFetching,
sportName,
+ fakeData,
+ getFirstClickableParam,
])
+ const beforeCloseTourCallback = () => {
+ isCurrentStats ? fetchTeamsStats(playingProgress) : fetchTeamsStats()
+ }
+
useEffect(() => {
let interval: NodeJS.Timeout
@@ -107,6 +157,9 @@ export const useTeamsStats = ({
}, [fetchTeamsStats, playingProgress, isCurrentStats])
return {
+ beforeCloseTourCallback,
+ getFirstClickableParam,
+ isClickable,
statsType,
teamsStats,
}
diff --git a/src/features/MatchPage/styled.tsx b/src/features/MatchPage/styled.tsx
index 921d44d8..affa412e 100644
--- a/src/features/MatchPage/styled.tsx
+++ b/src/features/MatchPage/styled.tsx
@@ -1,9 +1,12 @@
import styled, { css } from 'styled-components/macro'
-import { devices } from 'config/devices'
-import { isMobileDevice } from 'config/userAgent'
+import { isMobileDevice, devices } from 'config'
-export const Wrapper = styled.div`
+type WrapperProps = {
+ isTourOpen?: boolean,
+}
+
+export const Wrapper = styled.div`
width: 100%;
height: calc(100vh - 115px);
margin: 20px 0px 0 10px;
@@ -24,6 +27,12 @@ export const Wrapper = styled.div`
}
`
: ''};
+
+ ${({ isTourOpen }) => (isTourOpen && isMobileDevice
+ ? css`
+ padding: 0 5px;
+ `
+ : '')};
`
export const Container = styled.div`
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx
index 8854e436..0380d6da 100644
--- a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/index.tsx
@@ -30,6 +30,7 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
isExpanded,
paramColumnWidth,
params,
+ paramsCount,
showExpandButton,
showLeftArrow,
showRightArrow,
@@ -60,6 +61,7 @@ export const usePlayersTable = ({ teamId }: PlayersTableProps) => {
isExpanded,
paramColumnWidth,
params,
+ paramsCount,
players,
showExpandButton,
showLeftArrow,
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
index 65c38b1f..5db4b71b 100644
--- a/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/hooks/useTable.tsx
@@ -16,20 +16,15 @@ import { useQueryClient } from 'react-query'
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 map from 'lodash/map'
import { isMobileDevice, querieKeys } from 'config'
-import type {
- PlayerParam,
- PlayersStats,
- MatchScore,
-} from 'requests'
+import type { PlayerParam, MatchScore } from 'requests'
import { getStatsEvents } from 'requests'
-import { usePageParams, useToggle } from 'hooks'
+import { usePageParams } from 'hooks'
import { useMatchPageStore } from 'features/MatchPage/store'
import { useLexicsConfig } from 'features/LexicsStore'
@@ -63,47 +58,27 @@ export const useTable = ({
const [paramColumnWidth, setParamColumnWidth] = useState(PARAM_COLUMN_WIDTH_DEFAULT)
const {
- close: reduceTable,
- isOpen: isExpanded,
- toggle: toggleIsExpanded,
- } = useToggle()
- const {
+ getParams,
+ isExpanded,
playersStats,
playingProgress,
playStatsEpisodes,
profile,
+ reduceTable,
setIsPlayingFiltersEpisodes,
setPlayingData,
setWatchAllEpisodesTimer,
statsType,
+ toggleIsExpanded,
} = useMatchPageStore()
+
const { profileId, sportType } = usePageParams()
const client = useQueryClient()
const matchScore = client.getQueryData(querieKeys.matchScore)
- 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 params = useMemo(() => getParams(playersStats[teamId]), [getParams, playersStats, teamId])
const lexics = useMemo(() => (
reduce>(
@@ -251,6 +226,7 @@ export const useTable = ({
isExpanded,
paramColumnWidth,
params,
+ paramsCount,
showExpandButton: !isMobileDevice && paramsCount > DISPLAYED_PARAMS_COLUMNS,
showLeftArrow,
showRightArrow,
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
index 1061daeb..f96d52ad 100644
--- a/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/index.tsx
@@ -1,5 +1,7 @@
import { Fragment } from 'react'
+import { useTour } from '@reactour/tour'
+
import map from 'lodash/map'
import includes from 'lodash/includes'
@@ -11,9 +13,14 @@ import { useLexicsStore } from 'features/LexicsStore'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Loader } from 'features/Loader'
import { defaultTheme } from 'features/Theme/config'
+import { Spotlight, Steps } from 'features/MatchTour'
import type { PlayersTableProps } from './types'
-import { FIRST_COLUMN_WIDTH_DEFAULT, FIRST_COLUMN_WIDTH_EXPANDED } from './config'
+import {
+ DISPLAYED_PARAMS_COLUMNS,
+ FIRST_COLUMN_WIDTH_DEFAULT,
+ FIRST_COLUMN_WIDTH_EXPANDED,
+} from './config'
import { usePlayersTable } from './hooks'
import { Cell } from './Cell'
import {
@@ -44,6 +51,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
isExpanded,
paramColumnWidth,
params,
+ paramsCount,
players,
showExpandButton,
showLeftArrow,
@@ -61,6 +69,7 @@ export const PlayersTable = (props: PlayersTableProps) => {
playingData,
watchAllEpisodesTimer,
} = useMatchPageStore()
+ const { currentStep, isOpen } = useTour()
const firstColumnWidth = isExpanded ? FIRST_COLUMN_WIDTH_EXPANDED : FIRST_COLUMN_WIDTH_DEFAULT
@@ -75,21 +84,28 @@ export const PlayersTable = (props: PlayersTableProps) => {
- {!isExpanded && (
+ {!isExpanded && paramsCount > DISPLAYED_PARAMS_COLUMNS && (
- {showRightArrow && (
-
-
-
- )}
+
+
+ {Boolean(currentStep === Steps.ShowMoreStats && isOpen) && (
+
+ )}
+
)}
-
+
| {
isExpanded={isExpanded}
aria-label={isExpanded ? 'Reduce' : 'Expand'}
onClick={toggleIsExpanded}
+ data-step={Steps.ShowLessStats}
>
+ {Boolean(currentStep === Steps.ShowLessStats && isOpen) && (
+
+ )}
)}
|
diff --git a/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx b/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
index 84d1721b..aa70dc05 100644
--- a/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
+++ b/src/features/MatchSidePlaylists/components/PlayersTable/styled.tsx
@@ -29,6 +29,7 @@ export const Container = styled.div`
type TableWrapperProps = {
isExpanded?: boolean,
+ isOpenTour?: boolean,
}
export const TableWrapper = styled.div`
@@ -54,6 +55,19 @@ export const TableWrapper = styled.div`
`
: '')}
+
+ ${({ isOpenTour }) => (isOpenTour
+ ? css`
+ clip-path: none;
+ `
+ : '')}
+
+ ${({ isExpanded, isOpenTour }) => (isOpenTour && isExpanded
+ ? css`
+ overflow-x: initial;
+ `
+ : '')}
+
${isMobileDevice
? ''
: css`
@@ -67,12 +81,23 @@ export const TableWrapper = styled.div`
: ''};
`
-export const Table = styled.table`
+type TableProps = {
+ fullWidth?: boolean,
+}
+
+export const Table = styled.table`
border-radius: 5px;
border-spacing: 0;
border-collapse: collapse;
letter-spacing: -0.078px;
table-layout: fixed;
+
+ ${({ fullWidth }) => (fullWidth
+ ? css`
+ width: 100%;
+ `
+ : '')
+}
`
type ParamShortTitleProps = {
@@ -251,7 +276,11 @@ const ArrowButton = styled(ArrowButtonBase)`
: ''};
`
-export const ArrowButtonRight = styled(ArrowButton)`
+type ArrowButtonRightProps = {
+ visible?: boolean,
+}
+
+export const ArrowButtonRight = styled(ArrowButton)`
right: 0;
border-top-right-radius: 5px;
@@ -259,6 +288,13 @@ export const ArrowButtonRight = styled(ArrowButton)`
left: auto;
right: 7px;
}
+
+ ${({ visible }) => (!visible
+ ? css`
+ visibility: hidden;
+ `
+ : '')
+}
`
export const ArrowButtonLeft = styled(ArrowButton)`
@@ -276,7 +312,7 @@ export const ExpandButton = styled(ArrowButton)`
${Arrow} {
left: ${({ isExpanded }) => (isExpanded ? -6 : -2)}px;
- :last-child {
+ :last-of-type {
margin-left: 7px;
}
}
diff --git a/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx b/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx
index ea8df099..79f8c957 100644
--- a/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx
+++ b/src/features/MatchSidePlaylists/components/TabStats/hooks.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState } from 'react'
+import { useEffect } from 'react'
import isEmpty from 'lodash/isEmpty'
@@ -9,11 +9,11 @@ import { useMatchPageStore } from 'features/MatchPage/store'
import { StatsType, Tabs } from './config'
export const useTabStats = () => {
- const [selectedTab, setSelectedTab] = useState(Tabs.TEAMS)
-
const {
isEmptyPlayersStats,
profile: matchProfile,
+ selectedStatsTable: selectedTab,
+ setSelectedStatsTable: setSelectedTab,
statsType,
teamsStats,
toggleStatsType,
@@ -56,7 +56,12 @@ export const useTabStats = () => {
default:
}
- }, [isVisibleTeam1PlayersTab, isVisibleTeam2PlayersTab, isVisibleTeamsTab])
+ }, [
+ isVisibleTeam1PlayersTab,
+ isVisibleTeam2PlayersTab,
+ isVisibleTeamsTab,
+ setSelectedTab,
+ ])
return {
isFinalStatsType,
diff --git a/src/features/MatchSidePlaylists/components/TabStats/index.tsx b/src/features/MatchSidePlaylists/components/TabStats/index.tsx
index 297e4465..e059deff 100644
--- a/src/features/MatchSidePlaylists/components/TabStats/index.tsx
+++ b/src/features/MatchSidePlaylists/components/TabStats/index.tsx
@@ -1,6 +1,10 @@
import type { ComponentProps } from 'react'
import { createPortal } from 'react-dom'
+import { useTour } from '@reactour/tour'
+
+import includes from 'lodash/includes'
+
import { isMobileDevice } from 'config'
import { getTeamAbbr } from 'helpers'
@@ -11,6 +15,7 @@ import { T9n } from 'features/T9n'
import { useMatchPageStore } from 'features/MatchPage/store'
import { Name } from 'features/Name'
import { useLexicsStore } from 'features/LexicsStore'
+import { Spotlight, Steps } from 'features/MatchTour'
import { Tabs } from './config'
import { useTabStats } from './hooks'
@@ -59,6 +64,8 @@ export const TabStats = () => {
const modalRoot = useModalRoot()
+ const { currentStep, isOpen } = useTour()
+
const TabPane = tabPanes[selectedTab]
if (!matchProfile) return null
@@ -73,16 +80,21 @@ export const TabStats = () => {
setSelectedTab(Tabs.TEAMS)}
+ data-step={Steps.TeamsTab}
>
+ {Boolean(currentStep === Steps.TeamsTab && isOpen) && (
+
+ )}
)}
{isVisibleTeam1PlayersTab && (
setSelectedTab(Tabs.TEAM1)}
+ data-step={Steps.PlayersTab}
>
{
}}
/>
+ {Boolean(currentStep === Steps.PlayersTab && isOpen) && (
+
+ )}
)}
{isVisibleTeam2PlayersTab && (
@@ -134,7 +149,15 @@ export const TabStats = () => {
)}
-
+
`
export const Tab = styled.button.attrs({ role: 'tab' })`
position: relative;
display: flex;
- justify-content: space-between;
+ justify-content: center;
align-items: center;
padding: 0 10px 10px;
font-size: 12px;
@@ -78,10 +80,36 @@ export const Tab = styled.button.attrs({ role: 'tab' })`
color: ${({ theme }) => theme.colors.white};
}
}
+
+ ${isMobileDevice
+ ? css`
+ width: 20vw;
+ `
+ : ''
+}
`
-export const Switch = styled.div`
+type SwitchProps = {
+ highlighted?: boolean,
+}
+
+export const Switch = styled.div`
+ position: relative;
display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 27px;
+ margin-top: -5px;
+ padding: 0 5px;
+ border: 1px solid transparent;
+
+ ${({ highlighted }) => (highlighted
+ ? css`
+ border-radius: 6px;
+ border-color: #0057FF;
+ background: radial-gradient(50% 50% at 50% 50%, rgba(0, 87, 255, 0) 70.25%, rgba(0, 87, 255, 0.4) 100%);
+ `
+ : '')}
`
export const SwitchTitle = styled(T9n)`
@@ -98,7 +126,6 @@ export const SwitchButton = styled.button`
width: 20px;
height: 7px;
margin-left: 5px;
- margin-top: 5px;
border-radius: 2px;
border: none;
border: 1px solid ${({ theme }) => theme.colors.white};
diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx
index 4f0a183d..83975631 100644
--- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/Cell.tsx
@@ -1,6 +1,8 @@
import { Fragment, useRef } from 'react'
import { useQueryClient } from 'react-query'
+import { useTour } from '@reactour/tour'
+
import isNumber from 'lodash/isNumber'
import { KEYBOARD_KEYS, querieKeys } from 'config'
@@ -16,6 +18,7 @@ import { usePageParams, useEventListener } from 'hooks'
import { getHalfTime } from 'features/MatchPage/helpers/getHalfTime'
import { useMatchPageStore } from 'features/MatchPage/store'
+import { Spotlight, Steps } from 'features/MatchTour'
import { StatsType } from '../TabStats/config'
import { CircleAnimationBar } from '../CircleAnimationBar'
@@ -28,19 +31,22 @@ import {
} from './styled'
type CellProps = {
+ firstClickableParam: Param | null,
teamId: number,
teamStatItem: TeamStatItem | null,
}
export const Cell = ({
+ firstClickableParam,
teamId,
teamStatItem,
}: CellProps) => {
- const paramValueContainerRef = useRef(null)
+ const paramValueContainerRef = useRef(null)
const { profileId, sportType } = usePageParams()
const {
+ isClickable,
playingData,
playingProgress,
playStatsEpisodes,
@@ -52,14 +58,12 @@ export const Cell = ({
watchAllEpisodesTimer,
} = useMatchPageStore()
+ const { currentStep, isOpen } = useTour()
+
const client = useQueryClient()
const matchScore = client.getQueryData(querieKeys.matchScore)
- const isClickable = (param: Param) => (
- Boolean(param.val) && param.clickable
- )
-
const getDisplayedValue = (val: number | null) => (
isNumber(val) ? String(val) : '-'
)
@@ -137,8 +141,15 @@ export const Cell = ({
onClick={() => onParamClick(teamStatItem.param1)}
data-param-id={teamStatItem.param1.id}
hasValue={Boolean(teamStatItem.param1.val)}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...firstClickableParam === teamStatItem.param1 && {
+ 'data-step': Steps.ClickToWatchPlaylist,
+ }}
>
{getDisplayedValue(teamStatItem.param1.val)}
+ {firstClickableParam === teamStatItem.param1
+ && Boolean(currentStep === Steps.ClickToWatchPlaylist && isOpen)
+ && }
)}
@@ -163,8 +174,15 @@ export const Cell = ({
onClick={() => onParamClick(teamStatItem.param2!)}
data-param-id={teamStatItem.param2.id}
hasValue={Boolean(teamStatItem.param2.val)}
+ // eslint-disable-next-line react/jsx-props-no-spreading
+ {...firstClickableParam === teamStatItem.param2 && {
+ 'data-step': Steps.ClickToWatchPlaylist,
+ }}
>
{getDisplayedValue(teamStatItem.param2.val)}
+ {firstClickableParam === teamStatItem.param2
+ && Boolean(currentStep === Steps.ClickToWatchPlaylist && isOpen)
+ && }
)}
diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
index 035d2eee..257b8ca2 100644
--- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/hooks.tsx
@@ -6,6 +6,7 @@ import { useMatchPageStore } from 'features/MatchPage/store'
export const useTeamsStatsTable = () => {
const {
+ getFirstClickableParam,
plaingOrder,
profile,
setCircleAnimation,
@@ -26,6 +27,7 @@ export const useTeamsStatsTable = () => {
}, [setCircleAnimation, plaingOrder])
return {
+ firstClickableParam: getFirstClickableParam(teamsStats),
getStatItemById,
}
}
diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
index a67c3284..c1c05cde 100644
--- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/index.tsx
@@ -1,3 +1,5 @@
+import { useTour } from '@reactour/tour'
+
import map from 'lodash/map'
import { useMatchPageStore } from 'features/MatchPage/store'
@@ -25,10 +27,15 @@ export const TeamsStatsTable = () => {
teamsStats,
} = useMatchPageStore()
- const { getStatItemById } = useTeamsStatsTable()
+ const {
+ firstClickableParam,
+ getStatItemById,
+ } = useTeamsStatsTable()
const { shortSuffix } = useLexicsStore()
+ const { isOpen } = useTour()
+
if (!profile) return null
if (isTeamsStatsFetching) {
@@ -39,7 +46,7 @@ export const TeamsStatsTable = () => {
return (
-
+
@@ -69,6 +76,7 @@ export const TeamsStatsTable = () => {
|
@@ -78,6 +86,7 @@ export const TeamsStatsTable = () => {
|
)
diff --git a/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
index 42b6713c..dc990ed2 100644
--- a/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
+++ b/src/features/MatchSidePlaylists/components/TeamsStatsTable/styled.tsx
@@ -7,7 +7,11 @@ import { customScrollbar } from 'features/Common'
export const Container = styled.div``
-export const TableWrapper = styled.div`
+type TableWrapperProps = {
+ isOpenTour?: boolean,
+}
+
+export const TableWrapper = styled.div`
width: 100%;
overflow: auto;
font-size: 11px;
@@ -20,6 +24,13 @@ export const TableWrapper = styled.div`
max-height: calc(100vh - 203px);
`};
+ ${({ isOpenTour }) => (isOpenTour
+ ? css`
+ clip-path: none;
+ overflow: initial;
+ `
+ : '')}
+
${customScrollbar}
`
@@ -90,11 +101,11 @@ type TParamValue = {
export const ParamValue = styled.span.attrs(({ clickable }: TParamValue) => ({
...clickable && { tabIndex: 0 },
}))`
+ position: relative;
display: inline-block;
width: 15px;
height: 15px;
text-align: center;
- position: relative;
font-weight: ${({ clickable }) => (clickable ? 700 : 400)};
color: ${({ clickable, theme }) => (clickable ? '#5EB2FF' : theme.colors.white)};
diff --git a/src/features/MatchSidePlaylists/index.tsx b/src/features/MatchSidePlaylists/index.tsx
index bb67f45e..22ab2bf7 100644
--- a/src/features/MatchSidePlaylists/index.tsx
+++ b/src/features/MatchSidePlaylists/index.tsx
@@ -3,14 +3,26 @@ import {
useRef,
useState,
} from 'react'
+import { createPortal } from 'react-dom'
+
+import { useTour } from '@reactour/tour'
import type { PlaylistOption } from 'features/MatchPage/types'
import { useMatchPageStore } from 'features/MatchPage/store'
+import {
+ Spotlight,
+ Steps,
+ TOUR_COMPLETED_STORAGE_KEY,
+} from 'features/MatchTour'
+
+import { Overlay } from 'components/Overlay'
-import { useEventListener } from 'hooks'
+import { useEventListener, useModalRoot } from 'hooks'
import { isIOS } from 'config/userAgent'
+import { getLocalStorageItem } from 'helpers/getLocalStorage'
+
import { Tabs } from './config'
import { TabEvents } from './components/TabEvents'
import { TabWatch } from './components/TabWatch'
@@ -25,6 +37,7 @@ import {
TabIcon,
TabTitle,
Container,
+ TabButton,
} from './styled'
const tabPanes = {
@@ -61,6 +74,14 @@ export const MatchSidePlaylists = ({
onTabClick,
} = useMatchSidePlaylists()
+ const {
+ currentStep,
+ isOpen,
+ setIsOpen,
+ } = useTour()
+
+ const modalRoot = useModalRoot()
+
const TabPane = tabPanes[selectedTab]
const containerRef = useRef(null)
@@ -82,6 +103,19 @@ export const MatchSidePlaylists = ({
tabPaneContainerRef.current?.clientHeight,
])
+ useEffect(() => {
+ if (
+ getLocalStorageItem(TOUR_COMPLETED_STORAGE_KEY) === 'true'
+ || isOpen
+ || !isStatsTabVisible
+ || Number(profile?.c_match_calc_status) < 2
+ ) return undefined
+
+ const timer = setTimeout(() => setIsOpen(true), 1500)
+
+ return () => clearTimeout(timer)
+ }, [isStatsTabVisible, setIsOpen, profile?.c_match_calc_status, isOpen])
+
useEventListener({
callback: () => {
const screenLandscape = isIOS ? window.orientation : window.screen.orientation.type
@@ -98,7 +132,12 @@ export const MatchSidePlaylists = ({
})
return (
-
+
{isWatchTabVisible ? (
@@ -106,8 +145,10 @@ export const MatchSidePlaylists = ({
aria-pressed={selectedTab === Tabs.WATCH}
onClick={() => onTabClick(Tabs.WATCH)}
>
-
-
+
+
+
+
) : null}
{isEventTabVisible ? (
@@ -115,8 +156,10 @@ export const MatchSidePlaylists = ({
aria-pressed={selectedTab === Tabs.EVENTS}
onClick={() => onTabClick(Tabs.EVENTS)}
>
-
-
+
+
+
+
) : null}
{isPlayersTabVisible ? (
@@ -124,17 +167,25 @@ export const MatchSidePlaylists = ({
aria-pressed={selectedTab === Tabs.PLAYERS}
onClick={() => onTabClick(Tabs.PLAYERS)}
>
-
-
+
+
+
+
) : null}
{isStatsTabVisible ? (
onTabClick(Tabs.STATS)}
+ data-step={Steps.Start}
>
-
-
+ {Boolean(currentStep === Steps.Start && isOpen) && (
+
+ )}
+
+
+
+
) : null}
@@ -144,6 +195,7 @@ export const MatchSidePlaylists = ({
hasScroll={hasTabPaneScroll}
ref={tabPaneContainerRef}
forWatchTab={selectedTab === Tabs.WATCH}
+ highlighted={Boolean(isOpen)}
>
+ {modalRoot.current && isOpen && createPortal(
+ ,
+ modalRoot.current,
+ )}
)
}
diff --git a/src/features/MatchSidePlaylists/styled.tsx b/src/features/MatchSidePlaylists/styled.tsx
index 6410245e..9c582b10 100644
--- a/src/features/MatchSidePlaylists/styled.tsx
+++ b/src/features/MatchSidePlaylists/styled.tsx
@@ -9,8 +9,22 @@ import {
import { customScrollbar } from 'features/Common'
import { T9n } from 'features/T9n'
-export const Wrapper = styled.div`
+type WrapperProps = {
+ highlighted?: boolean,
+ isTourOpen?: boolean,
+}
+
+export const Wrapper = styled.div`
padding-right: 14px;
+ padding-top: 10px;
+
+ ${({ highlighted }) => (highlighted
+ ? css`
+ border: 1px solid #0057FF;
+ border-radius: 5px;
+ box-shadow: 0px 0px 66px 16px rgba(0, 87, 255, 0.8);
+ `
+ : '')}
${isMobileDevice
? css`
@@ -21,6 +35,13 @@ export const Wrapper = styled.div`
${customScrollbar}
`
: ''};
+
+ ${({ isTourOpen }) => (isTourOpen
+ ? css`
+ overflow-y: initial;
+ z-index: 9999;
+ `
+ : '')}
`
export const TabsWrapper = styled.div``
@@ -36,8 +57,6 @@ export const TabsGroup = styled.div.attrs({ role: 'tablist' })`
${({ hasLessThanFourTabs }) => (hasLessThanFourTabs
? css`
- padding-top: 10px;
-
${Tab} {
justify-content: center;
flex-direction: row;
@@ -58,7 +77,7 @@ export const TabTitle = styled(T9n)`
color: ${({ theme }) => theme.colors.white};
`
-export const Tab = styled.button.attrs({ role: 'tab' })`
+export const TabButton = styled.button`
display: flex;
flex-direction: column;
justify-content: space-between;
@@ -69,12 +88,18 @@ export const Tab = styled.button.attrs({ role: 'tab' })`
cursor: pointer;
border: none;
background: none;
+`
+
+export const Tab = styled.div.attrs({ role: 'tab' })`
+ position: relative;
&[aria-pressed="true"], :hover {
- opacity: 1;
+ ${TabButton} {
+ opacity: 1;
- ${TabTitle} {
- font-weight: 600;
+ ${TabTitle} {
+ font-weight: 600;
+ }
}
}
@@ -107,6 +132,7 @@ export const TabIcon = styled.div`
type TContainer = {
forWatchTab?: boolean,
hasScroll: boolean,
+ highlighted?: boolean,
}
export const Container = styled.div`
@@ -140,6 +166,12 @@ export const Container = styled.div`
}
`
: ''};
+
+ ${({ highlighted }) => (highlighted
+ ? css`
+ overflow-y: initial;
+ `
+ : '')}
`
type ButtonProps = {
diff --git a/src/features/MatchTour/TourProvider.tsx b/src/features/MatchTour/TourProvider.tsx
new file mode 100644
index 00000000..1651a719
--- /dev/null
+++ b/src/features/MatchTour/TourProvider.tsx
@@ -0,0 +1,198 @@
+import type {
+ PropsWithChildren,
+ ComponentProps,
+ CSSProperties,
+} from 'react'
+import { Fragment } from 'react'
+
+import compact from 'lodash/compact'
+
+import type { StepType } from '@reactour/tour'
+import { TourProvider as TourProviderLib } from '@reactour/tour'
+
+import { isMobileDevice } from 'config'
+
+import { disableBodyScroll, enableBodyScroll } from 'helpers'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+
+import { Steps } from './config'
+import {
+ ContentComponent,
+ Body,
+ BodyText,
+ Title,
+} from './components/ContentComponent'
+
+const getPopoverStyle = (base: CSSProperties): CSSProperties => ({
+ ...base,
+ borderRadius: 6,
+ lineHeight: 1,
+ maxWidth: 'auto',
+ padding: 20,
+ width: 340,
+ ...isMobileDevice && {
+ padding: '20px 25px',
+ textAlign: 'center',
+ width: '95vw',
+ },
+})
+
+const getPopoverPosition = (baseCoords: [number, number]): [number, number] => (
+ isMobileDevice ? [0.001, 0.001] : baseCoords
+)
+
+const getSelector = (step: Steps) => (
+ isMobileDevice ? `[data-step="${Steps.Start}"]` : `[data-step="${step}"]`
+)
+
+const steps: Array = compact([
+ {
+ content: (
+
+
+
+
+
+
+ ),
+ padding: {
+ popover: getPopoverPosition([15, 10]),
+ },
+ selector: getSelector(Steps.Start),
+ styles: {
+ popover: (base) => ({
+ ...getPopoverStyle(base),
+ textAlign: 'left',
+ }),
+ },
+ },
+ {
+ content: (
+
+
+
+
+
+
+ ),
+ padding: {
+ popover: getPopoverPosition([5, 0.001]),
+ },
+ selector: getSelector(Steps.Welcome),
+ styles: {
+ popover: (base) => ({
+ ...getPopoverStyle(base),
+ ...isMobileDevice && {
+ padding: '20px 0',
+ },
+ }),
+ },
+ },
+ {
+ content: (
+
+
+
+
+
+
+ ),
+ padding: {
+ popover: getPopoverPosition([8, 20]),
+ },
+ selector: getSelector(Steps.TeamsTab),
+ },
+ {
+ content: (
+
+ ),
+ selector: getSelector(Steps.ClickToWatchPlaylist),
+ },
+ {
+ content: (
+
+ ),
+ padding: {
+ popover: getPopoverPosition([10, 17]),
+ },
+ selector: getSelector(Steps.PlayersTab),
+ },
+ {
+ content: (
+
+ ),
+ selector: getSelector(Steps.ShowMoreStats),
+ },
+ !isMobileDevice && {
+ content: (
+
+ ),
+ padding: {
+ popover: [20, 10],
+ },
+ selector: getSelector(Steps.ShowLessStats),
+ },
+ {
+ content: (
+
+ ),
+ padding: {
+ popover: getPopoverPosition([10, 0.001]),
+ },
+ selector: getSelector(Steps.FinalStats),
+ },
+ {
+ content: (
+
+ ),
+ padding: {
+ popover: getPopoverPosition([10, 0.001]),
+ },
+ selector: getSelector(Steps.FinalStats),
+ },
+])
+
+const styles: ComponentProps['styles'] = {
+ maskWrapper: () => ({
+ display: 'none',
+ }),
+ popover: getPopoverStyle,
+}
+
+const padding: ComponentProps['padding'] = {
+ popover: isMobileDevice ? [0.001, 0.001] : [15, 25],
+}
+
+export const TourProvider = ({ children }: PropsWithChildren<{}>) => {
+ const { beforeCloseTourCallback } = useMatchPageStore()
+
+ const afterOpen = (target: Element | null) => {
+ target && disableBodyScroll(target)
+ }
+
+ const beforeClose = (target: Element | null) => {
+ target && enableBodyScroll(target)
+ beforeCloseTourCallback()
+ }
+
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/features/MatchTour/components/ContentComponent/hooks.tsx b/src/features/MatchTour/components/ContentComponent/hooks.tsx
new file mode 100644
index 00000000..66c15559
--- /dev/null
+++ b/src/features/MatchTour/components/ContentComponent/hooks.tsx
@@ -0,0 +1,161 @@
+import {
+ useEffect,
+ useRef,
+ useMemo,
+ useCallback,
+} from 'react'
+
+import throttle from 'lodash/throttle'
+
+import type { PopoverContentProps } from '@reactour/tour'
+
+import { isMobileDevice, KEYBOARD_KEYS } from 'config'
+
+import { useEventListener } from 'hooks'
+
+import { useMatchPageStore } from 'features/MatchPage/store'
+import { Tabs } from 'features/MatchSidePlaylists/config'
+import { StatsType, Tabs as StatTabs } from 'features/MatchSidePlaylists/components/TabStats/config'
+
+import { Steps } from '../../config'
+
+const KEY_PRESS_DELAY = 1500
+
+export const useContentComponent = ({
+ currentStep,
+ setCurrentStep,
+ setIsOpen,
+ steps,
+}: PopoverContentProps) => {
+ const {
+ setSelectedStatsTable,
+ setSelectedTab,
+ setStatsType,
+ toggleIsExpanded,
+ } = useMatchPageStore()
+
+ const timerRef = useRef()
+
+ const back = useCallback(() => {
+ switch (currentStep) {
+ case Steps.Start:
+ case Steps.Welcome:
+ return
+ case Steps.PlayersTab:
+ setSelectedStatsTable(StatTabs.TEAMS)
+ break
+ case Steps.ShowLessStats:
+ if (!isMobileDevice) {
+ toggleIsExpanded()
+ }
+ break
+ case Steps.FinalStats:
+ if (isMobileDevice) {
+ setStatsType(StatsType.FINAL_STATS)
+ } else {
+ toggleIsExpanded()
+ }
+ break
+
+ case Steps.CurrentStats:
+ setStatsType(StatsType.FINAL_STATS)
+ break
+
+ default:
+ }
+
+ timerRef.current = setTimeout(() => setCurrentStep((step) => step - 1), 0)
+ }, [
+ currentStep,
+ setCurrentStep,
+ setSelectedStatsTable,
+ setStatsType,
+ toggleIsExpanded,
+ ])
+
+ const next = useCallback(() => {
+ switch (currentStep) {
+ case steps.length - 1:
+ return
+ case Steps.Start:
+ setSelectedTab(Tabs.STATS)
+ break
+
+ case Steps.ClickToWatchPlaylist:
+ setSelectedStatsTable(StatTabs.TEAM1)
+ break
+
+ case Steps.ShowMoreStats:
+ if (isMobileDevice) {
+ setStatsType(StatsType.FINAL_STATS)
+ } else {
+ toggleIsExpanded()
+ }
+ break
+
+ case Steps.ShowLessStats:
+ if (isMobileDevice) {
+ setStatsType(StatsType.CURRENT_STATS)
+ } else {
+ setStatsType(StatsType.FINAL_STATS)
+ toggleIsExpanded()
+ }
+ break
+
+ case Steps.FinalStats:
+ if (!isMobileDevice) {
+ setStatsType(StatsType.CURRENT_STATS)
+ }
+ break
+
+ default:
+ }
+
+ timerRef.current = setTimeout(() => setCurrentStep((step) => step + 1), 0)
+ }, [
+ currentStep,
+ setCurrentStep,
+ setSelectedStatsTable,
+ setSelectedTab,
+ setStatsType,
+ toggleIsExpanded,
+ steps.length,
+ ])
+
+ const skipTour = useCallback(() => {
+ setIsOpen(false)
+ }, [setIsOpen])
+
+ useEventListener({
+ callback: useMemo(() => throttle((e: KeyboardEvent) => {
+ e.stopPropagation()
+
+ switch (e.code) {
+ case KEYBOARD_KEYS.ArrowLeft:
+ back()
+ break
+
+ case KEYBOARD_KEYS.ArrowRight:
+ next()
+ break
+
+ case KEYBOARD_KEYS.Esc:
+ skipTour()
+ break
+ default:
+ }
+ }, KEY_PRESS_DELAY), [back, next, skipTour]),
+ event: 'keydown',
+ options: true,
+ })
+
+ useEffect(() => () => {
+ timerRef.current && clearTimeout(timerRef.current)
+ }, [])
+
+ return {
+ back,
+ next,
+ skipTour,
+ }
+}
diff --git a/src/features/MatchTour/components/ContentComponent/index.tsx b/src/features/MatchTour/components/ContentComponent/index.tsx
new file mode 100644
index 00000000..f9e03494
--- /dev/null
+++ b/src/features/MatchTour/components/ContentComponent/index.tsx
@@ -0,0 +1,123 @@
+import type { ComponentType } from 'react'
+import { Fragment } from 'react'
+
+import type { PopoverContentProps } from '@reactour/tour'
+
+import { isMobileDevice } from 'config'
+
+import { T9n } from 'features/T9n'
+
+import { useContentComponent } from './hooks'
+import {
+ PrevButton,
+ NextButton,
+ ActionButtonsContainer,
+ Counter,
+ SkipTour,
+ ArrowWrapper,
+} from './styled'
+
+import { Steps } from '../../config'
+
+export * from './styled'
+
+const Arrow = () => (
+
+
+
+
+)
+
+export const ContentComponent: ComponentType = (props) => {
+ const {
+ back,
+ next,
+ skipTour,
+ } = useContentComponent(props)
+
+ const { currentStep, steps } = props
+
+ const renderActionButtons = () => {
+ switch (currentStep) {
+ case Steps.Start:
+ return (
+
+
+
+
+
+
+
+
+ )
+
+ case Steps.Welcome:
+ return (
+
+
+ {currentStep}/{steps.length - 1}
+
+
+
+
+
+
+
+
+ )
+
+ case steps.length - 1:
+ return (
+
+
+ {currentStep}/{steps.length - 1}
+
+
+
+
+
+
+
+
+ )
+
+ default:
+ return (
+
+
+ {currentStep}/{steps.length - 1}
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+ }
+
+ return (
+
+ {steps[currentStep].content}
+
+ {renderActionButtons()}
+
+ {!isMobileDevice && }
+
+ )
+}
+
diff --git a/src/features/MatchTour/components/ContentComponent/styled.tsx b/src/features/MatchTour/components/ContentComponent/styled.tsx
new file mode 100644
index 00000000..85da4902
--- /dev/null
+++ b/src/features/MatchTour/components/ContentComponent/styled.tsx
@@ -0,0 +1,158 @@
+import styled, { css } from 'styled-components/macro'
+
+import includes from 'lodash/includes'
+
+import { isMobileDevice } from 'config'
+
+import { T9n } from 'features/T9n'
+
+import { Steps } from '../../config'
+
+const NavButton = styled.button`
+ padding: 0;
+ border: none;
+ font-size: 12px;
+ font-weight: 700;
+ white-space: nowrap;
+ text-transform: uppercase;
+ text-decoration: none;
+ background: none;
+ cursor: pointer;
+
+ ${isMobileDevice
+ ? css`
+ font-size: 15px;
+ `
+ : ''}
+`
+
+type PrevButtonProps = {
+ isLastStep?: boolean,
+}
+
+export const PrevButton = styled(NavButton)`
+ color: rgba(0, 0, 0, 0.5);
+`
+
+export const NextButton = styled(NavButton)`
+ color: #294FC3;
+`
+
+export const SkipTour = styled.button`
+ margin-top: -2px;
+ padding: 0;
+ border: none;
+ font-weight: 400;
+ font-size: 12px;
+ color: rgba(0, 0, 0, 0.5);
+ text-decoration: underline;
+ text-transform: uppercase;
+ cursor: pointer;
+ background: none;
+
+ ${isMobileDevice
+ ? css`
+ font-size: 15px;
+ `
+ : ''}
+`
+
+export const Counter = styled.div`
+ color: rgba(0, 0, 0, 0.5);
+ font-size: 12px;
+ font-weight: 700;
+ white-space: nowrap;
+
+ ${isMobileDevice
+ ? css`
+ font-size: 15px;
+ `
+ : ''}
+`
+
+type TitleProps = {
+ alignLeft?: boolean,
+}
+
+export const Title = styled(T9n)`
+ display: block;
+ margin-bottom: 10px;
+ font-size: 14px;
+ font-weight: 700;
+ line-height: 17px;
+
+ ${isMobileDevice
+ ? css`
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ padding: 0 3%;
+ line-height: 20px;
+ font-size: 16px;
+ `
+ : ''}
+
+ ${({ alignLeft }) => (alignLeft
+ ? css`
+ padding: 0;
+ `
+ : '')}
+`
+
+export const Body = styled.div`
+ margin-bottom: 15px;
+`
+
+export const BodyText = styled(T9n)`
+ font-size: 14px;
+ line-height: 17px;
+
+ ${isMobileDevice
+ ? css`
+ line-height: 20px;
+ font-size: 16px;
+ `
+ : ''}
+`
+
+type ActionButtonsContainerProps = {
+ step: Steps,
+}
+
+export const ActionButtonsContainer = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 24px;
+ padding: 0;
+ justify-content: space-between;
+
+ ${isMobileDevice
+ ? css`
+ padding: 0 5%;
+ margin: auto;
+ gap: 20px;
+ justify-content: center;
+ `
+ : ''}
+
+${({ step }) => (isMobileDevice && step === Steps.Start
+ ? css`
+ justify-content: space-between;
+ padding: 0;
+ `
+ : '')}
+
+ ${({ step }) => (isMobileDevice && (includes([Steps.FinalStats, Steps.Welcome], step))
+ ? css`
+ padding: 0 20%;
+ `
+ : '')}
+`
+
+export const ArrowWrapper = styled.svg`
+ position: absolute;
+ top: 24px;
+ right: 15px;
+`
diff --git a/src/features/MatchTour/components/Spotlight/index.tsx b/src/features/MatchTour/components/Spotlight/index.tsx
new file mode 100644
index 00000000..eade3547
--- /dev/null
+++ b/src/features/MatchTour/components/Spotlight/index.tsx
@@ -0,0 +1,100 @@
+import { memo, useRef } from 'react'
+
+import { useTour } from '@reactour/tour'
+
+import styled, { css, keyframes } from 'styled-components/macro'
+
+import { isMobileDevice } from 'config'
+
+import { Steps } from '../../config'
+
+type WrapperProps = {
+ step: number,
+}
+
+const getBaseSize = ({ step }: WrapperProps) => {
+ let baseSize = isMobileDevice ? 57 : 55
+
+ switch (step) {
+ case Steps.ClickToWatchPlaylist:
+ if (isMobileDevice) baseSize = 39
+ break
+ case Steps.TeamsTab:
+ case Steps.PlayersTab:
+ case Steps.ShowMoreStats:
+ if (isMobileDevice) baseSize = 75
+ break
+ case isMobileDevice ? Steps.ShowLessStats : Steps.FinalStats:
+ case isMobileDevice ? Steps.FinalStats : Steps.CurrentStats:
+ baseSize = 118
+ break
+
+ default:
+ }
+
+ return baseSize
+}
+
+const getAnimation = ({ step }: WrapperProps) => {
+ const baseSize = getBaseSize({ step })
+
+ return keyframes`
+ to {
+ scale: ${(baseSize + 5) / baseSize};
+ }
+ `
+}
+
+const Wrapper = styled.div`
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ display: block;
+ width: ${getBaseSize}px;
+ height: ${getBaseSize}px;
+ border-radius: 100%;
+ border: 1px solid #0057FF;
+ translate: -50% -50%;
+ background: radial-gradient(50% 50% at 50% 50%, rgba(0, 87, 255, 0) 70.25%, rgba(0, 87, 255, 0.4) 100%);
+ animation: ${getAnimation} 0.8s ease-in-out infinite alternate;
+ z-index: 9999;
+
+ ${({ step }) => {
+ switch (step) {
+ case Steps.ShowMoreStats:
+ return css`left: 3px;`
+
+ case Steps.ShowLessStats:
+ return isMobileDevice
+ ? ''
+ : css`left: 0;`
+
+ case isMobileDevice ? Steps.ShowLessStats : Steps.FinalStats:
+ case isMobileDevice ? Steps.FinalStats : Steps.CurrentStats:
+ return css`
+ right: -14px;
+ left: auto;
+ translate: 0 -50%;
+ `
+
+ default:
+ return ''
+ }
+ }}
+`
+
+const SpotlightFC = () => {
+ const ref = useRef(null)
+
+ const { currentStep } = useTour()
+
+ return (
+
+ )
+}
+
+export const Spotlight = memo(SpotlightFC)
+
diff --git a/src/features/MatchTour/components/index.tsx b/src/features/MatchTour/components/index.tsx
new file mode 100644
index 00000000..1ca579ee
--- /dev/null
+++ b/src/features/MatchTour/components/index.tsx
@@ -0,0 +1,2 @@
+export * from './ContentComponent'
+export * from './Spotlight'
diff --git a/src/features/MatchTour/config.tsx b/src/features/MatchTour/config.tsx
new file mode 100644
index 00000000..02435c26
--- /dev/null
+++ b/src/features/MatchTour/config.tsx
@@ -0,0 +1,13 @@
+export enum Steps {
+ Start,
+ Welcome,
+ TeamsTab,
+ ClickToWatchPlaylist,
+ PlayersTab,
+ ShowMoreStats,
+ ShowLessStats,
+ FinalStats,
+ CurrentStats,
+}
+
+export const TOUR_COMPLETED_STORAGE_KEY = 'tour_completed'
diff --git a/src/features/MatchTour/index.tsx b/src/features/MatchTour/index.tsx
new file mode 100644
index 00000000..4ae12cb7
--- /dev/null
+++ b/src/features/MatchTour/index.tsx
@@ -0,0 +1,3 @@
+export * from './config'
+export * from './TourProvider'
+export * from './components'
diff --git a/src/features/MultiSourcePlayer/hooks/index.tsx b/src/features/MultiSourcePlayer/hooks/index.tsx
index 8421f4ac..08ac5a6d 100644
--- a/src/features/MultiSourcePlayer/hooks/index.tsx
+++ b/src/features/MultiSourcePlayer/hooks/index.tsx
@@ -5,8 +5,12 @@ import {
useRef,
} from 'react'
+import { useTour } from '@reactour/tour'
+
import size from 'lodash/size'
+import { KEYBOARD_KEYS } from 'config'
+
import { useControlsVisibility } from 'features/StreamPlayer/hooks/useControlsVisibility'
import { useFullscreen } from 'features/StreamPlayer/hooks/useFullscreen'
import { useVolume } from 'features/VideoPlayer/hooks/useVolume'
@@ -83,6 +87,8 @@ export const useMultiSourcePlayer = ({
setPlayingProgress,
} = useMatchPageStore()
+ const { isOpen } = useTour()
+
const { profileId, sportType } = usePageParams()
/** время для сохранения статистики просмотра матча */
@@ -95,7 +101,7 @@ export const useMultiSourcePlayer = ({
activePlayer,
loadedProgress,
playedProgress,
- playing,
+ playing: statePlaying,
ready,
seek,
seeking,
@@ -121,6 +127,8 @@ export const useMultiSourcePlayer = ({
const duration = useDuration(chapters)
+ const playing = Boolean(statePlaying && !isOpen)
+
const handleError = useCallback(() => {
onError?.()
}, [onError])
@@ -310,8 +318,9 @@ export const useMultiSourcePlayer = ({
useEventListener({
callback: (e: KeyboardEvent) => {
- if (e.code === 'ArrowLeft') rewindBackward()
- else if (e.code === 'ArrowRight') rewindForward()
+ if (isOpen) return
+ if (e.code === KEYBOARD_KEYS.ArrowLeft) rewindBackward()
+ else if (e.code === KEYBOARD_KEYS.ArrowRight) rewindForward()
},
event: 'keydown',
})
diff --git a/src/features/PageLayout/styled.tsx b/src/features/PageLayout/styled.tsx
index 832a2d9e..3c1b32ed 100644
--- a/src/features/PageLayout/styled.tsx
+++ b/src/features/PageLayout/styled.tsx
@@ -3,9 +3,36 @@ import styled, { css } from 'styled-components/macro'
import { isMobileDevice } from 'config/userAgent'
import { customScrollbar } from 'features/Common'
-export const PageWrapper = styled.div<{isIOS?: boolean}>`
+type PageWrapperProps = {
+ isIOS?: boolean,
+ isTourOpen?: boolean,
+}
+
+export const PageWrapper = styled.div`
width: 100%;
touch-action: ${({ isIOS }) => (isIOS ? 'none' : 'unset')};
+
+ ${({ isTourOpen }) => (isTourOpen
+ ? css`
+ pointer-events: none;
+ overflow: hidden;
+ `
+ : '')}
+
+ ~ .reactour__popover {
+ ${isMobileDevice
+ ? css`
+ @media screen and (orientation: landscape) {
+ /* добавлен important чтобы переопределить стили либы */
+ left: 50% !important;
+ top: auto !important;
+ bottom: 0;
+ width: 70vw !important;
+ transform: translate(-50%, 0) !important;
+ }`
+ : ''};
+
+ }
`
export const Main = styled.main`
diff --git a/src/features/StreamPlayer/hooks/index.tsx b/src/features/StreamPlayer/hooks/index.tsx
index ab96e28b..0aebd25a 100644
--- a/src/features/StreamPlayer/hooks/index.tsx
+++ b/src/features/StreamPlayer/hooks/index.tsx
@@ -7,6 +7,8 @@ import {
useState,
} from 'react'
+import { useTour } from '@reactour/tour'
+
import size from 'lodash/size'
import isNumber from 'lodash/isNumber'
import isEmpty from 'lodash/isEmpty'
@@ -14,7 +16,7 @@ import isUndefined from 'lodash/isUndefined'
import Hls from 'hls.js'
-import { isIOS } from 'config/userAgent'
+import { isIOS, KEYBOARD_KEYS } from 'config'
import {
useObjectState,
@@ -86,7 +88,7 @@ export const useVideoPlayer = ({
duration: fullMatchDuration,
loadedProgress,
playedProgress,
- playing,
+ playing: statePlaying,
ready,
seek,
seeking,
@@ -104,6 +106,10 @@ export const useVideoPlayer = ({
setPlayingProgress,
} = useMatchPageStore()
+ const { isOpen } = useTour()
+
+ const playing = Boolean(statePlaying && !isOpen)
+
/** время для сохранения статистики просмотра матча */
const timeForStatistics = useRef(0)
@@ -319,8 +325,9 @@ export const useVideoPlayer = ({
useEventListener({
callback: (e: KeyboardEvent) => {
- if (e.code === 'ArrowLeft') rewindBackward()
- else if (e.code === 'ArrowRight') rewindForward()
+ if (isOpen) return
+ if (e.code === KEYBOARD_KEYS.ArrowLeft) rewindBackward()
+ else if (e.code === KEYBOARD_KEYS.ArrowRight) rewindForward()
},
event: 'keydown',
})
diff --git a/src/helpers/bodyScroll/index.tsx b/src/helpers/bodyScroll/index.tsx
new file mode 100644
index 00000000..e8b56014
--- /dev/null
+++ b/src/helpers/bodyScroll/index.tsx
@@ -0,0 +1,267 @@
+/* eslint-disable no-param-reassign */
+import { isIOS } from 'config'
+
+type BodyScrollOptions = {
+ allowTouchMove?: ((el: EventTarget) => boolean) | undefined,
+ reserveScrollBarGap?: boolean | undefined,
+}
+
+let previousBodyPosition: {
+ left: string,
+ position: string,
+ right: string,
+ top: string,
+} | undefined
+
+let locks: Array<{
+ options: BodyScrollOptions,
+ targetElement: HTMLElement | Element,
+}> = []
+
+let initialClientY = -1
+let documentListenerAdded = false
+let previousBodyOverflowSetting: string | undefined
+let previousBodyPaddingRight: string | undefined
+
+// returns true if `el` should be allowed to receive touchmove events.
+const allowTouchMove = (el: EventTarget | null) => locks.some((lock) => {
+ if (el && lock.options.allowTouchMove && lock.options.allowTouchMove(el)) {
+ return true
+ }
+
+ return false
+})
+
+const preventDefault = (rawEvent?: Event) => {
+ const e = rawEvent || window.event
+
+ // For the case whereby consumers adds a touchmove event listener to document.
+ // Recall that we do document.addEventListener('touchmove', preventDefault, { passive: false })
+ // in disableBodyScroll - so if we provide this opportunity to allowTouchMove, then
+ // the touchmove event on document will break.
+ // @ts-expect-error
+ if (allowTouchMove(e.target)) {
+ return true
+ }
+
+ /** Do not prevent if the event has more than one touch
+ * (usually meaning this is a multi touch gesture like pinch to zoom).
+ * */
+ // @ts-expect-error
+ if (e.touches.length > 1) return true
+ // @ts-expect-error
+ if (e.preventDefault) e.preventDefault()
+
+ return false
+}
+
+const setPositionFixed = () => window.requestAnimationFrame(() => {
+ // If previousBodyPosition is already set, don't set it again.
+ if (previousBodyPosition === undefined) {
+ previousBodyPosition = {
+ left: document.body.style.left,
+ position: document.body.style.position,
+ right: document.body.style.right,
+ top: document.body.style.top,
+ }
+
+ // Update the dom inside an animation frame
+ const {
+ innerHeight,
+ scrollX,
+ scrollY,
+ } = window
+ document.body.style.position = 'fixed'
+ // @ts-expect-error
+ document.body.style.top = -scrollY
+ // @ts-expect-error
+ document.body.style.left = -scrollX
+ // @ts-expect-error
+ document.body.style.right = 0
+
+ setTimeout(() => window.requestAnimationFrame(() => {
+ // Attempt to check if the bottom bar appeared due to the position change
+ const bottomBarHeight = innerHeight - window.innerHeight
+ if (bottomBarHeight && scrollY >= innerHeight) {
+ // Move the content further up so that the bottom bar doesn't hide it
+ // @ts-expect-error
+ document.body.style.top = -(scrollY + bottomBarHeight)
+ }
+ }), 300)
+ }
+})
+
+const setOverflowHidden = (options?: BodyScrollOptions) => {
+ // If previousBodyPaddingRight is already set, don't set it again.
+ if (previousBodyPaddingRight === undefined) {
+ const reserveScrollBarGap = !!options && options.reserveScrollBarGap === true
+ const scrollBarGap = window.innerWidth - document.documentElement.clientWidth
+
+ if (reserveScrollBarGap && scrollBarGap > 0) {
+ const computedBodyPaddingRight = parseInt(window.getComputedStyle(document.body).getPropertyValue('padding-right'), 10)
+ previousBodyPaddingRight = document.body.style.paddingRight
+ document.body.style.paddingRight = `${computedBodyPaddingRight + scrollBarGap}px`
+ }
+ }
+
+ // If previousBodyOverflowSetting is already set, don't set it again.
+ if (previousBodyOverflowSetting === undefined) {
+ previousBodyOverflowSetting = document.body.style.overflow
+ document.body.style.overflow = 'hidden'
+ }
+}
+
+// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollHeight#Problems_and_solutions
+const isTargetElementTotallyScrolled = (targetElement: HTMLElement | Element | null) => (
+ targetElement
+ ? targetElement.scrollHeight - targetElement.scrollTop <= targetElement.clientHeight
+ : false
+)
+
+const handleScroll = (event: TouchEvent, targetElement: HTMLElement | Element | null) => {
+ const clientY = event.targetTouches[0].clientY - initialClientY
+
+ if (allowTouchMove(event.target)) {
+ return false
+ }
+
+ if (targetElement && targetElement.scrollTop === 0 && clientY > 0) {
+ // element is at the top of its scroll.
+ return preventDefault(event)
+ }
+
+ if (isTargetElementTotallyScrolled(targetElement) && clientY < 0) {
+ // element is at the bottom of its scroll.
+ return preventDefault(event)
+ }
+
+ event.stopPropagation()
+ return true
+}
+
+const restorePositionSetting = () => {
+ if (previousBodyPosition !== undefined) {
+ // Convert the position from "px" to Int
+ const y = -parseInt(document.body.style.top, 10)
+ const x = -parseInt(document.body.style.left, 10)
+
+ // Restore styles
+ document.body.style.position = previousBodyPosition.position
+ document.body.style.top = previousBodyPosition.top
+ document.body.style.left = previousBodyPosition.left
+ document.body.style.right = previousBodyPosition.right
+
+ // Restore scroll
+ window.scrollTo(x, y)
+
+ previousBodyPosition = undefined
+ }
+}
+
+const restoreOverflowSetting = () => {
+ if (previousBodyPaddingRight !== undefined) {
+ document.body.style.paddingRight = previousBodyPaddingRight
+
+ // Restore previousBodyPaddingRight to undefined so setOverflowHidden knows it
+ // can be set again.
+ previousBodyPaddingRight = undefined
+ }
+
+ if (previousBodyOverflowSetting !== undefined) {
+ document.body.style.overflow = previousBodyOverflowSetting
+
+ // Restore previousBodyOverflowSetting to undefined
+ // so setOverflowHidden knows it can be set again.
+ previousBodyOverflowSetting = undefined
+ }
+}
+
+// Enables body scroll locking without breaking scrolling of a target element
+export const enableBodyScroll = (targetElement: HTMLElement | Element) => {
+ if (!targetElement) {
+ // eslint-disable-next-line no-console
+ console.error('enableBodyScroll unsuccessful - targetElement must be provided when calling enableBodyScroll on IOS devices.')
+ return
+ }
+
+ locks = locks.filter((lock) => lock.targetElement !== targetElement)
+
+ if (isIOS) {
+ // @ts-expect-error
+ targetElement.ontouchstart = null
+ // @ts-expect-error
+ targetElement.ontouchmove = null
+
+ if (documentListenerAdded && locks.length === 0) {
+ document.removeEventListener(
+ 'touchmove',
+ preventDefault,
+ // @ts-expect-error
+ { passive: false },
+ )
+ documentListenerAdded = false
+ }
+ }
+
+ if (isIOS) {
+ restorePositionSetting()
+ } else {
+ restoreOverflowSetting()
+ }
+}
+
+// Disable body scroll locking
+export const disableBodyScroll = (
+ targetElement: HTMLElement | Element, options?: BodyScrollOptions,
+) => {
+ // targetElement must be provided
+ if (!targetElement) {
+ // eslint-disable-next-line no-console
+ console.error('disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.')
+ return
+ }
+
+ // disableBodyScroll must not have been called on this targetElement before
+ if (locks.some((lock) => lock.targetElement === targetElement)) {
+ return
+ }
+
+ const lock = {
+ options: options || {},
+ targetElement,
+ }
+
+ locks = [...locks, lock]
+
+ if (isIOS) {
+ setPositionFixed()
+ } else {
+ setOverflowHidden(options)
+ }
+
+ if (isIOS) {
+ // @ts-expect-error
+ targetElement.ontouchstart = (event) => {
+ if (event.targetTouches.length === 1) {
+ // detect single touch.
+ initialClientY = event.targetTouches[0].clientY
+ }
+ }
+ // @ts-expect-error
+ targetElement.ontouchmove = (event) => {
+ if (event.targetTouches.length === 1) {
+ // detect single touch.
+ handleScroll(event, targetElement)
+ }
+ }
+
+ if (!documentListenerAdded) {
+ document.addEventListener(
+ 'touchmove',
+ preventDefault,
+ { passive: false },
+ )
+ documentListenerAdded = true
+ }
+ }
+}
diff --git a/src/helpers/index.tsx b/src/helpers/index.tsx
index ce271b5d..2bfc875e 100644
--- a/src/helpers/index.tsx
+++ b/src/helpers/index.tsx
@@ -14,3 +14,4 @@ export * from './getTeamAbbr'
export * from './cookie'
export * from './isMatchPage'
export * from './languageUrlParam'
+export * from './bodyScroll'
diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx
index 6e2f4ec7..ab04bf68 100644
--- a/src/hooks/index.tsx
+++ b/src/hooks/index.tsx
@@ -7,3 +7,4 @@ export * from './useObjectState'
export * from './usePageParams'
export * from './useTooltip'
export * from './useModalRoot'
+export * from './usePageLogger'
diff --git a/src/hooks/useEventListener.tsx b/src/hooks/useEventListener.tsx
index 3d0e4680..e35a859c 100644
--- a/src/hooks/useEventListener.tsx
+++ b/src/hooks/useEventListener.tsx
@@ -9,6 +9,7 @@ type Target = RefObject | HTMLElement | Window
type Args = {
callback: (e: EventMap[E]) => void,
event: E,
+ options?: Parameters<(HTMLElement | Window)['addEventListener']>[2],
target?: Target,
}
@@ -19,6 +20,7 @@ type Args = {
export const useEventListener = ({
callback,
event,
+ options,
target = window,
}: Args) => {
const callbackRef = useRef(callback)
@@ -39,9 +41,17 @@ export const useEventListener = ({
callbackRef.current(e)
}
- windowOrElement?.addEventListener(event, listener)
+ windowOrElement?.addEventListener(
+ event,
+ listener,
+ options,
+ )
return () => {
- windowOrElement?.removeEventListener(event, listener)
+ windowOrElement?.removeEventListener(
+ event,
+ listener,
+ options,
+ )
}
- }, [event, target])
+ }, [event, target, options])
}
diff --git a/src/requests/getMatchParticipants.tsx b/src/requests/getMatchParticipants.tsx
index edd0e7d1..1be5bd60 100644
--- a/src/requests/getMatchParticipants.tsx
+++ b/src/requests/getMatchParticipants.tsx
@@ -20,7 +20,7 @@ export type Player = {
lastname_national: string | null,
lastname_rus: string,
national_f_team: number | null,
- national_shirt_num: number,
+ national_shirt_num: number | null,
nickname_eng: string | null,
nickname_rus: string | null,
num: number | null,
diff --git a/src/requests/getPlayersStats.tsx b/src/requests/getPlayersStats.tsx
index af898d49..3980c694 100644
--- a/src/requests/getPlayersStats.tsx
+++ b/src/requests/getPlayersStats.tsx
@@ -12,7 +12,7 @@ export type PlayerParam = {
lexica_short: number | null,
markers: Array | null,
name_en: string,
- name_ru: string,
+ name_ru: string | null,
val: number | null,
}
diff --git a/src/requests/getTeamsStats.tsx b/src/requests/getTeamsStats.tsx
index ecf7db2f..ec8fd690 100644
--- a/src/requests/getTeamsStats.tsx
+++ b/src/requests/getTeamsStats.tsx
@@ -9,9 +9,9 @@ export type Param = {
data_type: string,
id: number,
lexic: number,
- markers: Array,
+ markers: Array | null,
name_en: string,
- name_ru: string,
+ name_ru: string | null,
val: number | null,
}