Ott 140 calendar filter (#49)
parent
6d44eefd1b
commit
2fff5da887
@ -0,0 +1,25 @@ |
|||||||
|
import { useEffect } from 'react' |
||||||
|
|
||||||
|
import ru from 'date-fns/locale/ru' |
||||||
|
import en from 'date-fns/locale/en-GB' |
||||||
|
|
||||||
|
import { registerLocale, setDefaultLocale } from 'react-datepicker' |
||||||
|
|
||||||
|
import { useLexicsStore } from 'features/LexicsStore' |
||||||
|
|
||||||
|
/** |
||||||
|
* react-datepicker использует date-fns локейлы для локализации |
||||||
|
* регистрируем [en, ru] чтобы названия дней недели менялись при смене языка |
||||||
|
*/ |
||||||
|
export const useDatepickerLocales = () => { |
||||||
|
const { lang } = useLexicsStore() |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
registerLocale('ru', ru) |
||||||
|
registerLocale('en', en) |
||||||
|
}, []) |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
setDefaultLocale(lang) |
||||||
|
}, [lang]) |
||||||
|
} |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
import React from 'react' |
||||||
|
|
||||||
|
import type { ReactDatePickerProps } from 'react-datepicker' |
||||||
|
import DatePickerComponent from 'react-datepicker' |
||||||
|
import 'react-datepicker/dist/react-datepicker.css' |
||||||
|
|
||||||
|
import addMonths from 'date-fns/addMonths' |
||||||
|
|
||||||
|
import { useLexicsStore } from 'features/LexicsStore' |
||||||
|
import { getDisplayDate, getMonthName } from 'features/DateFilter/helpers' |
||||||
|
|
||||||
|
import { useDatepickerLocales } from './hooks' |
||||||
|
import { |
||||||
|
Wrapper, |
||||||
|
MonthsWrapper, |
||||||
|
CurrentMonth, |
||||||
|
MonthButton, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
type Args = ( |
||||||
|
Parameters<NonNullable<ReactDatePickerProps['renderCustomHeader']>>[0] |
||||||
|
& { lang: string } |
||||||
|
) |
||||||
|
|
||||||
|
const monthType = 'short' |
||||||
|
|
||||||
|
const renderCustomHeader = ({ |
||||||
|
date, |
||||||
|
decreaseMonth, |
||||||
|
increaseMonth, |
||||||
|
lang, |
||||||
|
}: Args) => { |
||||||
|
const prevMonth = getMonthName({ |
||||||
|
date: addMonths(date, -1), |
||||||
|
lang, |
||||||
|
monthType, |
||||||
|
}) |
||||||
|
const nextMonth = getMonthName({ |
||||||
|
date: addMonths(date, 1), |
||||||
|
lang, |
||||||
|
monthType, |
||||||
|
}) |
||||||
|
const currentDate = getDisplayDate({ |
||||||
|
date, |
||||||
|
lang, |
||||||
|
}) |
||||||
|
return ( |
||||||
|
<MonthsWrapper> |
||||||
|
<MonthButton onClick={decreaseMonth}>{prevMonth}</MonthButton> |
||||||
|
<CurrentMonth>{currentDate.month}, {currentDate.year}</CurrentMonth> |
||||||
|
<MonthButton onClick={increaseMonth}>{nextMonth}</MonthButton> |
||||||
|
</MonthsWrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
type Props = Pick<ReactDatePickerProps, ( |
||||||
|
| 'onChange' |
||||||
|
| 'open' |
||||||
|
| 'selected' |
||||||
|
)> |
||||||
|
|
||||||
|
export const DatePicker = ({ |
||||||
|
onChange, |
||||||
|
open, |
||||||
|
selected, |
||||||
|
}: Props) => { |
||||||
|
const { lang } = useLexicsStore() |
||||||
|
useDatepickerLocales() |
||||||
|
return ( |
||||||
|
<Wrapper> |
||||||
|
<DatePickerComponent |
||||||
|
open={open} |
||||||
|
selected={selected} |
||||||
|
onChange={onChange} |
||||||
|
renderCustomHeader={(props) => renderCustomHeader({ ...props, lang })} |
||||||
|
/> |
||||||
|
</Wrapper> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,138 @@ |
|||||||
|
import styled from 'styled-components/macro' |
||||||
|
|
||||||
|
export const Wrapper = styled.div` |
||||||
|
position: absolute; |
||||||
|
left: 0; |
||||||
|
top: calc(100% + 14px); |
||||||
|
|
||||||
|
.react-datepicker { |
||||||
|
margin: 0; |
||||||
|
border: none; |
||||||
|
width: 288px; |
||||||
|
background-color: #666666; |
||||||
|
box-shadow: 0px 2px 50px #000000; |
||||||
|
border-radius: 2px; |
||||||
|
font-family: inherit; |
||||||
|
color: #fff; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__header { |
||||||
|
border: none; |
||||||
|
border-radius: 2; |
||||||
|
padding-top: 12px; |
||||||
|
background-color: #666666; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__month-container { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__day-names { |
||||||
|
margin-top: 24px; |
||||||
|
margin-bottom: 12px; |
||||||
|
display: flex; |
||||||
|
justify-content: space-around; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__day-name { |
||||||
|
margin: 0; |
||||||
|
width: 28px; |
||||||
|
color: inherit; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 16px; |
||||||
|
text-align: center; |
||||||
|
letter-spacing: 0.1px; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__month { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__week { |
||||||
|
margin: 8px 0; |
||||||
|
display: flex; |
||||||
|
justify-content: space-around; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__day { |
||||||
|
margin: 0; |
||||||
|
border: none; |
||||||
|
outline: none; |
||||||
|
width: 28px; |
||||||
|
height: 28px; |
||||||
|
color: inherit; |
||||||
|
opacity: 0.6; |
||||||
|
font-weight: normal; |
||||||
|
font-size: 12px; |
||||||
|
border-radius: 50%; |
||||||
|
|
||||||
|
:hover { |
||||||
|
background-color: rgba(255, 255, 255, 0.4); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__day--selected { |
||||||
|
color: #005EDD; |
||||||
|
background-color: #ffffff; |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__day--keyboard-selected { |
||||||
|
color: inherit; |
||||||
|
background-color: transparent; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__day--outside-month { |
||||||
|
opacity: 0.2 |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker-popper { |
||||||
|
transform: translate(0, 0) !important; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker-popper[data-placement^="bottom"] { |
||||||
|
margin-top: 0px; |
||||||
|
} |
||||||
|
|
||||||
|
.react-datepicker__input-container, |
||||||
|
.react-datepicker__triangle { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const MonthsWrapper = styled.div` |
||||||
|
width: 100%; |
||||||
|
height: 28px; |
||||||
|
padding: 0 12px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: space-between; |
||||||
|
font-weight: 600; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 16px; |
||||||
|
text-align: center; |
||||||
|
letter-spacing: 0.1px; |
||||||
|
text-transform: capitalize; |
||||||
|
` |
||||||
|
|
||||||
|
export const CurrentMonth = styled.span` |
||||||
|
min-width: 100px; |
||||||
|
height: 100%; |
||||||
|
padding: 0 14px; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background-color: #005EDD; |
||||||
|
border-radius: 16px; |
||||||
|
` |
||||||
|
|
||||||
|
export const MonthButton = styled.button` |
||||||
|
border: none; |
||||||
|
outline: none; |
||||||
|
padding: 0; |
||||||
|
opacity: 0.4; |
||||||
|
color: inherit; |
||||||
|
background-color: transparent; |
||||||
|
text-transform: inherit; |
||||||
|
cursor: pointer; |
||||||
|
` |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
type Args = { |
||||||
|
date: Date, |
||||||
|
lang: string, |
||||||
|
monthType?: 'long' | 'short', |
||||||
|
} |
||||||
|
|
||||||
|
export const getMonthName = ({ |
||||||
|
date, |
||||||
|
lang, |
||||||
|
monthType, |
||||||
|
}: Args) => ( |
||||||
|
date.toLocaleString(lang, { month: monthType }) |
||||||
|
) |
||||||
|
|
||||||
|
export const getDisplayDate = ({ |
||||||
|
date, |
||||||
|
lang, |
||||||
|
monthType = 'long', |
||||||
|
}: Args) => ({ |
||||||
|
day: date.getDate(), |
||||||
|
month: getMonthName({ |
||||||
|
date, |
||||||
|
lang, |
||||||
|
monthType, |
||||||
|
}), |
||||||
|
year: date.getFullYear(), |
||||||
|
}) |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
import { |
||||||
|
useState, |
||||||
|
useCallback, |
||||||
|
useEffect, |
||||||
|
} from 'react' |
||||||
|
|
||||||
|
import debounce from 'lodash/debounce' |
||||||
|
import format from 'date-fns/format' |
||||||
|
import addDays from 'date-fns/addDays' |
||||||
|
import startOfDay from 'date-fns/startOfDay' |
||||||
|
|
||||||
|
import { getMatches } from 'requests' |
||||||
|
import { useToggle } from 'hooks' |
||||||
|
import { useLexicsStore } from 'features/LexicsStore' |
||||||
|
|
||||||
|
import { getDisplayDate } from '../helpers' |
||||||
|
|
||||||
|
const dateFormat = 'dd/MM/yyyy HH:mm:ss' |
||||||
|
|
||||||
|
export const useDateFilter = () => { |
||||||
|
const { lang } = useLexicsStore() |
||||||
|
const { |
||||||
|
close, |
||||||
|
isOpen, |
||||||
|
open, |
||||||
|
} = useToggle() |
||||||
|
const fetchMatches = useCallback(debounce(getMatches, 300), []) |
||||||
|
const [currentDate, setCurrentDate] = useState(new Date()) |
||||||
|
|
||||||
|
const date = getDisplayDate({ |
||||||
|
date: currentDate, |
||||||
|
lang, |
||||||
|
}) |
||||||
|
|
||||||
|
const onPreviousClick = () => { |
||||||
|
setCurrentDate(addDays(currentDate, -1)) |
||||||
|
} |
||||||
|
|
||||||
|
const onNextClick = () => { |
||||||
|
setCurrentDate(addDays(currentDate, 1)) |
||||||
|
} |
||||||
|
|
||||||
|
const onDateChange = (newDate: Date | null) => { |
||||||
|
if (newDate) { |
||||||
|
setCurrentDate(newDate) |
||||||
|
close() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
const formattedDate = format(startOfDay(currentDate), dateFormat) |
||||||
|
fetchMatches({ |
||||||
|
date: formattedDate, |
||||||
|
sportType: 1, |
||||||
|
tournamentId: 8, |
||||||
|
}) |
||||||
|
}, [fetchMatches, currentDate]) |
||||||
|
|
||||||
|
return { |
||||||
|
close, |
||||||
|
currentDate, |
||||||
|
date, |
||||||
|
isOpen, |
||||||
|
onDateChange, |
||||||
|
onNextClick, |
||||||
|
onPreviousClick, |
||||||
|
open, |
||||||
|
} |
||||||
|
} |
||||||
@ -1,5 +1,54 @@ |
|||||||
import React from 'react' |
import React from 'react' |
||||||
|
|
||||||
import { Wrapper } from './styled' |
import { OutsideClick } from 'features/OutsideClick' |
||||||
|
|
||||||
export const DateFilter = () => <Wrapper /> |
import { useDateFilter } from './hooks' |
||||||
|
import { DatePicker } from './components/DatePicker' |
||||||
|
import { |
||||||
|
Wrapper, |
||||||
|
Button, |
||||||
|
ArrowLeft, |
||||||
|
ArrowRight, |
||||||
|
DateButton, |
||||||
|
Day, |
||||||
|
MonthYearWrapper, |
||||||
|
Month, |
||||||
|
Year, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
export const DateFilter = () => { |
||||||
|
const { |
||||||
|
close, |
||||||
|
currentDate, |
||||||
|
date, |
||||||
|
isOpen, |
||||||
|
onDateChange, |
||||||
|
onNextClick, |
||||||
|
onPreviousClick, |
||||||
|
open, |
||||||
|
} = useDateFilter() |
||||||
|
return ( |
||||||
|
<Wrapper active={isOpen}> |
||||||
|
<Button onClick={onPreviousClick} borderLeftRadius={2}> |
||||||
|
<ArrowLeft /> |
||||||
|
</Button> |
||||||
|
<OutsideClick onClick={close}> |
||||||
|
<DateButton onClick={open}> |
||||||
|
<Day>{date.day}</Day> |
||||||
|
<MonthYearWrapper> |
||||||
|
<Month>{date.month}</Month> |
||||||
|
<Year>{date.year}</Year> |
||||||
|
</MonthYearWrapper> |
||||||
|
</DateButton> |
||||||
|
<DatePicker |
||||||
|
open={isOpen} |
||||||
|
selected={currentDate} |
||||||
|
onChange={onDateChange} |
||||||
|
/> |
||||||
|
</OutsideClick> |
||||||
|
<Button onClick={onNextClick} borderRightRadius={2}> |
||||||
|
<ArrowRight /> |
||||||
|
</Button> |
||||||
|
</Wrapper> |
||||||
|
) |
||||||
|
} |
||||||
|
|||||||
@ -1,3 +1,145 @@ |
|||||||
import styled from 'styled-components/macro' |
import styled from 'styled-components/macro' |
||||||
|
|
||||||
export const Wrapper = styled.div`` |
type Props = { |
||||||
|
active: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export const Wrapper = styled.div<Props>` |
||||||
|
position: relative; |
||||||
|
height: 100%; |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); |
||||||
|
|
||||||
|
${({ active }) => (active |
||||||
|
? ` |
||||||
|
& ${Button}, ${DateButton} { |
||||||
|
color: #fff; |
||||||
|
background-color: #666666; |
||||||
|
} |
||||||
|
` |
||||||
|
: '' |
||||||
|
)}; |
||||||
|
` |
||||||
|
|
||||||
|
export const DateButton = styled.button` |
||||||
|
border: none; |
||||||
|
outline: none; |
||||||
|
padding: 0; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
text-align: left; |
||||||
|
width: 192px; |
||||||
|
height: 100%; |
||||||
|
border-left: 1px solid #222222; |
||||||
|
border-right: 1px solid #222222; |
||||||
|
cursor: pointer; |
||||||
|
background-color: #3F3F3F; |
||||||
|
color: #999999; |
||||||
|
|
||||||
|
:hover { |
||||||
|
background-color: #484848; |
||||||
|
} |
||||||
|
|
||||||
|
:active { |
||||||
|
color: #fff; |
||||||
|
background-color: #666666; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const Day = styled.span` |
||||||
|
font-weight: bold; |
||||||
|
font-size: 38px; |
||||||
|
letter-spacing: -0.03em; |
||||||
|
` |
||||||
|
|
||||||
|
export const Month = styled.span` |
||||||
|
font-weight: bold; |
||||||
|
font-size: 14px; |
||||||
|
` |
||||||
|
|
||||||
|
export const MonthYearWrapper = styled.div` |
||||||
|
height: 28px; |
||||||
|
margin-left: 4px; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: space-between; |
||||||
|
` |
||||||
|
|
||||||
|
export const Year = styled.span` |
||||||
|
font-style: normal; |
||||||
|
font-weight: 300; |
||||||
|
font-size: 14px; |
||||||
|
` |
||||||
|
|
||||||
|
const Arrow = styled.div` |
||||||
|
width: 16px; |
||||||
|
height: 2px; |
||||||
|
margin: auto; |
||||||
|
background-color: #222222; |
||||||
|
|
||||||
|
&::before { |
||||||
|
content: ''; |
||||||
|
position: absolute; |
||||||
|
width: 10px; |
||||||
|
height: 10px; |
||||||
|
border-left: 2px solid #222222; |
||||||
|
border-bottom: 2px solid #222222; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const ArrowLeft = styled(Arrow)` |
||||||
|
&:before { |
||||||
|
transform: rotate(45deg); |
||||||
|
top: 19px; |
||||||
|
left: 17px; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
export const ArrowRight = styled(Arrow)` |
||||||
|
&:before { |
||||||
|
transform: rotate(225deg); |
||||||
|
top: 19px; |
||||||
|
right: 17px; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
type ButtonProps = { |
||||||
|
borderLeftRadius?: number, |
||||||
|
borderRightRadius?: number, |
||||||
|
} |
||||||
|
|
||||||
|
export const Button = styled.button<ButtonProps>` |
||||||
|
position: relative; |
||||||
|
outline: none; |
||||||
|
border: none; |
||||||
|
width: 48px; |
||||||
|
height: 100%; |
||||||
|
cursor: pointer; |
||||||
|
background-color: #3F3F3F; |
||||||
|
${({ borderLeftRadius }) => ( |
||||||
|
borderLeftRadius |
||||||
|
? ` |
||||||
|
border-top-left-radius: ${borderLeftRadius}px; |
||||||
|
border-bottom-left-radius: ${borderLeftRadius}px; |
||||||
|
` |
||||||
|
: '' |
||||||
|
)} |
||||||
|
${({ borderRightRadius }) => ( |
||||||
|
borderRightRadius |
||||||
|
? ` |
||||||
|
border-top-right-radius: ${borderRightRadius}px; |
||||||
|
border-bottom-right-radius: ${borderRightRadius}px; |
||||||
|
` |
||||||
|
: '' |
||||||
|
)} |
||||||
|
|
||||||
|
:hover { |
||||||
|
background-color: #484848; |
||||||
|
} |
||||||
|
|
||||||
|
:active { |
||||||
|
background-color: #666666; |
||||||
|
} |
||||||
|
` |
||||||
|
|||||||
@ -0,0 +1,56 @@ |
|||||||
|
import { DATA_URL, PROCEDURES } from 'config' |
||||||
|
import { callApi, getResponseData } from 'helpers' |
||||||
|
|
||||||
|
const proc = PROCEDURES.get_matches |
||||||
|
|
||||||
|
export type Item = { |
||||||
|
id: number, |
||||||
|
matches: Array<Match>, |
||||||
|
name_eng: string, |
||||||
|
name_rus: string, |
||||||
|
sport: number, |
||||||
|
} |
||||||
|
|
||||||
|
export type Match = { |
||||||
|
date: string, |
||||||
|
id: number, |
||||||
|
round_id: number, |
||||||
|
stream_status: number, |
||||||
|
team1: Team, |
||||||
|
team2: Team, |
||||||
|
} |
||||||
|
|
||||||
|
export type Team = { |
||||||
|
id: number, |
||||||
|
name_eng: string, |
||||||
|
name_rus: string, |
||||||
|
score: number, |
||||||
|
} |
||||||
|
|
||||||
|
type Args = { |
||||||
|
date: string, |
||||||
|
sportType: number, |
||||||
|
tournamentId: number, |
||||||
|
} |
||||||
|
|
||||||
|
export const getMatches = ({ |
||||||
|
date, |
||||||
|
sportType, |
||||||
|
tournamentId, |
||||||
|
}: Args): Promise<Array<Item>> => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
params: { |
||||||
|
_p_date: date, |
||||||
|
_p_sport: sportType, |
||||||
|
_p_tournament_id: tournamentId, |
||||||
|
}, |
||||||
|
proc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return callApi({ |
||||||
|
config, |
||||||
|
url: DATA_URL, |
||||||
|
}).then(getResponseData(proc)) |
||||||
|
} |
||||||
Loading…
Reference in new issue