From 1cc39c24e2e4522a28d14bd180b0ae0fc217fbca Mon Sep 17 00:00:00 2001 From: Rakov Roman Date: Fri, 8 Jul 2022 17:50:16 +0300 Subject: [PATCH] fix(#2556): display of tabs on match or event page --- src/config/lexics/indexLexics.tsx | 1 + src/features/Common/Tabs/index.tsx | 22 +++--- .../CardFrontside/MatchCardMobile/index.tsx | 6 +- .../MatchCard/CardFrontside/index.tsx | 6 +- .../components/FinishedMatch/index.tsx | 9 ++- .../MatchPage/components/LiveMatch/index.tsx | 9 ++- .../MatchPage/hooks/useMatchProfile.tsx | 4 + .../MatchPage/hooks/useTournamentData.tsx | 55 ++++++++++++++ src/features/MatchPage/index.tsx | 3 + src/features/MatchPage/styled.tsx | 2 +- src/features/MatchPage/types.tsx | 7 ++ .../components/MatchPlaylists/index.tsx | 45 ++++++----- .../components/VideoDate/VideoDate.tsx | 73 ++++++++++++++++++ .../TabVideo/components/VideoDate/styled.tsx | 42 +++++++++++ .../components/TabVideo/index.tsx | 59 +++++++++++++++ .../components/TabVideo/styled.tsx | 7 ++ src/features/MatchSidePlaylists/config.tsx | 1 + src/features/MatchSidePlaylists/hooks.tsx | 74 ++++++++++++++++++- src/features/MatchSidePlaylists/index.tsx | 58 +++++++++++---- src/features/Search/index.tsx | 2 +- src/requests/getMatches/types.tsx | 2 + 21 files changed, 434 insertions(+), 53 deletions(-) create mode 100644 src/features/MatchPage/hooks/useTournamentData.tsx create mode 100644 src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/VideoDate.tsx create mode 100644 src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx create mode 100644 src/features/MatchSidePlaylists/components/TabVideo/index.tsx create mode 100644 src/features/MatchSidePlaylists/components/TabVideo/styled.tsx diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx index 2b990cec..06dad8e7 100644 --- a/src/config/lexics/indexLexics.tsx +++ b/src/config/lexics/indexLexics.tsx @@ -29,6 +29,7 @@ const matchPopupLexics = { selected_player_actions: 13413, started_streaming_at: 16042, streamed_live_on: 16043, + video: 1017, views: 13440, watch: 818, watch_from: 13022, diff --git a/src/features/Common/Tabs/index.tsx b/src/features/Common/Tabs/index.tsx index b36e96e7..b9969ffd 100644 --- a/src/features/Common/Tabs/index.tsx +++ b/src/features/Common/Tabs/index.tsx @@ -15,6 +15,7 @@ export const Tab = styled.button.attrs(({ selected }: TabProps) => ({ border: none; outline: none; height: 100%; + width: 100%; padding-top: 0.38rem; padding-bottom: 0.27rem; font-weight: 600; @@ -73,25 +74,28 @@ export const Tab = styled.button.attrs(({ selected }: TabProps) => ({ } ` : ''}; -` -type TabGroupProps = { - buttons: number, -} + &:only-child { + background: transparent; + color: #FFFFFF; + cursor: default; + } +` export const TabsGroup = styled.div.attrs({ role: 'group', -})` +})` width: 100%; height: 100%; - box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); overflow: hidden; - border: 1px solid #FFFFFF; border-radius: 2px; + display: flex; - ${Tab} { - width: ${({ buttons }) => 100 / buttons}%; + > :not(:only-child) { + border: 1px solid #FFFFFF; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); } + ${isMobileDevice ? css` height: 28px; diff --git a/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx b/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx index 421e8702..a617ce77 100644 --- a/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx +++ b/src/features/MatchCard/CardFrontside/MatchCardMobile/index.tsx @@ -1,5 +1,5 @@ import type { KeyboardEvent } from 'react' -import { useRouteMatch } from 'react-router' +import { useLocation, useRouteMatch } from 'react-router' import getUnixTime from 'date-fns/getUnixTime' @@ -49,6 +49,7 @@ export const CardFrontsideMobile = ({ onClick, onKeyPress, }: Props) => { + const location = useLocation() const { access, date, @@ -63,6 +64,7 @@ export const CardFrontsideMobile = ({ tournament, } = match const isHomePage = useRouteMatch(PAGES.home)?.isExact + const isMatchPage = location.pathname.includes(PAGES.match) const tournamentName = useName(tournament) const { isInFavorites } = useUserFavoritesStore() const { isScoreHidden } = useMatchSwitchesStore() @@ -106,7 +108,7 @@ export const CardFrontsideMobile = ({ )} - {isHomePage ? null : formattedDate} + {isHomePage || isMatchPage ? null : formattedDate} {live && ( diff --git a/src/features/MatchCard/CardFrontside/index.tsx b/src/features/MatchCard/CardFrontside/index.tsx index 214aecc7..c3eb64f8 100644 --- a/src/features/MatchCard/CardFrontside/index.tsx +++ b/src/features/MatchCard/CardFrontside/index.tsx @@ -1,5 +1,5 @@ import type { KeyboardEvent } from 'react' -import { useRouteMatch } from 'react-router' +import { useLocation, useRouteMatch } from 'react-router' import getUnixTime from 'date-fns/getUnixTime' @@ -51,6 +51,7 @@ export const CardFrontside = ({ onClick, onKeyPress, }: Props) => { + const location = useLocation() const { access, date, @@ -65,6 +66,7 @@ export const CardFrontside = ({ tournament, } = match const isHomePage = useRouteMatch(PAGES.home)?.isExact + const isMatchPage = location.pathname.includes(PAGES.match) const tournamentName = useName(tournament) const { isInFavorites } = useUserFavoritesStore() const { isScoreHidden } = useMatchSwitchesStore() @@ -119,7 +121,7 @@ export const CardFrontside = ({ {access === MatchAccess.CanBuyMatch && } - {isHomePage ? null : formattedDate} + {isHomePage || isMatchPage ? null : formattedDate} {live && ( diff --git a/src/features/MatchPage/components/FinishedMatch/index.tsx b/src/features/MatchPage/components/FinishedMatch/index.tsx index 2fae92ce..e88b90f0 100644 --- a/src/features/MatchPage/components/FinishedMatch/index.tsx +++ b/src/features/MatchPage/components/FinishedMatch/index.tsx @@ -13,13 +13,19 @@ import { SettingsPopup } from '../SettingsPopup' import { useFinishedMatch } from './hooks' import { Container } from '../../styled' import { Modal } from './styled' +import { TournamentData } from '../../types' type Props = { events: Events, profile: MatchInfo, + tournamentData: TournamentData, } -export const FinishedMatch = ({ events, profile }: Props) => { +export const FinishedMatch = ({ + events, + profile, + tournamentData, +}: Props) => { const { chapters, closeSettingsPopup, @@ -61,6 +67,7 @@ export const FinishedMatch = ({ events, profile }: Props) => { selectedPlaylist={selectedPlaylist} onSelect={onPlaylistSelect} profile={profile} + tournamentData={tournamentData} /> ) diff --git a/src/features/MatchPage/components/LiveMatch/index.tsx b/src/features/MatchPage/components/LiveMatch/index.tsx index c2db8b86..00b18e7f 100644 --- a/src/features/MatchPage/components/LiveMatch/index.tsx +++ b/src/features/MatchPage/components/LiveMatch/index.tsx @@ -10,13 +10,19 @@ import { MatchSidePlaylists } from 'features/MatchSidePlaylists' import { Container } from '../../styled' import { useLiveMatch } from './hooks' +import { TournamentData } from '../../types' type Props = { events: Events, profile: MatchInfo, + tournamentData: TournamentData, } -export const LiveMatch = ({ events, profile }: Props) => { +export const LiveMatch = ({ + events, + profile, + tournamentData, +}: Props) => { const { matchPlaylists, onPlayerProgressChange, @@ -42,6 +48,7 @@ export const LiveMatch = ({ events, profile }: Props) => { { [matchProfile, matchPlaylists], ) + const { tournamentData } = useTournamentData(matchProfile?.tournament.id ?? null) + const isStarted = useMemo(() => ( profile?.date ? parseDate(profile.date) < new Date() @@ -68,5 +71,6 @@ export const useMatchProfile = () => { events, isStarted, profile, + tournamentData, } } diff --git a/src/features/MatchPage/hooks/useTournamentData.tsx b/src/features/MatchPage/hooks/useTournamentData.tsx new file mode 100644 index 00000000..2d1c9c0c --- /dev/null +++ b/src/features/MatchPage/hooks/useTournamentData.tsx @@ -0,0 +1,55 @@ +import { + useEffect, + useMemo, + useState, +} from 'react' + +import { format } from 'date-fns' +import sortedUniq from 'lodash/sortedUniq' +import isNull from 'lodash/isNull' +import sortBy from 'lodash/sortBy' + +import type { Match } from 'features/Matches' +import { prepareMatches } from 'features/Matches/helpers/prepareMatches' + +import { getTournamentMatches } from 'requests' + +import { parseDate } from 'helpers/parseDate' + +import { usePageParams } from 'hooks/usePageParams' + +import { TournamentData } from '../types' + +export const useTournamentData = (tournamentId: number | null) => { + const { sportType } = usePageParams() + + const [tournamentMatches, setTournamentMatches] = useState>([]) + const [matchDates, setMatchDates] = useState>([]) + + useEffect(() => { + if (!isNull(tournamentId)) { + (async () => { + const matchesBySection = await getTournamentMatches({ + limit: 1000, + offset: 0, + sportType, + tournamentId, + }) + + const matchDateList = matchesBySection.broadcast.map((match) => ( + parseDate(match.date) + )).sort((a, b) => a.getTime() - b.getTime()) + + setMatchDates(sortedUniq(matchDateList.map((date) => format(date, 'yyyy-MM-dd')))) + setTournamentMatches(sortBy(prepareMatches(matchesBySection.broadcast), ['date'])) + })() + } + }, [tournamentId, sportType]) + + const tournamentData: TournamentData = useMemo(() => ({ + matchDates, + matches: tournamentMatches, + }), [matchDates, tournamentMatches]) + + return { tournamentData } +} diff --git a/src/features/MatchPage/index.tsx b/src/features/MatchPage/index.tsx index 064d8991..17be22dd 100644 --- a/src/features/MatchPage/index.tsx +++ b/src/features/MatchPage/index.tsx @@ -31,6 +31,7 @@ const MatchPage = () => { events, isStarted, profile, + tournamentData, } = useMatchProfile() const { @@ -81,12 +82,14 @@ const MatchPage = () => { )} {playFromScout && ( )} diff --git a/src/features/MatchPage/styled.tsx b/src/features/MatchPage/styled.tsx index 8c5bf2a1..58be9c72 100644 --- a/src/features/MatchPage/styled.tsx +++ b/src/features/MatchPage/styled.tsx @@ -50,7 +50,7 @@ export const Container = styled.div` margin-right: 0; padding: 0; margin-bottom: 15px; - min-height: 32vh; + min-height: min-content; @media screen and (orientation: landscape){ display: block; diff --git a/src/features/MatchPage/types.tsx b/src/features/MatchPage/types.tsx index 51d202cf..8a5b7892 100644 --- a/src/features/MatchPage/types.tsx +++ b/src/features/MatchPage/types.tsx @@ -1,5 +1,7 @@ import type { Lexics, Episodes } from 'requests' +import type { Match } from 'features/Matches' + import type { MatchPlaylistIds } from './helpers/buildPlaylists' export enum PlaylistTypes { @@ -64,3 +66,8 @@ export type Playlists = { score2: number, state?: string, } + +export type TournamentData = { + matchDates: Array, + matches: Array, +} diff --git a/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx b/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx index a1f009c8..306d54f7 100644 --- a/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx +++ b/src/features/MatchSidePlaylists/components/MatchPlaylists/index.tsx @@ -1,5 +1,8 @@ +import { useMemo } from 'react' + import styled, { css } from 'styled-components/macro' +import filter from 'lodash/filter' import isEmpty from 'lodash/isEmpty' import map from 'lodash/map' @@ -36,21 +39,27 @@ export const MatchPlaylists = ({ onSelect, playlists, selectedMathPlaylist, -}: Props) => ( - - { - map(playlists, (playlist) => ( - - onSelect?.(playlist)} - > - - - - )) - } - -) +}: Props) => { + const filteredPlayListByDuration = useMemo(() => ( + filter(playlists, (playlist) => !!playlist.duration) + ), [playlists]) + + return ( + + { + map(filteredPlayListByDuration, (playlist) => ( + + onSelect?.(playlist)} + > + + + + )) + } + + ) +} diff --git a/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/VideoDate.tsx b/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/VideoDate.tsx new file mode 100644 index 00000000..7ca9f140 --- /dev/null +++ b/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/VideoDate.tsx @@ -0,0 +1,73 @@ +import { + useCallback, + useMemo, +} from 'react' + +import { format } from 'date-fns' + +import { WeekDay, Wrapper } from './styled' + +export type Props = { + matchDates: Array, + onDateClick: (date: string) => void, + selectedDate: string, +} + +export const VideoDate = (props: Props) => { + const { + matchDates, + onDateClick, + selectedDate, + } = props + + const selectedDateIndex = useMemo(() => ( + matchDates.findIndex((date) => date === selectedDate) + ), [matchDates, selectedDate]) + + const currentDay = useMemo(() => ( + matchDates.length ? matchDates[selectedDateIndex] : null + ), [matchDates, selectedDateIndex]) + + const previousDay = useMemo(() => { + if (selectedDateIndex !== 0) { + return matchDates[selectedDateIndex - 1] + } + return null + }, [matchDates, selectedDateIndex]) + + const nextDay = useMemo(() => { + if (selectedDateIndex !== matchDates.length - 1) { + return matchDates[selectedDateIndex + 1] + } + return null + }, [matchDates, selectedDateIndex]) + + const onDayClick = (date: string) => { + onDateClick?.(date) + } + + const formatDate = useCallback((date: string) => format(Date.parse(date), 'MMM dd, EE'), []) + + return ( + + {previousDay && ( + onDayClick(previousDay)} + >{formatDate(previousDay)} + + )} + {currentDay && ( + {formatDate(currentDay)} + + )} + {nextDay && ( + onDayClick(nextDay)} + >{formatDate(nextDay)} + + )} + + ) +} diff --git a/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx b/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx new file mode 100644 index 00000000..eae67980 --- /dev/null +++ b/src/features/MatchSidePlaylists/components/TabVideo/components/VideoDate/styled.tsx @@ -0,0 +1,42 @@ +import styled, { css } from 'styled-components' + +export const Wrapper = styled.div` + color: #FFFFFF; + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 10px; + + > :not(:last-child) { + margin-right: 20px; + } +` + +export const WeekDay = styled.div.attrs(() => ({ + 'aria-hidden': true, +}))<{isActive?: boolean}>` + position: relative; + color: rgba(255, 255, 255, 0.5); + font-size: 12px; + white-space: nowrap; + padding: 5px; + cursor: pointer; + + ${({ isActive }) => ( + isActive + ? css` + color: #FFFFFF; + cursor: default; + + :after { + position: absolute; + bottom: 0; + left: 0; + content: ''; + width: 100%; + height: 2px; + background-color: #FFFFFF; + } + ` + : '')} +` diff --git a/src/features/MatchSidePlaylists/components/TabVideo/index.tsx b/src/features/MatchSidePlaylists/components/TabVideo/index.tsx new file mode 100644 index 00000000..43eed331 --- /dev/null +++ b/src/features/MatchSidePlaylists/components/TabVideo/index.tsx @@ -0,0 +1,59 @@ +import { + Fragment, + useMemo, + useState, +} from 'react' + +import { format } from 'date-fns' +import map from 'lodash/map' + +import { MatchCard } from 'features/MatchCard' +import { TournamentData } from 'features/MatchPage/types' + +import { MatchInfo } from 'requests' + +import { parseDate } from 'helpers/parseDate' + +import { usePageParams } from 'hooks/usePageParams' + +import { VideoDate } from './components/VideoDate/VideoDate' +import { MatchesWrapper } from './styled' + +type Props = { + profile: MatchInfo, + tournamentData: TournamentData, +} + +export const TabVideo = ({ + profile, + tournamentData, +}: Props) => { + const { profileId } = usePageParams() + const [selectedDate, setSelectedDate] = useState(format(parseDate(profile?.date!), 'yyyy-MM-dd')) + + const matches = useMemo(() => ( + tournamentData.matches.filter((match) => ( + format(match.date, 'yyyy-MM-dd') === selectedDate && match.id !== profileId + )) + ), [profileId, selectedDate, tournamentData.matches]) + + return ( + + + + { + map(matches, (match) => ( + + )) + } + + + ) +} diff --git a/src/features/MatchSidePlaylists/components/TabVideo/styled.tsx b/src/features/MatchSidePlaylists/components/TabVideo/styled.tsx new file mode 100644 index 00000000..7cb19d4c --- /dev/null +++ b/src/features/MatchSidePlaylists/components/TabVideo/styled.tsx @@ -0,0 +1,7 @@ +import styled from 'styled-components' + +export const MatchesWrapper = styled.div` + > :not(:last-child) { + margin-bottom: 15px; + } +` diff --git a/src/features/MatchSidePlaylists/config.tsx b/src/features/MatchSidePlaylists/config.tsx index 63c32547..9ab22db3 100644 --- a/src/features/MatchSidePlaylists/config.tsx +++ b/src/features/MatchSidePlaylists/config.tsx @@ -1,4 +1,5 @@ export enum Tabs { WATCH, EVENTS, + VIDEO } diff --git a/src/features/MatchSidePlaylists/hooks.tsx b/src/features/MatchSidePlaylists/hooks.tsx index 9d766549..aff2f7e9 100644 --- a/src/features/MatchSidePlaylists/hooks.tsx +++ b/src/features/MatchSidePlaylists/hooks.tsx @@ -1,11 +1,79 @@ -import { useState } from 'react' +import { + useEffect, + useMemo, + useState, +} from 'react' + +import reduce from 'lodash/reduce' + +import { Playlists, TournamentData } from 'features/MatchPage/types' + +import type { Events } from 'requests' import { Tabs } from './config' -export const useMatchSidePlaylists = () => { - const [selectedTab, setSelectedTab] = useState(Tabs.WATCH) +export type Props = { + events: Events, + playlists: Playlists, + tournamentData: TournamentData, +} + +export const useMatchSidePlaylists = (props: Props) => { + const { + events, + playlists, + tournamentData, + } = props + + const [selectedTab, setSelectedTab] = useState(Tabs.VIDEO) + + const isWatchTabVisible = useMemo(() => { + const playListFilter = reduce( + playlists.match, + (acc, item) => { + let result = acc + if (item.duration) result++ + return result + }, + 0, + ) + return playListFilter > 1 + }, [playlists]) + + const isEventTabVisible = useMemo(() => { + if (events.length > 0) { + setSelectedTab(Tabs.EVENTS) + return true + } + return false + }, [events]) + + const isVideoTabVisible = useMemo(() => { + if (tournamentData.matches.length > 0) { + setSelectedTab(Tabs.EVENTS) + return true + } + return false + }, [tournamentData]) + + useEffect(() => { + switch (true) { + case isWatchTabVisible: + setSelectedTab(Tabs.WATCH) + break + case isEventTabVisible: + setSelectedTab(Tabs.EVENTS) + break + case isVideoTabVisible: + setSelectedTab(Tabs.VIDEO) + break + } + }, [isEventTabVisible, isVideoTabVisible, isWatchTabVisible]) return { + isEventTabVisible, + isVideoTabVisible, + isWatchTabVisible, onTabClick: setSelectedTab, selectedTab, } diff --git a/src/features/MatchSidePlaylists/index.tsx b/src/features/MatchSidePlaylists/index.tsx index e6fb02fe..2565949f 100644 --- a/src/features/MatchSidePlaylists/index.tsx +++ b/src/features/MatchSidePlaylists/index.tsx @@ -1,12 +1,17 @@ import type { Events, MatchInfo } from 'requests' -import type { PlaylistOption, Playlists } from 'features/MatchPage/types' +import type { + PlaylistOption, + Playlists, + TournamentData, +} from 'features/MatchPage/types' import { Tab, TabsGroup } from 'features/Common' import { T9n } from 'features/T9n' import { Tabs } from './config' import { TabEvents } from './components/TabEvents' import { TabWatch } from './components/TabWatch' +import { TabVideo } from './components/TabVideo' import { useMatchSidePlaylists } from './hooks' import { Wrapper, @@ -17,6 +22,7 @@ import { const tabPanes = { [Tabs.WATCH]: TabWatch, [Tabs.EVENTS]: TabEvents, + [Tabs.VIDEO]: TabVideo, } type Props = { @@ -25,6 +31,7 @@ type Props = { playlists: Playlists, profile: MatchInfo, selectedPlaylist?: PlaylistOption, + tournamentData: TournamentData, } export const MatchSidePlaylists = ({ @@ -33,34 +40,55 @@ export const MatchSidePlaylists = ({ playlists, profile, selectedPlaylist, + tournamentData, }: Props) => { const { + isEventTabVisible, + isVideoTabVisible, + isWatchTabVisible, onTabClick, selectedTab, - } = useMatchSidePlaylists() + } = useMatchSidePlaylists({ + events, + playlists, + tournamentData, + }) const TabPane = tabPanes[selectedTab] return ( - - onTabClick(Tabs.WATCH)} - > - - - onTabClick(Tabs.EVENTS)} - > - - + + {isWatchTabVisible ? ( + onTabClick(Tabs.WATCH)} + > + + + ) : null} + {isEventTabVisible ? ( + onTabClick(Tabs.EVENTS)} + > + + + ) : null} + {isVideoTabVisible ? ( + onTabClick(Tabs.VIDEO)} + > + + + ) : null} { {showResults && ( - +