feat(#ott-2710): add matches filters

keep-around/3d3716c8eecddecc93aa1855ad3f52b67b2f7b4b
alexnofoget 3 years ago committed by Andrei Dekterev
parent 349b96179e
commit 3d3716c8ee
  1. 58850
      package-lock.json
  2. 3
      public/images/closeBlack.svg
  3. 3
      public/images/closeWhite.svg
  4. 2
      src/components/SelectFilter/styled.tsx
  5. 12
      src/config/lexics/indexLexics.tsx
  6. 65
      src/features/HeaderFilters/store/hooks/index.tsx
  7. 185
      src/features/HeaderFilters/store/hooks/useMatchFilters.tsx
  8. 8
      src/features/HeaderMobile/index.tsx
  9. 41
      src/features/HomePage/components/ClearFiltersPopup/index.tsx
  10. 113
      src/features/HomePage/components/ClearFiltersPopup/styled.tsx
  11. 160
      src/features/HomePage/components/Dropdown/Dropdown.tsx
  12. 62
      src/features/HomePage/components/Dropdown/helpers.tsx
  13. 215
      src/features/HomePage/components/Dropdown/styled.tsx
  14. 24
      src/features/HomePage/components/Dropdown/types.tsx
  15. 47
      src/features/HomePage/components/HeaderFilters/index.tsx
  16. 3
      src/features/HomePage/components/HeaderFilters/styled.tsx
  17. 14
      src/features/HomePage/components/MatchesFilters/helpers.tsx
  18. 110
      src/features/HomePage/components/MatchesFilters/index.tsx
  19. 169
      src/features/HomePage/components/MatchesFilters/styled.tsx
  20. 87
      src/features/HomePage/components/MobileMatchesFilters/index.tsx
  21. 71
      src/features/HomePage/components/MobileMatchesFilters/styled.tsx
  22. 15
      src/features/HomePage/hooks.tsx
  23. 3
      src/features/UserFavorites/styled.tsx
  24. 29
      src/requests/getMatches/getHomeMatches.tsx
  25. 13
      src/requests/getMatches/types.tsx

58850
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,3 @@
<svg width="10" height="11" viewBox="0 0 10 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.14144 1.19127C1.87731 0.92713 1.44906 0.92713 1.18492 1.19127C0.920782 1.4554 0.920782 1.88366 1.18492 2.14779L4.53696 5.49984L1.18492 8.85188C0.920782 9.11602 0.920782 9.54427 1.18492 9.8084C1.44906 10.0725 1.87731 10.0725 2.14144 9.8084L5.49349 6.45636L8.84553 9.8084C9.10967 10.0725 9.53792 10.0725 9.80206 9.8084C10.0662 9.54427 10.0662 9.11602 9.80206 8.85188L6.45001 5.49984L9.80206 2.14779C10.0662 1.88366 10.0662 1.4554 9.80206 1.19127C9.53792 0.92713 9.10967 0.92713 8.84553 1.19127L5.49349 4.54331L2.14144 1.19127Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 696 B

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.16171 0.691267C0.897571 0.42713 0.46932 0.42713 0.205183 0.691267C-0.0589542 0.955404 -0.0589542 1.38366 0.205183 1.64779L3.55723 4.99984L0.205183 8.35188C-0.0589542 8.61602 -0.0589542 9.04427 0.205183 9.3084C0.46932 9.57254 0.897571 9.57254 1.16171 9.3084L4.51375 5.95636L7.8658 9.3084C8.12993 9.57254 8.55818 9.57254 8.82232 9.3084C9.08646 9.04427 9.08646 8.61602 8.82232 8.35188L5.47028 4.99984L8.82232 1.64779C9.08646 1.38366 9.08646 0.955404 8.82232 0.691267C8.55818 0.42713 8.12993 0.42713 7.8658 0.691267L4.51375 4.04331L1.16171 0.691267Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 717 B

@ -7,7 +7,7 @@ export const ScSelectFilter = styled.div`
display: flex;
color: white;
font-size: 14px;
width: 30%;
width: auto;
text-transform: uppercase;
font-weight: 700;
align-items: center;

@ -110,6 +110,17 @@ const newDevicePopup = {
ok: 724,
}
const matchesFilter = {
arena: 19803,
clear_filters: 4302,
clear_popup_message: 19812,
division: 19806,
gender: 19801,
main_team: 19804,
round: 19805,
youth_age: 19802,
}
export const indexLexics = {
add_to_favorites: 14967,
add_to_favorites_error: 12943,
@ -175,6 +186,7 @@ export const indexLexics = {
watch_from_last_pause: 13022,
watch_now: 13020,
...matchesFilter,
...filterPopup,
...confirmPopup,
...highlightsPageLexic,

@ -13,6 +13,7 @@ import { useQueryParamStore } from 'hooks'
import { filterKeys } from '../config'
import { isValidDate } from '../helpers/isValidDate'
import { useMatchFilters } from './useMatchFilters'
export const useFilters = () => {
const { search } = useLocation()
@ -22,7 +23,27 @@ export const useFilters = () => {
key: filterKeys.DATE,
validator: isValidDate,
})
const {
activeFilters,
changeInput,
clearAllFilters,
clearFilters,
clickCancel,
clickClearAll,
closeDropdownList,
confirmClear,
currentFilters,
filtersList,
filtersSize,
handleSetFilters,
inputValue,
isEmptyFilters,
isOpenList,
openDropdownList,
openPopup,
queryParams,
setFilters,
} = useMatchFilters(selectedDate)
const [selectedSport, setSelectedSport] = useState(['all_sports'])
const [selectedLeague, setSelectedLeague] = useState<Array<string | number>>(['all_competitions'])
const [selectedFilters, setSelectedFilters] = useState<Array<string>>([])
@ -61,14 +82,33 @@ export const useFilters = () => {
])
const store = useMemo(() => ({
activeFilters,
changeInput,
clearAllFilters,
clearFilters,
clickCancel,
clickClearAll,
closeDropdownList,
confirmClear,
currentFilters,
filtersList,
filtersSize,
handleSetFilters,
inputValue,
isEmptyFilters,
isOpenList,
isShowTournament,
isTodaySelected,
openDropdownList,
openPopup,
queryParams,
resetFilters,
selectTournament,
selectedDate,
selectedFilters,
selectedLeague,
selectedSport,
setFilters,
setIsShowTournament,
setSelectTournament,
setSelectedDate,
@ -79,16 +119,35 @@ export const useFilters = () => {
sportIds,
updateDate,
}), [
activeFilters,
clickCancel,
clickClearAll,
confirmClear,
changeInput,
clearAllFilters,
clearFilters,
closeDropdownList,
currentFilters,
filtersList,
filtersSize,
handleSetFilters,
inputValue,
isEmptyFilters,
isOpenList,
isShowTournament,
isTodaySelected,
openPopup,
openDropdownList,
queryParams,
resetFilters,
selectTournament,
selectedDate,
selectedFilters,
selectedLeague,
selectedSport,
selectTournament,
setSelectTournament,
setFilters,
setIsShowTournament,
setSelectTournament,
setSelectedDate,
setSelectedFilters,
setSelectedLeague,

@ -0,0 +1,185 @@
import {
useCallback,
useEffect,
useMemo,
useState,
MouseEvent,
ChangeEvent,
} from 'react'
import some from 'lodash/some'
import isNil from 'lodash/isNil'
import includes from 'lodash/includes'
import filter from 'lodash/filter'
import isEmpty from 'lodash/isEmpty'
import map from 'lodash/map'
import reduce from 'lodash/reduce'
import size from 'lodash/size'
import { format } from 'date-fns'
import {
getHomeMatches,
Match,
TQueryParams,
} from 'requests'
const getTimezoneOffset = (date: Date) => {
const offset = date.getTimezoneOffset()
if (offset === 0) return offset
return -(offset)
}
const getDate = (date: Date) => format(date, 'yyyy-MM-dd')
export type TActiveFilters = {
arena?: Array<TDefaultType>,
division?: Array<TDefaultType>,
gender?: Array<TDefaultType>,
main_team?: Array<TDefaultType>,
round?: Array<TDefaultType>,
youth_age?: Array<number>,
}
export type TDefaultType = {
id: number,
name_eng: string,
name_rus: string,
}
export const useMatchFilters = (selectedDate: Date) => {
const [filtersList, setFiltersList] = useState<Array<Match>>()
const [isOpenList, setOpenList] = useState<string>('')
const [activeFilters, setActiveFilters] = useState<TActiveFilters>({})
const [inputValue, setInputValue] = useState<string>('')
const [openPopup, setOpenPopup] = useState(false)
const setFilters = (filters: Array<Match>) => {
setFiltersList(filters)
}
const openDropdownList = (title: string) => () => {
setOpenList(title === isOpenList ? '' : title)
}
const closeDropdownList = () => {
setOpenList('')
}
const fetchMatches = useCallback(
(limit: number, offset: number) => getHomeMatches({
date: getDate(selectedDate),
limit,
offset,
timezoneOffset: getTimezoneOffset(selectedDate),
}),
[selectedDate],
)
useEffect(() => {
fetchMatches(1000, 0).then((resp) => setFiltersList(resp.broadcast))
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedDate])
const currentFilters = useMemo(() => {
const arr = []
if (some(filtersList, (item) => !isNil(item.arena?.id))) arr.push('arena')
if (some(filtersList, (item) => !isNil(item.round?.id))) arr.push('round')
if (some(filtersList, (item) => !isNil(item.team1?.main_team) || !isNil(item.team2?.main_team))) arr.push('main_team')
if (some(filtersList, (item) => !isNil(item.team1?.youth_age) || !isNil(item.team2?.youth_age))) arr.push('youth_age')
if (some(filtersList, (item) => !isNil(item.team1?.gender) || !isNil(item.team2?.gender))) arr.push('gender')
if (some(filtersList, (item) => !isNil(item.team1?.division?.id) || !isNil(item.team2?.division?.id))) arr.push('division')
return arr
}, [filtersList])
const handleSetFilters = (filterName: string, value: any) => {
const isFilterPresent = !isNil(activeFilters[filterName as keyof typeof activeFilters])
if (isFilterPresent) {
const isValuePresent = includes(
activeFilters[filterName as keyof typeof activeFilters], value,
)
const currentValue = isValuePresent
? filter(activeFilters[filterName as keyof typeof activeFilters], (item) => item !== value)
: [...activeFilters[filterName as keyof typeof activeFilters]!, value]
return setActiveFilters({
...activeFilters,
[filterName]: currentValue,
})
}
return setActiveFilters({
...activeFilters,
[filterName]: [value],
})
}
const clearFilters = (filterName: string) => (e: MouseEvent | ChangeEvent<HTMLInputElement>) => {
e.stopPropagation()
e.preventDefault()
return setActiveFilters({
...activeFilters,
[filterName]: [],
})
}
const clearAllFilters = (e: MouseEvent) => {
e.stopPropagation()
setActiveFilters({})
}
const isEmptyFilters = some(activeFilters, (filterItem) => isEmpty(filterItem))
|| isEmpty(activeFilters)
const queryParams: TQueryParams = useMemo(() => {
const params = {
arena: map(activeFilters.arena, (item) => item.id),
division: map(activeFilters.division, (item) => item.id),
gender: map(activeFilters.gender, (item) => item.id),
main_team: map(activeFilters.main_team, (item) => item.id),
round: map(activeFilters.round, (item) => item.id),
youth_age: activeFilters.youth_age,
}
return (params)
}, [activeFilters])
const changeInput = (e: ChangeEvent<HTMLInputElement>) => {
const { value } = e.target
setInputValue(value)
}
const clickClearAll = () => {
setOpenPopup(true)
}
const confirmClear = (e: MouseEvent) => {
setOpenPopup(false)
clearAllFilters(e)
}
const clickCancel = () => {
setOpenPopup(false)
}
const filtersSize = reduce(activeFilters,
(result,
value) => result + size(value), 0)
return {
activeFilters,
changeInput,
clearAllFilters,
clearFilters,
clickCancel,
clickClearAll,
closeDropdownList,
confirmClear,
currentFilters,
filtersList,
filtersSize,
handleSetFilters,
inputValue,
isEmptyFilters,
isOpenList,
openDropdownList,
openPopup,
queryParams,
setFilters,
}
}

@ -10,6 +10,7 @@ import { SportsFilter } from 'features/SportsFilter'
import { isSportFilterShownAtom } from 'features/HomePage/Atoms/HomePageAtoms'
import { SmartBanner } from 'components/SmartBanner'
import { MobileMathesFilters } from 'features/HomePage/components/MobileMatchesFilters'
import {
HeaderStyled,
ScoreSwitchWrapper,
@ -35,7 +36,12 @@ export const HeaderMobile = ({ isOpenDownload, setIsOpenDownload }: HeaderBanner
<HeaderMenu />
<DateFilter />
<ScSportsWrapper>
{!isLffClient && isSportFilterShown ? <SportsFilter /> : null}
{!isLffClient && isSportFilterShown ? (
<>
<SportsFilter />
<MobileMathesFilters />
</>
) : null}
<ScoreSwitchWrapper>
<ScoreSwitch />
</ScoreSwitchWrapper>

@ -0,0 +1,41 @@
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { T9n } from 'features/T9n'
import React from 'react'
import {
ButtonsContainer,
CancelButton,
ConfirmButton,
Modal,
PopupContainer,
PopupText,
PopupTitle,
} from './styled'
export const ClearFiltersPopup = () => {
const {
clickCancel,
confirmClear,
openPopup,
} = useHeaderFiltersStore()
return (
<Modal isOpen={openPopup} withCloseButton={false}>
<PopupContainer>
<PopupTitle>
<T9n t='clear_filters' />
</PopupTitle>
<PopupText>
<T9n t='clear_popup_message' />
</PopupText>
<ButtonsContainer>
<ConfirmButton onClick={confirmClear}>
<T9n t='clear' />
</ConfirmButton>
<CancelButton onClick={clickCancel}>
<T9n t='still_cancel' />
</CancelButton>
</ButtonsContainer>
</PopupContainer>
</Modal>
)
}

@ -0,0 +1,113 @@
import styled, { css } from 'styled-components/macro'
import { ModalWindow } from 'features/Modal/styled'
import {
Modal as BaseModal,
} from 'features/AuthServiceApp/components/RegisterPopup/styled'
import { isMobileDevice } from 'config/userAgent'
export const Modal = styled(BaseModal)`
${ModalWindow} {
${isMobileDevice
? css`
min-height: 201px;
max-width: 351px;
padding: 26px 19px 33px 19px;
`
: css`
min-height: 220px;
max-width: 611px;
padding: 37px 0 39px 0;
`}
}
`
export const PopupContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-style: normal;
color: #FFFFFF;
${isMobileDevice
? css``
: css`
min-width: 611px;`}
`
export const PopupTitle = styled.div`
font-weight: 700;
font-size: 24px;
line-height: 24px;
margin-bottom: 45px;
${isMobileDevice
? css`
font-size: 20px;
line-height: 24px;
margin-bottom: 25px;`
: css`
font-size: 24px;
line-height: 24px;
margin-bottom: 45px;`}
`
export const PopupText = styled.div`
font-weight: 400;
${isMobileDevice
? css`
font-size: 16px;
line-height: 22px;
margin-bottom: 33px;`
: css`
font-size: 20px;
line-height: 28px;
margin-bottom: 55px;`}
`
export const ButtonsContainer = styled.div`
display: flex;
flex-direction: row;
align-items: center;
${isMobileDevice
? css`
gap: 15px;`
: css`
gap: 20px;
`}
`
const Button = styled.button`
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
border-radius: 5px;
color: #FFFF;
font-weight: 600;
${isMobileDevice
? css`
font-size: 16px;
line-height: 16px;
width: 149px;
height: 38px;`
: css`
font-size: 20px;
line-height: 50px;
cursor: pointer;
width: 134px;
height: 50px;`}
`
export const ConfirmButton = styled(Button)`
background: #294FC4;
border: 1px solid #294FC4;
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.3));
`
export const CancelButton = styled(Button)`
border: 1px solid #FFFFFF;
background: none;
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.3));
`

@ -0,0 +1,160 @@
import React, { useMemo } from 'react'
import map from 'lodash/map'
import isEmpty from 'lodash/isEmpty'
import includes from 'lodash/includes'
import { T9n } from 'features/T9n'
import { Name } from 'features/Name'
import { useLexicsStore } from 'features/LexicsStore'
import { isMobileDevice } from 'config/userAgent'
import {
getAge,
getArena,
getDivision,
getGender,
getMainTeam,
getRound,
} from './helpers'
import {
TDropDownProps,
TFilterItem,
TItem,
} from './types'
import {
DropDownContainer,
FiltersList,
FilterItem,
Checkbox,
CommonButtonsBlock,
ClearButtonContainer,
SearchWithAllContainer,
SearchContainer,
SearchInput,
BackButton,
} from './styled'
const Label = ({ item }: TFilterItem) => {
const isNumber = typeof item === 'number'
return (
<>
{isNumber ? (<>U{item}</>) : (<Name nameObj={item} />)}
</>
)
}
export const DropDown = ({
activeFilters,
changeInput,
clearFilters,
closeDropdownList,
filterList,
filterTitle,
inputValue,
setFilters,
}: TDropDownProps) => {
const { translate } = useLexicsStore()
const dropDownList = useMemo(() => {
switch (filterTitle) {
case 'gender':
return getGender(filterList)
case 'youth_age':
return getAge(filterList)
case 'division':
return getDivision(filterList)
case 'main_team':
return getMainTeam(filterList)
case 'arena':
return getArena(filterList, inputValue)
case 'round':
return getRound(filterList)
default:
return []
}
}, [filterTitle, filterList, inputValue]) as Array<TItem> | Array<any>
const currentActiveFilter = activeFilters[filterTitle as keyof typeof activeFilters]
return (
<DropDownContainer>
<FiltersList>
<CommonButtonsBlock isArena={filterTitle === 'arena'}>
<SearchWithAllContainer>
{/* {isMobileDevice && (<T9n className='filter_title' t={filterTitle} />)} */}
{isMobileDevice && closeDropdownList
&& (
<BackButton onClick={closeDropdownList} className='back_button'>
{filterTitle}
</BackButton>
)}
{filterTitle === 'arena' && (
<SearchContainer>
<SearchInput
onChange={changeInput}
onClick={(e) => e.stopPropagation()}
placeholder={translate('search')}
/>
</SearchContainer>
)}
<Checkbox
onChange={clearFilters(filterTitle)}
checked={isEmpty(currentActiveFilter)}
labelLexic='all'
/>
</SearchWithAllContainer>
<ClearButtonContainer
onClick={clearFilters(filterTitle)}
>
<T9n
className='clear_button'
t='clear'
/>
</ClearButtonContainer>
</CommonButtonsBlock>
{!isEmpty(dropDownList) && map(dropDownList, (filterItem, i) => (
<>
<FilterItem
key={i}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setFilters(filterTitle, filterItem)
}}
>
{filterTitle === 'gender'
? (
<Checkbox
// eslint-disable-next-line max-len
checked={includes(currentActiveFilter,
filterItem || filterItem.name_eng || filterItem.name_rus)}
onChange={(e) => {
e.preventDefault()
e.stopPropagation()
setFilters(filterTitle, filterItem)
}}
labelLexic={filterItem.title}
/>
)
: (
<Checkbox
checked={includes(currentActiveFilter,
filterItem || filterItem.name_eng || filterItem.name_rus)}
onChange={(e) => {
e.preventDefault()
e.stopPropagation()
setFilters(filterTitle, filterItem)
}}
label={(<Label item={filterItem} />)}
/>
)}
</FilterItem>
</>
))}
</FiltersList>
</DropDownContainer>
)
}

