2021-01-01 21:51:40 +01:00
|
|
|
import React, { useState, useEffect, forwardRef, useRef, useCallback } from 'react';
|
2020-10-01 21:17:43 +02:00
|
|
|
import dayjs from 'dayjs';
|
2020-06-13 00:21:58 +02:00
|
|
|
import styled from 'styled-components';
|
2020-04-13 00:45:51 +02:00
|
|
|
import DatePicker from 'react-datepicker';
|
2020-06-13 00:21:58 +02:00
|
|
|
import _ from 'lodash';
|
2020-04-13 00:45:51 +02:00
|
|
|
import 'react-datepicker/dist/react-datepicker.css';
|
2020-06-13 00:21:58 +02:00
|
|
|
import { getYear, getMonth } from 'date-fns';
|
2020-07-17 02:40:23 +02:00
|
|
|
import { useForm, Controller } from 'react-hook-form';
|
2020-08-23 19:27:56 +02:00
|
|
|
import NOOP from 'shared/utils/noop';
|
2021-09-04 21:07:54 +02:00
|
|
|
import { Clock, Cross } from 'shared/icons';
|
|
|
|
import Select from 'react-select/src/Select';
|
2020-08-23 19:27:56 +02:00
|
|
|
|
2021-01-01 21:51:40 +01:00
|
|
|
import {
|
|
|
|
Wrapper,
|
|
|
|
RemoveDueDate,
|
|
|
|
DueDateInput,
|
|
|
|
DueDatePickerWrapper,
|
|
|
|
ConfirmAddDueDate,
|
|
|
|
DateRangeInputs,
|
|
|
|
AddDateRange,
|
|
|
|
ActionIcon,
|
|
|
|
ActionsWrapper,
|
|
|
|
ClearButton,
|
|
|
|
ActionsSeparator,
|
|
|
|
ActionClock,
|
|
|
|
ActionLabel,
|
|
|
|
} from './Styles';
|
2020-04-13 00:45:51 +02:00
|
|
|
|
|
|
|
type DueDateManagerProps = {
|
|
|
|
task: Task;
|
2021-01-01 21:51:40 +01:00
|
|
|
onDueDateChange: (task: Task, newDueDate: Date, hasTime: boolean) => void;
|
2020-06-19 01:12:15 +02:00
|
|
|
onRemoveDueDate: (task: Task) => void;
|
2020-04-13 00:45:51 +02:00
|
|
|
onCancel: () => void;
|
|
|
|
};
|
2020-06-13 00:21:58 +02:00
|
|
|
|
2020-06-16 00:36:59 +02:00
|
|
|
const Form = styled.form`
|
|
|
|
padding-top: 25px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const FormField = styled.div`
|
|
|
|
width: 50%;
|
|
|
|
display: inline-block;
|
|
|
|
`;
|
2020-06-13 00:21:58 +02:00
|
|
|
const HeaderSelectLabel = styled.div`
|
|
|
|
display: inline-block;
|
|
|
|
position: relative;
|
|
|
|
z-index: 9999;
|
|
|
|
border-radius: 3px;
|
|
|
|
cursor: pointer;
|
|
|
|
padding: 6px 10px;
|
|
|
|
text-decoration: underline;
|
|
|
|
margin: 6px 0;
|
|
|
|
font-size: 14px;
|
|
|
|
line-height: 16px;
|
|
|
|
margin-left: 0;
|
|
|
|
margin-right: 0;
|
|
|
|
padding-left: 4px;
|
|
|
|
padding-right: 4px;
|
|
|
|
color: #c2c6dc;
|
|
|
|
|
|
|
|
&:hover {
|
2021-05-03 00:31:24 +02:00
|
|
|
background: ${(props) => props.theme.colors.primary};
|
2020-06-13 00:21:58 +02:00
|
|
|
color: #c2c6dc;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const HeaderSelect = styled.select`
|
|
|
|
text-decoration: underline;
|
|
|
|
font-size: 14px;
|
|
|
|
text-align: center;
|
|
|
|
background: none;
|
|
|
|
outline: none;
|
|
|
|
border: none;
|
|
|
|
border-radius: 3px;
|
|
|
|
appearance: none;
|
2021-01-01 21:51:40 +01:00
|
|
|
width: 100%;
|
|
|
|
display: inline-block;
|
2020-06-13 00:21:58 +02:00
|
|
|
|
2021-01-01 21:51:40 +01:00
|
|
|
& option {
|
|
|
|
color: #c2c6dc;
|
2021-05-03 00:31:24 +02:00
|
|
|
background: ${(props) => props.theme.colors.bg.primary};
|
2021-01-01 21:51:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
& option:hover {
|
2021-05-03 00:31:24 +02:00
|
|
|
background: ${(props) => props.theme.colors.bg.secondary};
|
|
|
|
border: 1px solid ${(props) => props.theme.colors.primary};
|
2020-06-13 00:21:58 +02:00
|
|
|
outline: none !important;
|
|
|
|
box-shadow: none;
|
|
|
|
color: #c2c6dc;
|
|
|
|
}
|
|
|
|
|
|
|
|
&::-ms-expand {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
position: absolute;
|
|
|
|
z-index: 9998;
|
|
|
|
margin: 0;
|
|
|
|
left: 0;
|
|
|
|
top: 5px;
|
|
|
|
opacity: 0;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const HeaderButton = styled.button`
|
|
|
|
cursor: pointer;
|
|
|
|
color: #c2c6dc;
|
|
|
|
text-decoration: underline;
|
|
|
|
font-size: 14px;
|
|
|
|
text-align: center;
|
|
|
|
padding: 6px 10px;
|
|
|
|
margin: 6px 0;
|
|
|
|
background: none;
|
|
|
|
outline: none;
|
|
|
|
border: none;
|
|
|
|
border-radius: 3px;
|
|
|
|
&:hover {
|
2021-05-03 00:31:24 +02:00
|
|
|
background: ${(props) => props.theme.colors.primary};
|
2020-06-13 00:21:58 +02:00
|
|
|
color: #fff;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
const HeaderActions = styled.div`
|
|
|
|
position: relative;
|
|
|
|
text-align: center;
|
|
|
|
& > button:first-child {
|
|
|
|
float: left;
|
|
|
|
}
|
|
|
|
& > button:last-child {
|
|
|
|
float: right;
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
2020-06-19 01:12:15 +02:00
|
|
|
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
2021-01-01 21:51:40 +01:00
|
|
|
const currentDueDate = task.dueDate ? dayjs(task.dueDate).toDate() : null;
|
2021-05-03 00:31:24 +02:00
|
|
|
const {
|
|
|
|
register,
|
|
|
|
handleSubmit,
|
|
|
|
setValue,
|
|
|
|
setError,
|
|
|
|
formState: { errors },
|
|
|
|
control,
|
|
|
|
} = useForm<DueDateFormData>();
|
2020-04-13 00:45:51 +02:00
|
|
|
|
2021-01-01 21:51:40 +01:00
|
|
|
const [startDate, setStartDate] = useState<Date | null>(currentDueDate);
|
|
|
|
const [endDate, setEndDate] = useState<Date | null>(currentDueDate);
|
|
|
|
const [hasTime, enableTime] = useState(task.hasTime ?? false);
|
|
|
|
const firstRun = useRef<boolean>(true);
|
2020-06-16 00:36:59 +02:00
|
|
|
|
2021-01-01 21:51:40 +01:00
|
|
|
const debouncedFunctionRef = useRef((newDate: Date | null, nowHasTime: boolean) => {
|
|
|
|
if (!firstRun.current) {
|
|
|
|
if (newDate) {
|
|
|
|
onDueDateChange(task, newDate, nowHasTime);
|
|
|
|
} else {
|
|
|
|
onRemoveDueDate(task);
|
|
|
|
enableTime(false);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
firstRun.current = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const debouncedChange = useCallback(
|
|
|
|
_.debounce((newDate, nowHasTime) => debouncedFunctionRef.current(newDate, nowHasTime), 500),
|
|
|
|
[],
|
|
|
|
);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
debouncedChange(startDate, hasTime);
|
|
|
|
}, [startDate, hasTime]);
|
2020-06-13 00:21:58 +02:00
|
|
|
const years = _.range(2010, getYear(new Date()) + 10, 1);
|
|
|
|
const months = [
|
|
|
|
'January',
|
|
|
|
'February',
|
|
|
|
'March',
|
|
|
|
'April',
|
|
|
|
'May',
|
|
|
|
'June',
|
|
|
|
'July',
|
|
|
|
'August',
|
|
|
|
'September',
|
|
|
|
'October',
|
|
|
|
'November',
|
|
|
|
'December',
|
|
|
|
];
|
2021-01-01 21:51:40 +01:00
|
|
|
|
|
|
|
const onChange = (dates: any) => {
|
|
|
|
const [start, end] = dates;
|
|
|
|
setStartDate(start);
|
|
|
|
setEndDate(end);
|
2020-06-16 00:36:59 +02:00
|
|
|
};
|
2021-01-01 21:51:40 +01:00
|
|
|
const [isRange, setIsRange] = useState(false);
|
|
|
|
|
2020-04-13 00:45:51 +02:00
|
|
|
return (
|
|
|
|
<Wrapper>
|
2021-01-01 21:51:40 +01:00
|
|
|
<DateRangeInputs>
|
|
|
|
<DatePicker
|
|
|
|
selected={startDate}
|
2021-05-03 00:31:24 +02:00
|
|
|
onChange={(date) => {
|
|
|
|
if (!Array.isArray(date)) {
|
|
|
|
setStartDate(date);
|
|
|
|
}
|
|
|
|
}}
|
2021-01-01 21:51:40 +01:00
|
|
|
popperClassName="picker-hidden"
|
|
|
|
dateFormat="yyyy-MM-dd"
|
|
|
|
disabledKeyboardNavigation
|
|
|
|
isClearable
|
|
|
|
placeholderText="Select due date"
|
|
|
|
/>
|
|
|
|
{isRange ? (
|
2020-06-16 00:36:59 +02:00
|
|
|
<DatePicker
|
2021-01-01 21:51:40 +01:00
|
|
|
selected={startDate}
|
|
|
|
isClearable
|
2021-05-03 00:31:24 +02:00
|
|
|
onChange={(date) => {
|
|
|
|
if (!Array.isArray(date)) {
|
|
|
|
setStartDate(date);
|
|
|
|
}
|
|
|
|
}}
|
2021-01-01 21:51:40 +01:00
|
|
|
popperClassName="picker-hidden"
|
|
|
|
dateFormat="yyyy-MM-dd"
|
|
|
|
placeholderText="Select from date"
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<AddDateRange>Add date range</AddDateRange>
|
|
|
|
)}
|
|
|
|
</DateRangeInputs>
|
|
|
|
<DatePicker
|
|
|
|
selected={startDate}
|
2021-05-03 00:31:24 +02:00
|
|
|
onChange={(date) => {
|
|
|
|
if (!Array.isArray(date)) {
|
|
|
|
setStartDate(date);
|
|
|
|
}
|
|
|
|
}}
|
2021-01-01 21:51:40 +01:00
|
|
|
startDate={startDate}
|
|
|
|
useWeekdaysShort
|
|
|
|
renderCustomHeader={({
|
|
|
|
date,
|
|
|
|
changeYear,
|
|
|
|
changeMonth,
|
|
|
|
decreaseMonth,
|
|
|
|
increaseMonth,
|
|
|
|
prevMonthButtonDisabled,
|
|
|
|
nextMonthButtonDisabled,
|
|
|
|
}) => (
|
|
|
|
<HeaderActions>
|
|
|
|
<HeaderButton onClick={decreaseMonth} disabled={prevMonthButtonDisabled}>
|
|
|
|
Prev
|
|
|
|
</HeaderButton>
|
|
|
|
<HeaderSelectLabel>
|
|
|
|
{months[date.getMonth()]}
|
|
|
|
<HeaderSelect
|
|
|
|
value={months[getMonth(date)]}
|
|
|
|
onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}
|
|
|
|
>
|
2021-05-03 00:31:24 +02:00
|
|
|
{months.map((option) => (
|
2021-01-01 21:51:40 +01:00
|
|
|
<option key={option} value={option}>
|
|
|
|
{option}
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
</HeaderSelect>
|
|
|
|
</HeaderSelectLabel>
|
|
|
|
<HeaderSelectLabel>
|
|
|
|
{date.getFullYear()}
|
|
|
|
<HeaderSelect value={getYear(date)} onChange={({ target: { value } }) => changeYear(parseInt(value, 10))}>
|
2021-05-03 00:31:24 +02:00
|
|
|
{years.map((option) => (
|
2021-01-01 21:51:40 +01:00
|
|
|
<option key={option} value={option}>
|
|
|
|
{option}
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
</HeaderSelect>
|
|
|
|
</HeaderSelectLabel>
|
2020-06-16 00:36:59 +02:00
|
|
|
|
2021-01-01 21:51:40 +01:00
|
|
|
<HeaderButton onClick={increaseMonth} disabled={nextMonthButtonDisabled}>
|
|
|
|
Next
|
|
|
|
</HeaderButton>
|
|
|
|
</HeaderActions>
|
|
|
|
)}
|
|
|
|
inline
|
|
|
|
/>
|
|
|
|
<ActionsSeparator />
|
|
|
|
{hasTime && (
|
|
|
|
<ActionsWrapper>
|
|
|
|
<ActionClock width={16} height={16} />
|
|
|
|
<ActionLabel>Due Time</ActionLabel>
|
|
|
|
<DatePicker
|
2020-06-16 00:36:59 +02:00
|
|
|
selected={startDate}
|
2021-05-03 00:31:24 +02:00
|
|
|
onChange={(date) => {
|
|
|
|
if (!Array.isArray(date)) {
|
|
|
|
setStartDate(date);
|
|
|
|
}
|
2020-06-16 00:36:59 +02:00
|
|
|
}}
|
2021-01-01 21:51:40 +01:00
|
|
|
showTimeSelect
|
|
|
|
showTimeSelectOnly
|
|
|
|
timeIntervals={15}
|
|
|
|
timeCaption="Time"
|
|
|
|
dateFormat="h:mm aa"
|
2020-06-16 00:36:59 +02:00
|
|
|
/>
|
2021-01-01 21:51:40 +01:00
|
|
|
<ActionIcon onClick={() => enableTime(false)}>
|
|
|
|
<Cross width={16} height={16} />
|
|
|
|
</ActionIcon>
|
|
|
|
</ActionsWrapper>
|
|
|
|
)}
|
|
|
|
<ActionsWrapper>
|
|
|
|
{!hasTime && (
|
|
|
|
<ActionIcon
|
2020-06-16 00:36:59 +02:00
|
|
|
onClick={() => {
|
2021-01-01 21:51:40 +01:00
|
|
|
if (startDate === null) {
|
|
|
|
const today = new Date();
|
|
|
|
today.setHours(12, 30, 0);
|
|
|
|
setStartDate(today);
|
|
|
|
}
|
|
|
|
enableTime(true);
|
2020-06-16 00:36:59 +02:00
|
|
|
}}
|
|
|
|
>
|
2021-01-01 21:51:40 +01:00
|
|
|
<Clock width={16} height={16} />
|
|
|
|
</ActionIcon>
|
|
|
|
)}
|
|
|
|
<ClearButton onClick={() => setStartDate(null)}>{hasTime ? 'Clear all' : 'Clear'}</ClearButton>
|
|
|
|
</ActionsWrapper>
|
2020-04-13 00:45:51 +02:00
|
|
|
</Wrapper>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default DueDateManager;
|