diff --git a/public/images/closeBlack.svg b/public/images/closeBlack.svg new file mode 100644 index 00000000..8cb31a4f --- /dev/null +++ b/public/images/closeBlack.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/closeWhite.svg b/public/images/closeWhite.svg new file mode 100644 index 00000000..8bcc2ce2 --- /dev/null +++ b/public/images/closeWhite.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/SelectFilter/styled.tsx b/src/components/SelectFilter/styled.tsx index 543b770f..b0a2dd34 100644 --- a/src/components/SelectFilter/styled.tsx +++ b/src/components/SelectFilter/styled.tsx @@ -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; diff --git a/src/config/lexics/indexLexics.tsx b/src/config/lexics/indexLexics.tsx index 8bb9b918..939c0678 100644 --- a/src/config/lexics/indexLexics.tsx +++ b/src/config/lexics/indexLexics.tsx @@ -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, diff --git a/src/features/HeaderFilters/store/hooks/index.tsx b/src/features/HeaderFilters/store/hooks/index.tsx index 8530987c..27f7b4c9 100644 --- a/src/features/HeaderFilters/store/hooks/index.tsx +++ b/src/features/HeaderFilters/store/hooks/index.tsx @@ -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>(['all_competitions']) const [selectedFilters, setSelectedFilters] = useState>([]) @@ -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, diff --git a/src/features/HeaderFilters/store/hooks/useMatchFilters.tsx b/src/features/HeaderFilters/store/hooks/useMatchFilters.tsx new file mode 100644 index 00000000..3455c590 --- /dev/null +++ b/src/features/HeaderFilters/store/hooks/useMatchFilters.tsx @@ -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, + division?: Array, + gender?: Array, + main_team?: Array, + round?: Array, + youth_age?: Array, +} +export type TDefaultType = { + id: number, + name_eng: string, + name_rus: string, +} + +export const useMatchFilters = (selectedDate: Date) => { + const [filtersList, setFiltersList] = useState>() + const [isOpenList, setOpenList] = useState('') + const [activeFilters, setActiveFilters] = useState({}) + const [inputValue, setInputValue] = useState('') + const [openPopup, setOpenPopup] = useState(false) + + const setFilters = (filters: Array) => { + 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) => { + 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) => { + 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, + } +} diff --git a/src/features/HeaderMobile/index.tsx b/src/features/HeaderMobile/index.tsx index 86d26f4c..6076bce9 100644 --- a/src/features/HeaderMobile/index.tsx +++ b/src/features/HeaderMobile/index.tsx @@ -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 - {!isLffClient && isSportFilterShown ? : null} + {!isLffClient && isSportFilterShown ? ( + <> + + + + ) : null} diff --git a/src/features/HomePage/components/ClearFiltersPopup/index.tsx b/src/features/HomePage/components/ClearFiltersPopup/index.tsx new file mode 100644 index 00000000..3a510448 --- /dev/null +++ b/src/features/HomePage/components/ClearFiltersPopup/index.tsx @@ -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 ( + + + + + + + + + + + + + + + + + + + ) +} diff --git a/src/features/HomePage/components/ClearFiltersPopup/styled.tsx b/src/features/HomePage/components/ClearFiltersPopup/styled.tsx new file mode 100644 index 00000000..690d81cb --- /dev/null +++ b/src/features/HomePage/components/ClearFiltersPopup/styled.tsx @@ -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)); +` diff --git a/src/features/HomePage/components/Dropdown/Dropdown.tsx b/src/features/HomePage/components/Dropdown/Dropdown.tsx new file mode 100644 index 00000000..5ba084b7 --- /dev/null +++ b/src/features/HomePage/components/Dropdown/Dropdown.tsx @@ -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}) : ()} + + ) +} + +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 | Array + + const currentActiveFilter = activeFilters[filterTitle as keyof typeof activeFilters] + + return ( + + + + + {/* {isMobileDevice && ()} */} + {isMobileDevice && closeDropdownList + && ( + + {filterTitle} + + )} + {filterTitle === 'arena' && ( + + e.stopPropagation()} + placeholder={translate('search')} + /> + + )} + + + + + + + + {!isEmpty(dropDownList) && map(dropDownList, (filterItem, i) => ( + <> + { + e.preventDefault() + e.stopPropagation() + setFilters(filterTitle, filterItem) + }} + > + {filterTitle === 'gender' + ? ( + { + e.preventDefault() + e.stopPropagation() + setFilters(filterTitle, filterItem) + }} + labelLexic={filterItem.title} + /> + ) + : ( + { + e.preventDefault() + e.stopPropagation() + setFilters(filterTitle, filterItem) + }} + label={( + + ))} + + + ) +} diff --git a/src/features/HomePage/components/Dropdown/helpers.tsx b/src/features/HomePage/components/Dropdown/helpers.tsx new file mode 100644 index 00000000..caffc6fc --- /dev/null +++ b/src/features/HomePage/components/Dropdown/helpers.tsx @@ -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) => compact( + uniq( + flatten( + map( + filterList, (item) => [item.team1.youth_age, item.team2.youth_age], + ), + ), + ), +) + +export const getDivision = (filterList: Array) => filter( + flatten( + map( + filterList, (item) => [item.team1.division, item.team2.division], + ), + ), (item) => item?.id, +) + +export const getMainTeam = (filterList: Array) => filter( + flatten( + map( + filterList, (item) => [item.team1.main_team, item.team2.main_team], + ), + ), (item) => item?.id, +) + +export const getArena = (filterList: Array, 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) => filter( + map( + filterList, (item) => item.round, + ), (item) => item?.id, +) + +export const getGender = (filterList: Array) => { + 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 +} diff --git a/src/features/HomePage/components/Dropdown/styled.tsx b/src/features/HomePage/components/Dropdown/styled.tsx new file mode 100644 index 00000000..4cff2a99 --- /dev/null +++ b/src/features/HomePage/components/Dropdown/styled.tsx @@ -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` +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; +} +` diff --git a/src/features/HomePage/components/Dropdown/types.tsx b/src/features/HomePage/components/Dropdown/types.tsx new file mode 100644 index 00000000..77c26e29 --- /dev/null +++ b/src/features/HomePage/components/Dropdown/types.tsx @@ -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) => void, + clearFilters: (filterName: string) => (e: MouseEvent | ChangeEvent) => void, + closeDropdownList?: () => void, + filterList: Array, + 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, +} diff --git a/src/features/HomePage/components/HeaderFilters/index.tsx b/src/features/HomePage/components/HeaderFilters/index.tsx index c219c5a0..781164e1 100644 --- a/src/features/HomePage/components/HeaderFilters/index.tsx +++ b/src/features/HomePage/components/HeaderFilters/index.tsx @@ -1,26 +1,51 @@ -import { Fragment } from 'react' import { useRecoilValue } from 'recoil' -import { isLffClient } from 'config/clients' -import { ProfileTypes } from 'config/profileTypes' +import { isLffClient, isInSportsClient } from 'config/clients' -import { ProfileLink } from 'features/ProfileLink' 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, @@ -43,33 +68,38 @@ export const HeaderFilters = () => { setSelectedFilters([]) setSelectedLeague(['all_competitions']) } - return ( - {(!isShowTournament && selectTournament) && ( - + {!isShowTournament && ( + <> handleClickBack()} /> - - - - + + )} {!isLffClient && isShowTournament && isSportFilterShown && } - {isShowTournament && ( + {!isEmptyFilters && ( + + + + )} + {!isLffClient && ( + <> + + + + + )} + {isShowTournament && !isInSportsClient && ( ( + size(text) <= TEXT_LENGTH ? text : `${join(take(text, TEXT_LENGTH), '')}...`) + +export const checkSize = (filtersList: Array | Array | undefined) => ( + (filtersList && size(filtersList) > 2) +) diff --git a/src/features/HomePage/components/MatchesFilters/index.tsx b/src/features/HomePage/components/MatchesFilters/index.tsx new file mode 100644 index 00000000..15ff7714 --- /dev/null +++ b/src/features/HomePage/components/MatchesFilters/index.tsx @@ -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 () + 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 ( + + {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 ( + + {/* */} + {filterTitle} + + {map(shrinkedActiveFilters, (item, i) => ( + + {typeof item === 'number' + ? `U${item}` + : ()} + { + e.stopPropagation() + handleSetFilters(filterTitle, item) + }} + /> + + ))} + + {checkSize(currentActiveFilters) && isShrinkFilters && ( + + +{size(currentActiveFilters) - 2} + + )} + + {isOpenList === filterTitle && !isNil(filtersList) && !isMobileDevice && ( + + )} + + ) + })} + + + ) +} diff --git a/src/features/HomePage/components/MatchesFilters/styled.tsx b/src/features/HomePage/components/MatchesFilters/styled.tsx new file mode 100644 index 00000000..656c771b --- /dev/null +++ b/src/features/HomePage/components/MatchesFilters/styled.tsx @@ -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` +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)`` diff --git a/src/features/HomePage/components/MobileMatchesFilters/index.tsx b/src/features/HomePage/components/MobileMatchesFilters/index.tsx new file mode 100644 index 00000000..2547c38d --- /dev/null +++ b/src/features/HomePage/components/MobileMatchesFilters/index.tsx @@ -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) && ( + <> + + + {filtersSize !== 0 && ` ${filtersSize}`} + + + {isEmpty(isOpenList) + ? ( + <> + + {filtersSize !== 0 && ( + + + + )} + + + ) + : ( + <> + {!isNil(filtersList) && ( + + )} + + + )} + + + + )} + + ) +} diff --git a/src/features/HomePage/components/MobileMatchesFilters/styled.tsx b/src/features/HomePage/components/MobileMatchesFilters/styled.tsx new file mode 100644 index 00000000..1b40d4f8 --- /dev/null +++ b/src/features/HomePage/components/MobileMatchesFilters/styled.tsx @@ -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` +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); +` diff --git a/src/features/HomePage/hooks.tsx b/src/features/HomePage/hooks.tsx index c8b11b82..6f14c4d1 100644 --- a/src/features/HomePage/hooks.tsx +++ b/src/features/HomePage/hooks.tsx @@ -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) { diff --git a/src/features/UserFavorites/styled.tsx b/src/features/UserFavorites/styled.tsx index bb588989..85bb5e40 100644 --- a/src/features/UserFavorites/styled.tsx +++ b/src/features/UserFavorites/styled.tsx @@ -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` diff --git a/src/requests/getMatches/getHomeMatches.tsx b/src/requests/getMatches/getHomeMatches.tsx index f76c9aad..f1519251 100644 --- a/src/requests/getMatches/getHomeMatches.tsx +++ b/src/requests/getMatches/getHomeMatches.tsx @@ -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, + division?: Array, + gender?: Array, + main_team?: Array, + round?: Array, + youth_age?: Array, +} + 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 => { - 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], }, } diff --git a/src/requests/getMatches/types.tsx b/src/requests/getMatches/types.tsx index 15e8e9ce..af52baae 100644 --- a/src/requests/getMatches/types.tsx +++ b/src/requests/getMatches/types.tsx @@ -8,11 +8,20 @@ export type TournamentType = { sportType: SportTypes, } +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 = { @@ -22,6 +31,8 @@ export type SportInfo = { export type Match = { access: boolean, + /** Стадион */ + arena: TDefaultType | null, /** тип трансляции */ c_type_broadcast: number, calc: boolean, @@ -39,6 +50,8 @@ export type Match = { live: boolean, preview?: string, previewURL?: string, + /** Раунд */ + round?: TDefaultType, sport: SportTypes, sport_info: SportInfo, /** наличие завершенного hls стрима */