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 { 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' |
||||
|
||||
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