@ -0,0 +1,62 @@
import map from 'lodash/map'
import some from 'lodash/some'
import filter from 'lodash/filter'
import uniq from 'lodash/uniq'
import flatten from 'lodash/flatten'
import compact from 'lodash/compact'
import startsWith from 'lodash/startsWith'
import toLower from 'lodash/toLower'
import { Match } from 'requests'
const checkStartString = (text: string, searchString: string) => startsWith(
toLower(text), toLower(searchString),
)
export const getAge = (filterList: Array<Match>) => compact(
uniq(
flatten(
map(
filterList, (item) => [item.team1.youth_age, item.team2.youth_age],
),
),
),
)
export const getDivision = (filterList: Array<Match>) => filter(
flatten(
map(
filterList, (item) => [item.team1.division, item.team2.division],
),
), (item) => item?.id,
)
export const getMainTeam = (filterList: Array<Match>) => filter(
flatten(
map(
filterList, (item) => [item.team1.main_team, item.team2.main_team],
),
), (item) => item?.id,
)
export const getArena = (filterList: Array<Match>, inputValue: string) => filter(
map(
filterList, (item) => item.arena,
), (item) => item?.id
&& (checkStartString(item.name_eng, inputValue)
|| checkStartString(item.name_rus, inputValue)),
)
export const getRound = (filterList: Array<Match>) => filter(
map(
filterList, (item) => item.round,
), (item) => item?.id,
)
export const getGender = (filterList: Array<Match>) => {
const list = []
if (some(filterList, (item) => item.team1.gender === 1 || item.team2.gender === 1)) list.push({ id: 1, title: 'gender_male_long' })
if (some(filterList, (item) => item.team1.gender === 2 || item.team2.gender === 2)) list.push({ id: 2, title: 'gender_female_long' })
return list
}

