Calendar day next (#3513)

This commit is contained in:
Jaco Greeff 2020-08-25 17:53:00 +02:00 committed by GitHub
parent 5ffc99cb08
commit e963fa625b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 168 additions and 117 deletions

View File

@ -16,8 +16,11 @@ import { useTranslation } from './translate';
interface Props {
className?: string;
date: Date;
hasNextDay: boolean;
now: Date;
scheduled: EntryInfo[];
setNextDay: () => void;
setPrevDay: () => void;
}
const HOURS = ((): number[] => {
@ -30,16 +33,14 @@ const HOURS = ((): number[] => {
return hours;
})();
const NOOP = () => undefined;
function Day ({ className, date, now, scheduled }: Props): React.ReactElement<Props> {
function Day ({ className, date, hasNextDay, now, scheduled, setNextDay, setPrevDay }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const monthRef = useRef(MONTHS.map((m) => t(m)));
const [isToday, nowHours, nowMinutes] = useMemo(
() => [
date.getDate() === now.getDate() && date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear(),
date.getMonth() === now.getMonth() && date.getFullYear() === now.getFullYear() && date.getDate() === now.getDate(),
now.getHours(),
now.getMinutes()
],
@ -51,14 +52,14 @@ function Day ({ className, date, now, scheduled }: Props): React.ReactElement<Pr
<div className='dayHeader'>
<Button
icon='chevron-left'
isDisabled
onClick={NOOP}
isDisabled={isToday}
onClick={setPrevDay}
/>
<div>{date.getDate()} {monthRef.current[date.getMonth()]} {date.getFullYear()} {isToday && <DayTime />}</div>
<Button
icon='chevron-right'
isDisabled
onClick={NOOP}
isDisabled={!hasNextDay}
onClick={setNextDay}
/>
</div>
<div className='hoursContainer'>
@ -87,10 +88,6 @@ export default React.memo(styled(Day)`
font-size: 1.25rem;
justify-content: space-between;
padding: 1rem 1.5rem 0;
.ui--Button {
opacity: 0;
}
}
.hoursContainer {

View File

@ -2,9 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { EntryInfo } from './types';
import { DateState, EntryInfo } from './types';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useMemo, useRef } from 'react';
import styled from 'styled-components';
import { Button } from '@polkadot/react-components';
@ -14,110 +14,22 @@ import MonthDay from './MonthDay';
interface Props {
className?: string;
hasNextMonth: boolean;
lastDay: number;
now: Date;
onChange: (date: Date) => void;
scheduled: EntryInfo[];
setDay: (day: number) => void;
setNextMonth: () => void;
setPrevMonth: () => void;
state: DateState;
}
interface DateState {
dateMonth: Date;
dateMonthNext: Date;
dateSelected: Date;
days: number[];
startClass: string;
}
function newZeroDate (input: Date): Date {
const date = new Date(input);
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
function nextMonth (date: Date, firstDay = 1): Date {
const currMonth = date.getMonth();
return currMonth === 11
? new Date(date.getFullYear() + 1, 0, firstDay)
: new Date(date.getFullYear(), currMonth + 1, firstDay);
}
function prevMonth (date: Date): Date {
const currMonth = date.getMonth();
return currMonth === 0
? new Date(date.getFullYear() - 1, 11, 1)
: new Date(date.getFullYear(), currMonth - 1, 1);
}
function getDateState (_dateMonth: Date, _dateSelected: Date): DateState {
const dateMonth = newZeroDate(_dateMonth);
dateMonth.setDate(1);
const dateMonthNext = nextMonth(dateMonth);
const dateSelected = newZeroDate(_dateSelected);
const numDays = nextMonth(dateMonth, 0).getDate();
const days: number[] = [];
for (let i = 1; i <= numDays; i++) {
days.push(i);
}
return {
dateMonth,
dateMonthNext,
dateSelected,
days,
startClass: `start${DAYS[dateMonth.getDay()]}`
};
}
function Month ({ className, now, onChange, scheduled }: Props): React.ReactElement<Props> {
function Month ({ className, hasNextMonth, lastDay, now, scheduled, setDay, setNextMonth, setPrevMonth, state: { dateMonth, dateSelected, days, startClass } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [{ dateMonth, dateMonthNext, dateSelected, days, startClass }, setDate] = useState(getDateState(now, now));
const dayOfWeekRef = useRef(DAYS.map((d) => t(d)));
const monthRef = useRef(MONTHS.map((m) => t(m)));
const hasNextMonth = useMemo(
(): boolean => {
const nextTime = dateMonthNext.getTime();
return scheduled.some(({ dateTime }) => dateTime >= nextTime);
},
[dateMonthNext, scheduled]
);
const _nextMonth = useCallback(
() => setDate(({ dateMonth, dateSelected }) => getDateState(nextMonth(dateMonth), dateSelected)),
[]
);
const _prevMonth = useCallback(
() => setDate(({ dateMonth, dateSelected }) => getDateState(prevMonth(dateMonth), dateSelected)),
[]
);
const _setDay = useCallback(
(day: number) => setDate(({ dateMonth }): DateState => {
const date = new Date(dateMonth);
date.setDate(day);
return getDateState(date, date);
}),
[]
);
useEffect((): void => {
onChange(dateSelected);
}, [dateSelected, onChange]);
const [isCurrYear, isCurrMonth, isNowYear, isNowMonth, isOlderMonth] = useMemo(
() => [
dateMonth.getFullYear() === dateSelected.getFullYear(),
@ -136,13 +48,13 @@ function Month ({ className, now, onChange, scheduled }: Props): React.ReactElem
<Button
icon='chevron-left'
isDisabled={isNowYear && (isOlderMonth || isNowMonth)}
onClick={_prevMonth}
onClick={setPrevMonth}
/>
<div>{monthRef.current[dateMonth.getMonth()]} {dateMonth.getFullYear()}</div>
<Button
icon='chevron-right'
isDisabled={!hasNextMonth}
onClick={_nextMonth}
onClick={setNextMonth}
/>
</div>
<div className='dayOfWeek'>
@ -156,10 +68,10 @@ function Month ({ className, now, onChange, scheduled }: Props): React.ReactElem
dateMonth={dateMonth}
day={day}
isCurrent={isCurrYear && isCurrMonth && day === dateSelected.getDate()}
isDisabled={isNowYear && (isOlderMonth || (isNowMonth && now.getDate() > day))}
isDisabled={(isNowYear && (isOlderMonth || (isNowMonth && now.getDate() > day))) || (!hasNextMonth && day > lastDay)}
key={day}
scheduled={scheduled}
setDay={_setDay}
setDay={setDay}
/>
))}
</div>

View File

@ -2,7 +2,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useEffect, useRef, useState } from 'react';
import { DateState } from './types';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import styled from 'styled-components';
import { Tabs } from '@polkadot/react-components';
@ -10,6 +12,7 @@ import Day from './Day';
import Month from './Month';
import { useTranslation } from './translate';
import useScheduled from './useScheduled';
import { getDateState, nextMonth, prevMonth } from './util';
interface Props {
basePath: string;
@ -22,7 +25,7 @@ function CalendarApp ({ basePath, className }: Props): React.ReactElement<Props>
const { t } = useTranslation();
const scheduled = useScheduled();
const [now, setNow] = useState(new Date());
const [selected, setSelected] = useState(new Date());
const [dateState, setDateState] = useState(getDateState(now, now));
const itemsRef = useRef([{
isRoot: true,
@ -38,6 +41,72 @@ function CalendarApp ({ basePath, className }: Props): React.ReactElement<Props>
};
}, []);
const [hasNextMonth, hasNextDay, lastDay] = useMemo(
() => {
const nextDay = new Date(dateState.dateSelected);
nextDay.setDate(nextDay.getDate() + 1);
const nextDayTime = nextDay.getTime();
const nextMonthTime = dateState.dateMonthNext.getTime();
const hasNextMonth = scheduled.some(({ dateTime }) => dateTime >= nextMonthTime);
const hasNextDay = hasNextMonth || scheduled.some(({ dateTime }) => dateTime >= nextDayTime);
const lastDay = hasNextMonth
? 42 // more than days in month
: scheduled.reduce((lastDay: number, { date }): number => {
return date.getFullYear() === dateState.dateMonth.getFullYear() && date.getMonth() === dateState.dateMonth.getMonth()
? Math.max(lastDay, date.getDate())
: lastDay;
}, 0);
return [hasNextMonth, hasNextDay, lastDay];
},
[dateState, scheduled]
);
const _nextMonth = useCallback(
() => setDateState(({ dateMonth, dateSelected }) => getDateState(nextMonth(dateMonth), dateSelected)),
[]
);
const _prevMonth = useCallback(
() => setDateState(({ dateMonth, dateSelected }) => getDateState(prevMonth(dateMonth), dateSelected)),
[]
);
const _nextDay = useCallback(
() => setDateState(({ dateSelected }): DateState => {
const date = new Date(dateSelected);
date.setDate(date.getDate() + 1);
return getDateState(date, date);
}),
[]
);
const _prevDay = useCallback(
() => setDateState(({ dateSelected }): DateState => {
const date = new Date(dateSelected);
date.setDate(date.getDate() - 1);
return getDateState(date, date);
}),
[]
);
const _setDay = useCallback(
(day: number) => setDateState(({ dateMonth }): DateState => {
const date = new Date(dateMonth);
date.setDate(day);
return getDateState(date, date);
}),
[]
);
return (
<main className={className}>
<header>
@ -48,14 +117,22 @@ function CalendarApp ({ basePath, className }: Props): React.ReactElement<Props>
</header>
<div className='calendarFlex'>
<Day
date={selected}
date={dateState.dateSelected}
hasNextDay={hasNextDay}
now={now}
scheduled={scheduled}
setNextDay={_nextDay}
setPrevDay={_prevDay}
/>
<Month
hasNextMonth={hasNextMonth}
lastDay={lastDay}
now={now}
onChange={setSelected}
scheduled={scheduled}
setDay={_setDay}
setNextMonth={_nextMonth}
setPrevMonth={_prevMonth}
state={dateState}
/>
</div>
</main>

View File

@ -15,3 +15,11 @@ export interface EntryInfo {
isPending?: boolean;
type: EntryType;
}
export interface DateState {
dateMonth: Date;
dateMonthNext: Date;
dateSelected: Date;
days: number[];
startClass: string;
}

View File

@ -0,0 +1,57 @@
// Copyright 2017-2020 @polkadot/app-calendar authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { DateState } from './types';
import { DAYS } from './constants';
export function newZeroDate (input: Date): Date {
const date = new Date(input);
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
export function nextMonth (date: Date, firstDay = 1): Date {
const currMonth = date.getMonth();
return currMonth === 11
? new Date(date.getFullYear() + 1, 0, firstDay)
: new Date(date.getFullYear(), currMonth + 1, firstDay);
}
export function prevMonth (date: Date): Date {
const currMonth = date.getMonth();
return currMonth === 0
? new Date(date.getFullYear() - 1, 11, 1)
: new Date(date.getFullYear(), currMonth - 1, 1);
}
export function getDateState (_dateMonth: Date, _dateSelected: Date): DateState {
const dateMonth = newZeroDate(_dateMonth);
dateMonth.setDate(1);
const dateMonthNext = nextMonth(dateMonth);
const dateSelected = newZeroDate(_dateSelected);
const numDays = nextMonth(dateMonth, 0).getDate();
const days: number[] = [];
for (let i = 1; i <= numDays; i++) {
days.push(i);
}
return {
dateMonth,
dateMonthNext,
dateSelected,
days,
startClass: `start${DAYS[dateMonth.getDay()]}`
};
}