@ -0,0 +1,215 @@
import styled, { css } from 'styled-components/macro'
import { customScrollbar } from 'features/Common'
import { Checkbox as BaseCheckbox } from 'features/Common/Checkbox'
import { Label } from 'features/Common/Checkbox/styled'
import { CheckboxSvg } from 'features/Common/Checkbox/Icon'
import { isMobileDevice } from 'config/userAgent'
import { NameStyled } from 'features/Name'
export const DropDownContainer = styled.div`
${isMobileDevice
? css`
border-top: 1px solid #505050;`
: css`
position: absolute;
top: 57px;
right: -24px;
background: #333333;
border-radius: 3.5px;
z-index: 10;`}
`
export const FiltersList = styled.ul`
${customScrollbar}
overflow-y: auto;
display: flex;
flex-direction: column;
max-height: 500px;
`
export const FilterItem = styled.li`
${isMobileDevice
? css`
max-width: 280px;
white-space: nowrap;
padding: 10px 0 10px 13px;
`
: css`
min-width: 286px;
white-space: nowrap;
padding: 15px 26px;`}
:hover {
background: rgba(255, 255, 255, 0.2);
}
`
export const Checkbox = styled(BaseCheckbox)`
display: block;
text-transform: uppercase;
${Label} {
text-transform: uppercase;
font-weight: 700;
${isMobileDevice
? css`
font-size: 10px;
line-height: 11px;
`
: css`
font-size: 18px;
line-height: 16px;
`}
${NameStyled} {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
${({ checked }) => (checked
? css``
: css`
color: rgba(255, 255, 255, 0.6);`
)}
}
${CheckboxSvg} {
margin-right: 8px;
flex: 0 0 auto;
${isMobileDevice
? css`
width: 14px;
height: 14px;
`
: css`
width: 20px;
height: 20px;
`}
}
`
type TCommonButtonsBlock = {
isArena?: boolean,
}
export const CommonButtonsBlock = styled.div<TCommonButtonsBlock>`
display: flex;
justify-content: space-between;
${({ isArena }) => (isArena
? css`
align-items: flex-start;
padding: ${isMobileDevice ? '8px 25px 10px 13px' : '24px 26px 15px 26px'};
`
: css`
align-items: center;
padding:${isMobileDevice ? '8px 25px 10px 13px' : '15px 26px'};
`
)}
`
export const ClearButtonContainer = styled.div`
${isMobileDevice
? css`
position: absolute;
top: 14px;
right: 36px;`
: ''}
.clear_button {
font-style: normal;
${isMobileDevice
? css`
font-size: 10px;
line-height: 12px;`
: css`
font-size: 18px;
line-height: 22px;`}
font-weight: 400;
letter-spacing: 0.05em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
cursor: pointer;
}
`
export const SearchContainer = styled.div`
padding-right: ${isMobileDevice ? '0px' : '20px'};
position: relative;
:before {
content: '';
background: url(/images/search.svg) no-repeat;
position: absolute;
${isMobileDevice
? css`
left: 8px;
top: 7px;`
: css`
left: 13px;
top: 13px;`}
z-index: 2;
width: 13px;
height: 11px;
}
`
export const SearchInput = styled.input`
border: none;
background: transparent;
outline: none;
width: 100%;
background: #292929;
border-radius: 10px;
margin-bottom: 15px;
color: #FFFF;
${isMobileDevice
? css`
height: 24px;
min-width: 242px;
padding-left: 23px;`
: css`
min-width: 513px;
padding-left: 36px;
height: 36px;`}
::placeholder {
${isMobileDevice
? css`
font-size: 10px;
line-height: 15px;`
: css`
font-size: 18px;
line-height: 22px;`}
text-transform: uppercase;
letter-spacing: 0.05em;
}
`
export const SearchWithAllContainer = styled.div`
flex: 1 1 auto;
`
export const BackButton = styled.div`
font-style: normal;
font-weight: 700;
font-size: 10px;
line-height: 12px;
align-items: center;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #FFFFFF;
cursor: pointer;
position: absolute;
top: 12px;
left: 13px;
padding-left: 24px;
:before {
content: '';
display: block;
background: url(/images/arrowUpWhite.svg) center no-repeat;
background-size: 12px 12xp;
transform: rotate(-90deg);
right: 0;
width: 12px;
height: 12px;
position: absolute;
top: 0;
left: 0;
}
`

@ -0,0 +1,24 @@
import { MouseEvent, ChangeEvent } from 'react'
import { Match } from 'requests'
import { TActiveFilters } from 'features/HeaderFilters/store/hooks/useMatchFilters'
export type TDropDownProps = {
activeFilters: TActiveFilters,
changeInput: (e: ChangeEvent<HTMLInputElement>) => void,
clearFilters: (filterName: string) => (e: MouseEvent | ChangeEvent<HTMLInputElement>) => void,
closeDropdownList?: () => void,
filterList: Array<Match>,
filterTitle: string,
inputValue: string,
setFilters: (filterName: string, value: number) => void,
}
export type TItem = {
id: number,
name_eng: string,
name_rus: string,
}
export type TFilterItem = {
item: TItem | number,
}

@ -1,23 +1,51 @@
import { useRecoilValue } from 'recoil'
import { isLffClient } from 'config/clients'
import { isLffClient, isInSportsClient } from 'config/clients'
import { SportsFilter } from 'features/SportsFilter'
import { SelectFilter } from 'components/SelectFilter'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { T9n } from 'features/T9n'
import {
Modal as BaseModal,
} from 'features/AuthServiceApp/components/RegisterPopup/styled'
import styled from 'styled-components/macro'
import { ModalWindow } from 'features/Modal/styled'
import {
ScArrow,
ScHeaderFilters,
ScFilterItemsWrap,
ScFilterItem,
} from './styled'
import { isSportFilterShownAtom } from '../../Atoms/HomePageAtoms'
import { ClearFiltersPopup } from '../ClearFiltersPopup'
import { MatchesFilters } from '../MatchesFilters'
export const Modal = styled(BaseModal)`
${ModalWindow} {
min-height: 452px;
max-width: 280px;
padding: 37px 0 39px 0;
}
`
const ClearButton = styled.span`
cursor: pointer;
position: absolute;
left: 5px;
top: 38px;
font-style: normal;
font-weight: 400;
font-size: 18px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.5);
`
export const HeaderFilters = () => {
const {
clickClearAll,
isEmptyFilters,
isShowTournament,
selectedFilters,
selectTournament,
@ -40,7 +68,6 @@ export const HeaderFilters = () => {
setSelectedFilters([])
setSelectedLeague(['all_competitions'])
}
return (
<ScHeaderFilters>
{!isShowTournament && (
@ -60,7 +87,19 @@ export const HeaderFilters = () => {
)}
{!isLffClient && isShowTournament && isSportFilterShown && <SportsFilter />}
{isShowTournament && (
{!isEmptyFilters && (
<ClearButton onClick={clickClearAll}>
<T9n t='clear' />
</ClearButton>
)}
{!isLffClient && (
<>
<MatchesFilters />
<ClearFiltersPopup />
</>
)}
{isShowTournament && !isInSportsClient && (
<ScFilterItemsWrap>
<ScFilterItem
className={isActiveFilter('live') ? 'activeLive' : ''}

@ -5,7 +5,8 @@ import { Icon } from 'features/Icon'
export const ScHeaderFilters = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 23px;
margin-bottom: 38px;
position: relative;
.activeLive {
color: #ffffff;

@ -0,0 +1,14 @@
import join from 'lodash/join'
import take from 'lodash/take'
import size from 'lodash/size'
import { TDefaultType } from 'features/HeaderFilters/store/hooks/useMatchFilters'
import { isMobileDevice } from 'config/userAgent'
const TEXT_LENGTH = isMobileDevice ? 6 : 7
export const truncateString = (text: string) => (
size(text) <= TEXT_LENGTH ? text : `${join(take(text, TEXT_LENGTH), '')}...`)
export const checkSize = (filtersList: Array<TDefaultType> | Array<Number> | undefined) => (
(filtersList && size(filtersList) > 2)
)

@ -0,0 +1,110 @@
import React from 'react'
import map from 'lodash/map'
import isNil from 'lodash/isNil'
import size from 'lodash/size'
import { useName } from 'features/Name'
import { T9n } from 'features/T9n'
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { isMobileDevice } from 'config/userAgent'
import {
FilterContainer,
FiltersCount,
MatchFiltersContainer,
ActiveFilters,
ActiveFilter,
CloseButton,
} from './styled'
import { DropDown } from '../Dropdown/Dropdown'
import { checkSize, truncateString } from './helpers'
type TProps = {
filterTitle: string,
item: any,
}
const ActiveFilterText = ({ filterTitle, item }: TProps) => {
const name = useName(item)
if (filterTitle === 'gender') return (<T9n t={item.title} />)
return (
<>
{truncateString(name)}
</>
)
}
type TFiltersProps = {
isMobile?: boolean,
}
export const MatchesFilters = ({ isMobile }: TFiltersProps) => {
const {
activeFilters,
changeInput,
clearFilters,
currentFilters,
filtersList,
filtersSize,
handleSetFilters,
inputValue,
isOpenList,
openDropdownList,
} = useHeaderFiltersStore()
return (
<MatchFiltersContainer>
{map(currentFilters, (filterTitle, index) => {
const currentActiveFilters = activeFilters[filterTitle as keyof typeof activeFilters]
const isShrinkFilters = filtersSize >= 7 || size(currentActiveFilters) >= 7
|| isMobile
const shrinkedActiveFilters = isShrinkFilters && currentActiveFilters
&& size(currentActiveFilters) > 2
? [currentActiveFilters[0], currentActiveFilters[1]]
: currentActiveFilters
return (
<FilterContainer
active={isOpenList === filterTitle}
key={index}
onClick={openDropdownList(filterTitle)}
>
{/* <T9n className='filter_title' t={filterTitle} /> */}
<span className='filter_title'>{filterTitle}</span>
<ActiveFilters>
{map(shrinkedActiveFilters, (item, i) => (
<ActiveFilter key={i}>
{typeof item === 'number'
? `U${item}`
: (<ActiveFilterText filterTitle={filterTitle} item={item} />)}
<CloseButton onClick={(e) => {
e.stopPropagation()
handleSetFilters(filterTitle, item)
}}
/>
</ActiveFilter>
))}
{checkSize(currentActiveFilters) && isShrinkFilters && (
<FiltersCount>
+{size(currentActiveFilters) - 2}
</FiltersCount>
)}
</ActiveFilters>
{isOpenList === filterTitle && !isNil(filtersList) && !isMobileDevice && (
<DropDown
changeInput={changeInput}
inputValue={inputValue}
clearFilters={clearFilters}
activeFilters={activeFilters}
setFilters={handleSetFilters}
filterTitle={filterTitle}
filterList={filtersList}
/>
)}
</FilterContainer>
)
})}
</MatchFiltersContainer>
)
}

@ -0,0 +1,169 @@
import { isMobileDevice } from 'config/userAgent'
import styled, { css } from 'styled-components/macro'
export const MatchFiltersContainer = styled.div`
display: flex;
flex-wrap: no-wrap;
justify-content: flex-end;
width: 100%;
${isMobileDevice
? css`
flex-direction: column;
padding-right: 16px;
border-top: 1px solid #505050;
padding-top: 19px;
gap: 22px;
`
: css`
gap: 30px;
flex-direction: row;
padding-right: 25px;
`}
`
type TFilterContainer = {
active?: boolean,
}
export const FilterContainer = styled.div<TFilterContainer>`
font-style: normal;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
position: relative;
display: flex;
${isMobileDevice
? css`
color:#8b8b8b;
font-size: 10px;
line-height: 14px;
padding-left: 19px;
flex-direction: row;
align-items: center;
`
: css`
font-size: 18px;
line-height: 34px;
height: 34px;
color: #FFFFFF;
padding-left: 32px;
flex-direction: column;`}
.filter_title {
cursor: pointer;
${isMobileDevice && 'margin-right: 6px;'}
}
${({ active }) => active && !isMobileDevice && css`
:after {
content: '';
display: block;
position: fixed;
background: #000000;
opacity: 0.7;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 9;
cursor: auto;
}`}
:before {
display: block;
position: absolute;
top: 0;
content: '';
height: 100%;
cursor: pointer;
${({ active }) => (active
? css`
z-index: 12;
`
: css`
transform: rotate(180deg);
`)}
${isMobileDevice
? css`
background: url(/images/arrowUpWhite.svg) center no-repeat;
transform: rotate(90deg);
right: 0;
width: 10px;
`
: css`
background: url(/images/arrowUpWhite.svg) center no-repeat;
background-size: 20px 12px;
left: 0;
width: 20px;
`}
}
span {
${({ active }) => (active && css`
z-index:12;
position: relative;
`)}
}
`
export const ActiveFilters = styled.div`
display: flex;
flex-direction: row;
${isMobileDevice
? css`
gap: 5px;`
: css`
gap: 10px;`}
`
export const CloseButton = styled.div`
background: url(/images/closeWhite.svg) no-repeat;
${isMobileDevice
? css`
width: 7px;
height: 7px;
background-size: 7px;`
: css`
height: 9px;
width: 9px;
background-size: 10px;`}
`
export const ActiveFilter = styled.span`
font-style: normal;
font-weight: 600;
display: flex;
align-items: center;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #FFFFFF;
border: 1px solid #FFFFFF;
border-radius: 16px;
gap: 5px;
cursor: pointer;
${isMobileDevice
? css`
padding: 0 5px;
font-size: 10px;
line-height: 12px;`
: css`
padding: 0 10px;
font-size: 14px;
height: 17px;
:hover {
background: #ffff;
color: #000000;
${CloseButton} {
background: url(/images/closeBlack.svg) no-repeat;
}
}`}
`
export const FiltersCount = styled(ActiveFilter)``

@ -0,0 +1,87 @@
import { useHeaderFiltersStore } from 'features/HeaderFilters'
import { T9n } from 'features/T9n'
import React, { useState } from 'react'
import isEmpty from 'lodash/isEmpty'
import isNil from 'lodash/isNil'
import { MatchesFilters } from '../MatchesFilters'
import {
ClearButton,
CloseButton,
FilterContainer,
Modal,
} from './styled'
import { DropDown } from '../Dropdown/Dropdown'
import { ClearFiltersPopup } from '../ClearFiltersPopup'
export const MobileMathesFilters = () => {
const [isOpen, setOpen] = useState(false)
const {
activeFilters,
changeInput,
clearFilters,
clickClearAll,
closeDropdownList,
currentFilters,
filtersList,
filtersSize,
handleSetFilters,
inputValue,
isOpenList,
} = useHeaderFiltersStore()
const openFilters = () => setOpen(true)
const clearAllFilters = () => {
setOpen(false)
clickClearAll()
}
const closeFilters = () => {
setOpen(false)
closeDropdownList()
}
return (
<>
{!isEmpty(currentFilters) && (
<>
<FilterContainer active={filtersSize !== 0} onClick={openFilters}>
<T9n t='filter' />
{filtersSize !== 0 && ` ${filtersSize}`}
</FilterContainer>
<Modal isOpen={isOpen} withCloseButton={false}>
{isEmpty(isOpenList)
? (
<>
<MatchesFilters isMobile />
{filtersSize !== 0 && (
<ClearButton onClick={clearAllFilters}>
<T9n t='clear' />
</ClearButton>
)}
<CloseButton onClick={closeFilters} />
</>
)
: (
<>
{!isNil(filtersList) && (
<DropDown
changeInput={changeInput}
inputValue={inputValue}
clearFilters={clearFilters}
activeFilters={activeFilters}
setFilters={handleSetFilters}
filterTitle={isOpenList}
filterList={filtersList}
closeDropdownList={closeDropdownList}
/>
)}
<CloseButton onClick={closeFilters} />
</>
)}
</Modal>
<ClearFiltersPopup />
</>
)}
</>
)
}

@ -0,0 +1,71 @@
import styled, { css } from 'styled-components/macro'
import { ModalWindow } from 'features/Modal/styled'
import {
Modal as BaseModal,
} from 'features/AuthServiceApp/components/RegisterPopup/styled'
export const Modal = styled(BaseModal)`
${ModalWindow} {
min-height: auto;
max-height: 452px;
max-width: 293px;
padding: 35px 0 9px 0;
}
`
export const CloseButton = styled.div`
position: absolute;
width: 13px;
height: 13px;
background:url(/images/closeWhite.svg) no-repeat;
background-size: 13px;
top: 13px;
right: 13px;
`
type TFilterProps = {
active?: boolean,
}
export const FilterContainer = styled.div<TFilterProps>`
font-style: normal;
font-weight: 700;
font-size: 10px;
line-height: 12px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #FFFFFF;
padding-left: 22px;
position: relative;
margin-left: 20px;
:before{
content: '';
display: block;
position: absolute;
left: 0;
top: 0;
width:14px;
height: 14px;
${({ active }) => (active
? css`
background: url(/images/filter-white.svg) no-repeat;`
: css`
background: url(/images/filter-gray.svg) no-repeat;`)}
background-size: 14px 14px;
}
`
export const ClearButton = styled.div`
font-style: normal;
position: absolute;
top: 14px;
right: 36px;
font-size: 10px;
line-height: 12px;
font-weight: 400;
letter-spacing: 0.05em;
text-transform: uppercase;
color: rgba(255,255,255,0.5);
`

@ -35,7 +35,7 @@ const getDate = (date: Date) => format(date, 'yyyy-MM-dd')
export const useHomePage = () => {
const { user } = useAuthStore()
const { selectedDate } = useHeaderFiltersStore()
const { queryParams, selectedDate } = useHeaderFiltersStore()
const [isOpenDownload, setIsOpenDownload] = useState(false)
const [isShowConfirmPopup, setIsShowConfirmPopup] = useState(false)
const setIsSportFilterShown = useSetRecoilState(isSportFilterShownAtom)
@ -57,21 +57,22 @@ export const useHomePage = () => {
useEffect(() => {
const dateLastOpenSmartBanner = localStorage.getItem('dateLastOpenSmartBanner')
if (!dateLastOpenSmartBanner
|| Date.parse(JSON.parse(dateLastOpenSmartBanner)) < Date.parse(new Date().toISOString())
|| Date.parse(JSON.parse(dateLastOpenSmartBanner)) < Date.parse(new Date().toISOString())
) {
setIsOpenDownload(true)
}
}, [])
const fetchMatches = useCallback(
(limit: number, offset: number) => getHomeMatches({
const fetchMatches = useCallback((limit: number, offset: number) => getHomeMatches(
{
date: getDate(selectedDate),
limit,
offset,
queryParams,
timezoneOffset: getTimezoneOffset(selectedDate),
}),
[selectedDate],
)
},
), [selectedDate, queryParams])
useEffect(() => {
if (client.name === ClientNames.Facr) {

@ -18,7 +18,8 @@ export const UserSportFavWrapper = styled.aside`
left: 0;
top: 0;
bottom: 0;
z-index: 10;
width: 4%;
z-index: 8;
${isMobileDevice
? css`

@ -1,4 +1,5 @@
import { PROCEDURES, API_ROOT } from 'config'
import isEmpty from 'lodash/isEmpty'
import { client } from 'config/clients'
@ -8,10 +9,20 @@ import { getMatchesPreviews } from './getPreviews'
const proc = PROCEDURES.get_matches
export type TQueryParams = {
arena?: Array<number>,
division?: Array<number>,
gender?: Array<number>,
main_team?: Array<number>,
round?: Array<number>,
youth_age?: Array<number>,
}
type Args = {
date: string,
limit: number,
offset: number,
queryParams?: TQueryParams,
timezoneOffset: number,
}
@ -19,16 +30,22 @@ export const getHomeMatches = async ({
date,
limit,
offset,
queryParams,
timezoneOffset,
}: Args): Promise<MatchesBySection> => {
const url = `${API_ROOT}/v1/data/get-matches`
const url = `${API_ROOT}/v1/data/matches/query`
const config = {
body: {
_p_date: `${date} 00:00:00`,
_p_gmt: timezoneOffset,
_p_limit: limit,
_p_offset: offset,
arena: !isEmpty(queryParams?.arena) ? queryParams?.arena : undefined,
date: `${date} 00:00:00`,
division: !isEmpty(queryParams?.division) ? queryParams?.division : undefined,
gender: !isEmpty(queryParams?.gender) ? queryParams?.gender : undefined,
gmt: String(timezoneOffset),
limit: String(limit),
main_team: !isEmpty(queryParams?.main_team) ? queryParams?.main_team : undefined,
offset: String(offset),
round: !isEmpty(queryParams?.round) ? queryParams?.round : undefined,
youth_age: !isEmpty(queryParams?.youth_age) ? queryParams?.youth_age : undefined,
...client.requests?.[proc],
},
}

@ -7,11 +7,20 @@ export type TournamentType = {
name_rus: string,
}
export type TDefaultType = {
id: number,
name_eng: string,
name_rus: string,
}
type Team = {
division?: TDefaultType, // Дивизион
gender?: number, // ИД пола
id: number,
main_team?: TDefaultType, // Родительский клуб команды
name_eng: string,
name_rus: string,
score?: number | null,
youth_age?: number | null, // Возраст команды
}
export type SportInfo = {
@ -21,6 +30,8 @@ export type SportInfo = {
export type Match = {
access: boolean,
/** Стадион */
arena: TDefaultType | null,
/** тип трансляции */
c_type_broadcast: number,
calc: boolean,
@ -38,6 +49,8 @@ export type Match = {
live: boolean,
preview?: string,
previewURL?: string,
/** Раунд */
round?: TDefaultType,
sport: SportTypes,
sport_info: SportInfo,
/** наличие завершенного hls стрима */

Loading…
Cancel
Save