feat: redesign task due date manager
This commit is contained in:
parent
a8b3809515
commit
d6101d9221
@ -126,4 +126,8 @@ export default createGlobalStyle`
|
|||||||
}
|
}
|
||||||
|
|
||||||
${mixin.placeholderColor(color.textLight)}
|
${mixin.placeholderColor(color.textLight)}
|
||||||
|
|
||||||
|
.picker-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -793,12 +793,12 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
|
|||||||
<DueDateManager
|
<DueDateManager
|
||||||
task={task}
|
task={task}
|
||||||
onRemoveDueDate={t => {
|
onRemoveDueDate={t => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
|
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
|
||||||
hidePopup();
|
// hidePopup();
|
||||||
}}
|
}}
|
||||||
onDueDateChange={(t, newDueDate) => {
|
onDueDateChange={(t, newDueDate, hasTime) => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
|
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate, hasTime } });
|
||||||
hidePopup();
|
// hidePopup();
|
||||||
}}
|
}}
|
||||||
onCancel={NOOP}
|
onCancel={NOOP}
|
||||||
/>
|
/>
|
||||||
|
@ -632,12 +632,12 @@ const Details: React.FC<DetailsProps> = ({
|
|||||||
<DueDateManager
|
<DueDateManager
|
||||||
task={task}
|
task={task}
|
||||||
onRemoveDueDate={t => {
|
onRemoveDueDate={t => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null } });
|
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
|
||||||
hidePopup();
|
// hidePopup();
|
||||||
}}
|
}}
|
||||||
onDueDateChange={(t, newDueDate) => {
|
onDueDateChange={(t, newDueDate, hasTime) => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate } });
|
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate, hasTime } });
|
||||||
hidePopup();
|
// hidePopup();
|
||||||
}}
|
}}
|
||||||
onCancel={NOOP}
|
onCancel={NOOP}
|
||||||
/>
|
/>
|
||||||
|
@ -2,6 +2,8 @@ import styled from 'styled-components';
|
|||||||
import Button from 'shared/components/Button';
|
import Button from 'shared/components/Button';
|
||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
import Input from 'shared/components/Input';
|
import Input from 'shared/components/Input';
|
||||||
|
import ControlledInput from 'shared/components/ControlledInput';
|
||||||
|
import { Clock } from 'shared/icons';
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
export const Wrapper = styled.div`
|
||||||
display: flex
|
display: flex
|
||||||
@ -17,6 +19,11 @@ display: flex
|
|||||||
z-index: 10000;
|
z-index: 10000;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
& .react-datepicker__close-icon::after {
|
||||||
|
background: none;
|
||||||
|
font-size: 16px;
|
||||||
|
color: ${props => props.theme.colors.text.primary};
|
||||||
|
}
|
||||||
|
|
||||||
& .react-datepicker-time__header {
|
& .react-datepicker-time__header {
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${props => props.theme.colors.text.primary};
|
||||||
@ -91,6 +98,24 @@ display: flex
|
|||||||
border-bottom: 1px solid ${props => props.theme.colors.border};
|
border-bottom: 1px solid ${props => props.theme.colors.border};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .react-datepicker__input-container input {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
border-color: ${props => props.theme.colors.alternate};
|
||||||
|
background: #262c49;
|
||||||
|
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 0.7rem;
|
||||||
|
color: #c2c6dc;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 0 12px;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
|
||||||
|
border: 1px solid rgba(115, 103, 240);
|
||||||
|
background: ${props => props.theme.colors.bg.primary};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DueDatePickerWrapper = styled.div`
|
export const DueDatePickerWrapper = styled.div`
|
||||||
@ -110,6 +135,44 @@ export const RemoveDueDate = styled(Button)`
|
|||||||
margin: 0 0 0 4px;
|
margin: 0 0 0 4px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const AddDateRange = styled.div`
|
||||||
|
opacity: 0.6;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: ${props => mixin.rgba(props.theme.colors.primary, 0.8)};
|
||||||
|
&:hover {
|
||||||
|
color: ${props => mixin.rgba(props.theme.colors.primary, 1)};
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DateRangeInputs = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: -4px;
|
||||||
|
& > div:first-child,
|
||||||
|
& > div:last-child {
|
||||||
|
flex: 1 1 92px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
min-width: 92px;
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
& > ${AddDateRange} {
|
||||||
|
margin-left: 4px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
& > .react-datepicker-wrapper input {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const CancelDueDate = styled.div`
|
export const CancelDueDate = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -119,15 +182,86 @@ export const CancelDueDate = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DueDateInput = styled(Input)`
|
export const DueDateInput = styled(ControlledInput)`
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionWrapper = styled.div`
|
export const ActionsSeparator = styled.div`
|
||||||
padding-top: 8px;
|
margin-top: 8px;
|
||||||
|
height: 1px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background: #414561;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
`;
|
||||||
|
export const ActionsWrapper = styled.div`
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
& .react-datepicker-wrapper {
|
||||||
|
margin-left: auto;
|
||||||
|
width: 82px;
|
||||||
|
}
|
||||||
|
& .react-datepicker__input-container input {
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionClock = styled(Clock)`
|
||||||
|
align-self: center;
|
||||||
|
fill: ${props => props.theme.colors.primary};
|
||||||
|
margin: 0 8px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionLabel = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionIcon = styled.div`
|
||||||
|
height: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
width: 36px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 8px;
|
||||||
|
svg {
|
||||||
|
fill: ${props => props.theme.colors.text.primary};
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
transition-property: background, border, box-shadow, fill;
|
||||||
|
}
|
||||||
|
&:hover svg {
|
||||||
|
fill: ${props => props.theme.colors.text.secondary};
|
||||||
|
}
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ClearButton = styled.div`
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-left: auto;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: inline-flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
justify-content: center;
|
||||||
|
transition-duration: 0.2s;
|
||||||
|
transition-property: background, border, box-shadow, color, fill;
|
||||||
|
color: ${props => props.theme.colors.text.primary};
|
||||||
|
&:hover {
|
||||||
|
color: ${props => props.theme.colors.text.secondary};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, forwardRef } from 'react';
|
import React, { useState, useEffect, forwardRef, useRef, useCallback } from 'react';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import DatePicker from 'react-datepicker';
|
import DatePicker from 'react-datepicker';
|
||||||
@ -8,11 +8,27 @@ import { getYear, getMonth } from 'date-fns';
|
|||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
import NOOP from 'shared/utils/noop';
|
import NOOP from 'shared/utils/noop';
|
||||||
|
|
||||||
import { Wrapper, ActionWrapper, RemoveDueDate, DueDateInput, DueDatePickerWrapper, ConfirmAddDueDate } from './Styles';
|
import {
|
||||||
|
Wrapper,
|
||||||
|
RemoveDueDate,
|
||||||
|
DueDateInput,
|
||||||
|
DueDatePickerWrapper,
|
||||||
|
ConfirmAddDueDate,
|
||||||
|
DateRangeInputs,
|
||||||
|
AddDateRange,
|
||||||
|
ActionIcon,
|
||||||
|
ActionsWrapper,
|
||||||
|
ClearButton,
|
||||||
|
ActionsSeparator,
|
||||||
|
ActionClock,
|
||||||
|
ActionLabel,
|
||||||
|
} from './Styles';
|
||||||
|
import { Clock, Cross } from 'shared/icons';
|
||||||
|
import Select from 'react-select/src/Select';
|
||||||
|
|
||||||
type DueDateManagerProps = {
|
type DueDateManagerProps = {
|
||||||
task: Task;
|
task: Task;
|
||||||
onDueDateChange: (task: Task, newDueDate: Date) => void;
|
onDueDateChange: (task: Task, newDueDate: Date, hasTime: boolean) => void;
|
||||||
onRemoveDueDate: (task: Task) => void;
|
onRemoveDueDate: (task: Task) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
@ -52,14 +68,20 @@ const HeaderSelect = styled.select`
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 4px 6px;
|
|
||||||
background: none;
|
background: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
&:hover {
|
& option {
|
||||||
|
color: #c2c6dc;
|
||||||
|
background: ${props => props.theme.colors.bg.primary};
|
||||||
|
}
|
||||||
|
|
||||||
|
& option:hover {
|
||||||
background: ${props => props.theme.colors.bg.secondary};
|
background: ${props => props.theme.colors.bg.secondary};
|
||||||
border: 1px solid ${props => props.theme.colors.primary};
|
border: 1px solid ${props => props.theme.colors.primary};
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
@ -110,15 +132,34 @@ const HeaderActions = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
||||||
const now = dayjs();
|
const currentDueDate = task.dueDate ? dayjs(task.dueDate).toDate() : null;
|
||||||
const { register, handleSubmit, errors, setValue, setError, formState, control } = useForm<DueDateFormData>();
|
const { register, handleSubmit, errors, setValue, setError, formState, control } = useForm<DueDateFormData>();
|
||||||
const [startDate, setStartDate] = useState(new Date());
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
const newDate = dayjs(startDate).format('YYYY-MM-DD');
|
debouncedChange(startDate, hasTime);
|
||||||
setValue('endDate', newDate);
|
}, [startDate, hasTime]);
|
||||||
}, [startDate]);
|
|
||||||
|
|
||||||
const years = _.range(2010, getYear(new Date()) + 10, 1);
|
const years = _.range(2010, getYear(new Date()) + 10, 1);
|
||||||
const months = [
|
const months = [
|
||||||
'January',
|
'January',
|
||||||
@ -134,19 +175,21 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
|||||||
'November',
|
'November',
|
||||||
'December',
|
'December',
|
||||||
];
|
];
|
||||||
const saveDueDate = (data: any) => {
|
|
||||||
const newDate = dayjs(`${data.endDate} ${dayjs(data.endTime).format('h:mm A')}`, 'YYYY-MM-DD h:mm A');
|
const onChange = (dates: any) => {
|
||||||
if (newDate.isValid()) {
|
const [start, end] = dates;
|
||||||
onDueDateChange(task, newDate.toDate());
|
setStartDate(start);
|
||||||
}
|
setEndDate(end);
|
||||||
};
|
};
|
||||||
const CustomTimeInput = forwardRef(({ value, onClick }: any, $ref: any) => {
|
const [isRange, setIsRange] = useState(false);
|
||||||
|
|
||||||
|
const CustomTimeInput = forwardRef(({ value, onClick, onChange, onBlur, onFocus }: any, $ref: any) => {
|
||||||
return (
|
return (
|
||||||
<DueDateInput
|
<DueDateInput
|
||||||
id="endTime"
|
id="endTime"
|
||||||
value={value}
|
value={value}
|
||||||
name="endTime"
|
name="endTime"
|
||||||
ref={$ref}
|
onChange={onChange}
|
||||||
width="100%"
|
width="100%"
|
||||||
variant="alternate"
|
variant="alternate"
|
||||||
label="Time"
|
label="Time"
|
||||||
@ -154,114 +197,119 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<Form onSubmit={handleSubmit(saveDueDate)}>
|
<DateRangeInputs>
|
||||||
<FormField>
|
<DatePicker
|
||||||
<DueDateInput
|
selected={startDate}
|
||||||
id="endDate"
|
onChange={date => setStartDate(date)}
|
||||||
name="endDate"
|
popperClassName="picker-hidden"
|
||||||
width="100%"
|
dateFormat="yyyy-MM-dd"
|
||||||
variant="alternate"
|
disabledKeyboardNavigation
|
||||||
label="Date"
|
isClearable
|
||||||
defaultValue={now.format('YYYY-MM-DD')}
|
placeholderText="Select due date"
|
||||||
ref={register({
|
/>
|
||||||
required: 'End date is required.',
|
{isRange ? (
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</FormField>
|
|
||||||
<FormField>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
defaultValue={now.toDate()}
|
|
||||||
name="endTime"
|
|
||||||
render={({ onChange, onBlur, value }) => (
|
|
||||||
<DatePicker
|
|
||||||
onChange={onChange}
|
|
||||||
selected={value}
|
|
||||||
onBlur={onBlur}
|
|
||||||
showTimeSelect
|
|
||||||
showTimeSelectOnly
|
|
||||||
timeIntervals={15}
|
|
||||||
timeCaption="Time"
|
|
||||||
dateFormat="h:mm aa"
|
|
||||||
customInput={<CustomTimeInput />}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormField>
|
|
||||||
<DueDatePickerWrapper>
|
|
||||||
<DatePicker
|
<DatePicker
|
||||||
useWeekdaysShort
|
|
||||||
renderCustomHeader={({
|
|
||||||
date,
|
|
||||||
changeYear,
|
|
||||||
changeMonth,
|
|
||||||
decreaseMonth,
|
|
||||||
increaseMonth,
|
|
||||||
prevMonthButtonDisabled,
|
|
||||||
nextMonthButtonDisabled,
|
|
||||||
}) => (
|
|
||||||
<HeaderActions>
|
|
||||||
<HeaderButton onClick={decreaseMonth} disabled={prevMonthButtonDisabled}>
|
|
||||||
Prev
|
|
||||||
</HeaderButton>
|
|
||||||
<HeaderSelectLabel>
|
|
||||||
{months[date.getMonth()]}
|
|
||||||
<HeaderSelect
|
|
||||||
value={getYear(date)}
|
|
||||||
onChange={({ target: { value } }) => changeYear(parseInt(value, 10))}
|
|
||||||
>
|
|
||||||
{years.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</HeaderSelect>
|
|
||||||
</HeaderSelectLabel>
|
|
||||||
<HeaderSelectLabel>
|
|
||||||
{date.getFullYear()}
|
|
||||||
<HeaderSelect
|
|
||||||
value={months[getMonth(date)]}
|
|
||||||
onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}
|
|
||||||
>
|
|
||||||
{months.map(option => (
|
|
||||||
<option key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</HeaderSelect>
|
|
||||||
</HeaderSelectLabel>
|
|
||||||
|
|
||||||
<HeaderButton onClick={increaseMonth} disabled={nextMonthButtonDisabled}>
|
|
||||||
Next
|
|
||||||
</HeaderButton>
|
|
||||||
</HeaderActions>
|
|
||||||
)}
|
|
||||||
selected={startDate}
|
selected={startDate}
|
||||||
inline
|
isClearable
|
||||||
onChange={date => {
|
onChange={date => setStartDate(date)}
|
||||||
if (date) {
|
popperClassName="picker-hidden"
|
||||||
setStartDate(date);
|
dateFormat="yyyy-MM-dd"
|
||||||
}
|
placeholderText="Select from date"
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</DueDatePickerWrapper>
|
) : (
|
||||||
<ActionWrapper>
|
<AddDateRange>Add date range</AddDateRange>
|
||||||
<ConfirmAddDueDate type="submit" onClick={NOOP}>
|
)}
|
||||||
Save
|
</DateRangeInputs>
|
||||||
</ConfirmAddDueDate>
|
<DatePicker
|
||||||
<RemoveDueDate
|
selected={startDate}
|
||||||
variant="outline"
|
onChange={date => setStartDate(date)}
|
||||||
color="danger"
|
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))}
|
||||||
|
>
|
||||||
|
{months.map(option => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</HeaderSelect>
|
||||||
|
</HeaderSelectLabel>
|
||||||
|
<HeaderSelectLabel>
|
||||||
|
{date.getFullYear()}
|
||||||
|
<HeaderSelect value={getYear(date)} onChange={({ target: { value } }) => changeYear(parseInt(value, 10))}>
|
||||||
|
{years.map(option => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</HeaderSelect>
|
||||||
|
</HeaderSelectLabel>
|
||||||
|
|
||||||
|
<HeaderButton onClick={increaseMonth} disabled={nextMonthButtonDisabled}>
|
||||||
|
Next
|
||||||
|
</HeaderButton>
|
||||||
|
</HeaderActions>
|
||||||
|
)}
|
||||||
|
inline
|
||||||
|
/>
|
||||||
|
<ActionsSeparator />
|
||||||
|
{hasTime && (
|
||||||
|
<ActionsWrapper>
|
||||||
|
<ActionClock width={16} height={16} />
|
||||||
|
<ActionLabel>Due Time</ActionLabel>
|
||||||
|
<DatePicker
|
||||||
|
selected={startDate}
|
||||||
|
onChange={date => {
|
||||||
|
setStartDate(date);
|
||||||
|
}}
|
||||||
|
showTimeSelect
|
||||||
|
showTimeSelectOnly
|
||||||
|
timeIntervals={15}
|
||||||
|
timeCaption="Time"
|
||||||
|
dateFormat="h:mm aa"
|
||||||
|
/>
|
||||||
|
<ActionIcon onClick={() => enableTime(false)}>
|
||||||
|
<Cross width={16} height={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</ActionsWrapper>
|
||||||
|
)}
|
||||||
|
<ActionsWrapper>
|
||||||
|
{!hasTime && (
|
||||||
|
<ActionIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onRemoveDueDate(task);
|
if (startDate === null) {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(12, 30, 0);
|
||||||
|
setStartDate(today);
|
||||||
|
}
|
||||||
|
enableTime(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Remove
|
<Clock width={16} height={16} />
|
||||||
</RemoveDueDate>
|
</ActionIcon>
|
||||||
</ActionWrapper>
|
)}
|
||||||
</Form>
|
<ClearButton onClick={() => setStartDate(null)}>{hasTime ? 'Clear all' : 'Clear'}</ClearButton>
|
||||||
|
</ActionsWrapper>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -341,7 +341,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{task.dueDate ? (
|
{task.dueDate ? (
|
||||||
<SidebarButtonText>{dayjs(task.dueDate).format('MMM D [at] h:mm A')}</SidebarButtonText>
|
<SidebarButtonText>
|
||||||
|
{dayjs(task.dueDate).format(task.hasTime ? 'MMM D [at] h:mm A' : 'MMMM D')}
|
||||||
|
</SidebarButtonText>
|
||||||
) : (
|
) : (
|
||||||
<SidebarButtonText>No due date</SidebarButtonText>
|
<SidebarButtonText>No due date</SidebarButtonText>
|
||||||
)}
|
)}
|
||||||
|
@ -215,6 +215,7 @@ export type Task = {
|
|||||||
position: Scalars['Float'];
|
position: Scalars['Float'];
|
||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate?: Maybe<Scalars['Time']>;
|
||||||
|
hasTime: Scalars['Boolean'];
|
||||||
complete: Scalars['Boolean'];
|
complete: Scalars['Boolean'];
|
||||||
completedAt?: Maybe<Scalars['Time']>;
|
completedAt?: Maybe<Scalars['Time']>;
|
||||||
assigned: Array<Member>;
|
assigned: Array<Member>;
|
||||||
@ -883,6 +884,7 @@ export type UpdateTaskLocationPayload = {
|
|||||||
|
|
||||||
export type UpdateTaskDueDate = {
|
export type UpdateTaskDueDate = {
|
||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
|
hasTime: Scalars['Boolean'];
|
||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate?: Maybe<Scalars['Time']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1451,7 +1453,7 @@ export type FindTaskQuery = (
|
|||||||
{ __typename?: 'Query' }
|
{ __typename?: 'Query' }
|
||||||
& { findTask: (
|
& { findTask: (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'position' | 'complete'>
|
& Pick<Task, 'id' | 'name' | 'description' | 'dueDate' | 'position' | 'complete' | 'hasTime'>
|
||||||
& { taskGroup: (
|
& { taskGroup: (
|
||||||
{ __typename?: 'TaskGroup' }
|
{ __typename?: 'TaskGroup' }
|
||||||
& Pick<TaskGroup, 'id' | 'name'>
|
& Pick<TaskGroup, 'id' | 'name'>
|
||||||
@ -2314,6 +2316,7 @@ export type UpdateTaskDescriptionMutation = (
|
|||||||
export type UpdateTaskDueDateMutationVariables = Exact<{
|
export type UpdateTaskDueDateMutationVariables = Exact<{
|
||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate?: Maybe<Scalars['Time']>;
|
||||||
|
hasTime: Scalars['Boolean'];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
@ -2321,7 +2324,7 @@ export type UpdateTaskDueDateMutation = (
|
|||||||
{ __typename?: 'Mutation' }
|
{ __typename?: 'Mutation' }
|
||||||
& { updateTaskDueDate: (
|
& { updateTaskDueDate: (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'dueDate'>
|
& Pick<Task, 'id' | 'dueDate' | 'hasTime'>
|
||||||
) }
|
) }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -3017,6 +3020,7 @@ export const FindTaskDocument = gql`
|
|||||||
dueDate
|
dueDate
|
||||||
position
|
position
|
||||||
complete
|
complete
|
||||||
|
hasTime
|
||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@ -4692,10 +4696,13 @@ export type UpdateTaskDescriptionMutationHookResult = ReturnType<typeof useUpdat
|
|||||||
export type UpdateTaskDescriptionMutationResult = ApolloReactCommon.MutationResult<UpdateTaskDescriptionMutation>;
|
export type UpdateTaskDescriptionMutationResult = ApolloReactCommon.MutationResult<UpdateTaskDescriptionMutation>;
|
||||||
export type UpdateTaskDescriptionMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
export type UpdateTaskDescriptionMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
||||||
export const UpdateTaskDueDateDocument = gql`
|
export const UpdateTaskDueDateDocument = gql`
|
||||||
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time) {
|
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!) {
|
||||||
updateTaskDueDate(input: {taskID: $taskID, dueDate: $dueDate}) {
|
updateTaskDueDate(
|
||||||
|
input: {taskID: $taskID, dueDate: $dueDate, hasTime: $hasTime}
|
||||||
|
) {
|
||||||
id
|
id
|
||||||
dueDate
|
dueDate
|
||||||
|
hasTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -4716,6 +4723,7 @@ export type UpdateTaskDueDateMutationFn = ApolloReactCommon.MutationFunction<Upd
|
|||||||
* variables: {
|
* variables: {
|
||||||
* taskID: // value for 'taskID'
|
* taskID: // value for 'taskID'
|
||||||
* dueDate: // value for 'dueDate'
|
* dueDate: // value for 'dueDate'
|
||||||
|
* hasTime: // value for 'hasTime'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
@ -5167,4 +5175,4 @@ export function useUsersLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOp
|
|||||||
}
|
}
|
||||||
export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>;
|
export type UsersQueryHookResult = ReturnType<typeof useUsersQuery>;
|
||||||
export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>;
|
export type UsersLazyQueryHookResult = ReturnType<typeof useUsersLazyQuery>;
|
||||||
export type UsersQueryResult = ApolloReactCommon.QueryResult<UsersQuery, UsersQueryVariables>;
|
export type UsersQueryResult = ApolloReactCommon.QueryResult<UsersQuery, UsersQueryVariables>;
|
@ -6,6 +6,7 @@ query findTask($taskID: UUID!) {
|
|||||||
dueDate
|
dueDate
|
||||||
position
|
position
|
||||||
complete
|
complete
|
||||||
|
hasTime
|
||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time) {
|
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!) {
|
||||||
updateTaskDueDate (
|
updateTaskDueDate (
|
||||||
input: {
|
input: {
|
||||||
taskID: $taskID
|
taskID: $taskID
|
||||||
dueDate: $dueDate
|
dueDate: $dueDate
|
||||||
|
hasTime: $hasTime
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
dueDate
|
dueDate
|
||||||
|
hasTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
frontend/src/types.d.ts
vendored
1
frontend/src/types.d.ts
vendored
@ -102,6 +102,7 @@ type Task = {
|
|||||||
name: string;
|
name: string;
|
||||||
badges?: TaskBadges;
|
badges?: TaskBadges;
|
||||||
position: number;
|
position: number;
|
||||||
|
hasTime?: boolean;
|
||||||
dueDate?: string;
|
dueDate?: string;
|
||||||
complete?: boolean;
|
complete?: boolean;
|
||||||
completedAt?: string | null;
|
completedAt?: string | null;
|
||||||
|
@ -102,6 +102,7 @@ type Task struct {
|
|||||||
DueDate sql.NullTime `json:"due_date"`
|
DueDate sql.NullTime `json:"due_date"`
|
||||||
Complete bool `json:"complete"`
|
Complete bool `json:"complete"`
|
||||||
CompletedAt sql.NullTime `json:"completed_at"`
|
CompletedAt sql.NullTime `json:"completed_at"`
|
||||||
|
HasTime bool `json:"has_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskActivity struct {
|
type TaskActivity struct {
|
||||||
|
@ -34,7 +34,7 @@ UPDATE task SET name = $2 WHERE task_id = $1 RETURNING *;
|
|||||||
DELETE FROM task where task_group_id = $1;
|
DELETE FROM task where task_group_id = $1;
|
||||||
|
|
||||||
-- name: UpdateTaskDueDate :one
|
-- name: UpdateTaskDueDate :one
|
||||||
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING *;
|
UPDATE task SET due_date = $2, has_time = $3 WHERE task_id = $1 RETURNING *;
|
||||||
|
|
||||||
-- name: SetTaskComplete :one
|
-- name: SetTaskComplete :one
|
||||||
UPDATE task SET complete = $2, completed_at = $3 WHERE task_id = $1 RETURNING *;
|
UPDATE task SET complete = $2, completed_at = $3 WHERE task_id = $1 RETURNING *;
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
|
|
||||||
const createTask = `-- name: CreateTask :one
|
const createTask = `-- name: CreateTask :one
|
||||||
INSERT INTO task (task_group_id, created_at, name, position)
|
INSERT INTO task (task_group_id, created_at, name, position)
|
||||||
VALUES($1, $2, $3, $4) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
VALUES($1, $2, $3, $4) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTaskParams struct {
|
type CreateTaskParams struct {
|
||||||
@ -41,13 +41,14 @@ func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, e
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const createTaskAll = `-- name: CreateTaskAll :one
|
const createTaskAll = `-- name: CreateTaskAll :one
|
||||||
INSERT INTO task (task_group_id, created_at, name, position, description, complete, due_date)
|
INSERT INTO task (task_group_id, created_at, name, position, description, complete, due_date)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateTaskAllParams struct {
|
type CreateTaskAllParams struct {
|
||||||
@ -81,6 +82,7 @@ func (q *Queries) CreateTaskAll(ctx context.Context, arg CreateTaskAllParams) (T
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@ -158,7 +160,7 @@ func (q *Queries) DeleteTasksByTaskGroupID(ctx context.Context, taskGroupID uuid
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getAllTasks = `-- name: GetAllTasks :many
|
const getAllTasks = `-- name: GetAllTasks :many
|
||||||
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at FROM task
|
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time FROM task
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
|
func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
|
||||||
@ -180,6 +182,7 @@ func (q *Queries) GetAllTasks(ctx context.Context) ([]Task, error) {
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -243,7 +246,7 @@ func (q *Queries) GetProjectIDForTask(ctx context.Context, taskID uuid.UUID) (uu
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getTaskByID = `-- name: GetTaskByID :one
|
const getTaskByID = `-- name: GetTaskByID :one
|
||||||
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at FROM task WHERE task_id = $1
|
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time FROM task WHERE task_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error) {
|
func (q *Queries) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, error) {
|
||||||
@ -259,12 +262,13 @@ func (q *Queries) GetTaskByID(ctx context.Context, taskID uuid.UUID) (Task, erro
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTasksForTaskGroupID = `-- name: GetTasksForTaskGroupID :many
|
const getTasksForTaskGroupID = `-- name: GetTasksForTaskGroupID :many
|
||||||
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at FROM task WHERE task_group_id = $1
|
SELECT task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time FROM task WHERE task_group_id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error) {
|
func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.UUID) ([]Task, error) {
|
||||||
@ -286,6 +290,7 @@ func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.U
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -301,7 +306,7 @@ func (q *Queries) GetTasksForTaskGroupID(ctx context.Context, taskGroupID uuid.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
const setTaskComplete = `-- name: SetTaskComplete :one
|
const setTaskComplete = `-- name: SetTaskComplete :one
|
||||||
UPDATE task SET complete = $2, completed_at = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
UPDATE task SET complete = $2, completed_at = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type SetTaskCompleteParams struct {
|
type SetTaskCompleteParams struct {
|
||||||
@ -323,12 +328,13 @@ func (q *Queries) SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskComment = `-- name: UpdateTaskComment :one
|
const updateTaskComment = `-- name: UpdateTaskComment :one
|
||||||
UPDATE task_comment SET message = $2, updated_at = $3 WHERE task_comment_id = $1 RETURNING task_comment_id, task_id, created_at, updated_at, created_by, pinned, message
|
UPDATE task_comment SET message = $2, updated_at = COALESCE($3, updated_at) WHERE task_comment_id = $1 RETURNING task_comment_id, task_id, created_at, updated_at, created_by, pinned, message
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTaskCommentParams struct {
|
type UpdateTaskCommentParams struct {
|
||||||
@ -353,7 +359,7 @@ func (q *Queries) UpdateTaskComment(ctx context.Context, arg UpdateTaskCommentPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskDescription = `-- name: UpdateTaskDescription :one
|
const updateTaskDescription = `-- name: UpdateTaskDescription :one
|
||||||
UPDATE task SET description = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
UPDATE task SET description = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTaskDescriptionParams struct {
|
type UpdateTaskDescriptionParams struct {
|
||||||
@ -374,21 +380,23 @@ func (q *Queries) UpdateTaskDescription(ctx context.Context, arg UpdateTaskDescr
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskDueDate = `-- name: UpdateTaskDueDate :one
|
const updateTaskDueDate = `-- name: UpdateTaskDueDate :one
|
||||||
UPDATE task SET due_date = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
UPDATE task SET due_date = $2, has_time = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTaskDueDateParams struct {
|
type UpdateTaskDueDateParams struct {
|
||||||
TaskID uuid.UUID `json:"task_id"`
|
TaskID uuid.UUID `json:"task_id"`
|
||||||
DueDate sql.NullTime `json:"due_date"`
|
DueDate sql.NullTime `json:"due_date"`
|
||||||
|
HasTime bool `json:"has_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error) {
|
func (q *Queries) UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDateParams) (Task, error) {
|
||||||
row := q.db.QueryRowContext(ctx, updateTaskDueDate, arg.TaskID, arg.DueDate)
|
row := q.db.QueryRowContext(ctx, updateTaskDueDate, arg.TaskID, arg.DueDate, arg.HasTime)
|
||||||
var i Task
|
var i Task
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.TaskID,
|
&i.TaskID,
|
||||||
@ -400,12 +408,13 @@ func (q *Queries) UpdateTaskDueDate(ctx context.Context, arg UpdateTaskDueDatePa
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskLocation = `-- name: UpdateTaskLocation :one
|
const updateTaskLocation = `-- name: UpdateTaskLocation :one
|
||||||
UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
UPDATE task SET task_group_id = $2, position = $3 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTaskLocationParams struct {
|
type UpdateTaskLocationParams struct {
|
||||||
@ -427,12 +436,13 @@ func (q *Queries) UpdateTaskLocation(ctx context.Context, arg UpdateTaskLocation
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskName = `-- name: UpdateTaskName :one
|
const updateTaskName = `-- name: UpdateTaskName :one
|
||||||
UPDATE task SET name = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
UPDATE task SET name = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTaskNameParams struct {
|
type UpdateTaskNameParams struct {
|
||||||
@ -453,12 +463,13 @@ func (q *Queries) UpdateTaskName(ctx context.Context, arg UpdateTaskNameParams)
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTaskPosition = `-- name: UpdateTaskPosition :one
|
const updateTaskPosition = `-- name: UpdateTaskPosition :one
|
||||||
UPDATE task SET position = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at
|
UPDATE task SET position = $2 WHERE task_id = $1 RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTaskPositionParams struct {
|
type UpdateTaskPositionParams struct {
|
||||||
@ -479,6 +490,7 @@ func (q *Queries) UpdateTaskPosition(ctx context.Context, arg UpdateTaskPosition
|
|||||||
&i.DueDate,
|
&i.DueDate,
|
||||||
&i.Complete,
|
&i.Complete,
|
||||||
&i.CompletedAt,
|
&i.CompletedAt,
|
||||||
|
&i.HasTime,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
@ -383,6 +383,7 @@ type ComplexityRoot struct {
|
|||||||
CreatedAt func(childComplexity int) int
|
CreatedAt func(childComplexity int) int
|
||||||
Description func(childComplexity int) int
|
Description func(childComplexity int) int
|
||||||
DueDate func(childComplexity int) int
|
DueDate func(childComplexity int) int
|
||||||
|
HasTime func(childComplexity int) int
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
Labels func(childComplexity int) int
|
Labels func(childComplexity int) int
|
||||||
Name func(childComplexity int) int
|
Name func(childComplexity int) int
|
||||||
@ -2376,6 +2377,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Task.DueDate(childComplexity), true
|
return e.complexity.Task.DueDate(childComplexity), true
|
||||||
|
|
||||||
|
case "Task.hasTime":
|
||||||
|
if e.complexity.Task.HasTime == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Task.HasTime(childComplexity), true
|
||||||
|
|
||||||
case "Task.id":
|
case "Task.id":
|
||||||
if e.complexity.Task.ID == nil {
|
if e.complexity.Task.ID == nil {
|
||||||
break
|
break
|
||||||
@ -3135,6 +3143,7 @@ type Task {
|
|||||||
position: Float!
|
position: Float!
|
||||||
description: String
|
description: String
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
|
hasTime: Boolean!
|
||||||
complete: Boolean!
|
complete: Boolean!
|
||||||
completedAt: Time
|
completedAt: Time
|
||||||
assigned: [Member!]!
|
assigned: [Member!]!
|
||||||
@ -3477,6 +3486,7 @@ type UpdateTaskLocationPayload {
|
|||||||
|
|
||||||
input UpdateTaskDueDate {
|
input UpdateTaskDueDate {
|
||||||
taskID: UUID!
|
taskID: UUID!
|
||||||
|
hasTime: Boolean!
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13558,6 +13568,40 @@ func (ec *executionContext) _Task_dueDate(ctx context.Context, field graphql.Col
|
|||||||
return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
|
return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Task_hasTime(ctx context.Context, field graphql.CollectedField, obj *db.Task) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "Task",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.HasTime, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(bool)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Task_complete(ctx context.Context, field graphql.CollectedField, obj *db.Task) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Task_complete(ctx context.Context, field graphql.CollectedField, obj *db.Task) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -18596,6 +18640,12 @@ func (ec *executionContext) unmarshalInputUpdateTaskDueDate(ctx context.Context,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "hasTime":
|
||||||
|
var err error
|
||||||
|
it.HasTime, err = ec.unmarshalNBoolean2bool(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
case "dueDate":
|
case "dueDate":
|
||||||
var err error
|
var err error
|
||||||
it.DueDate, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v)
|
it.DueDate, err = ec.unmarshalOTime2ᚖtimeᚐTime(ctx, v)
|
||||||
@ -20968,6 +21018,11 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj
|
|||||||
res = ec._Task_dueDate(ctx, field, obj)
|
res = ec._Task_dueDate(ctx, field, obj)
|
||||||
return res
|
return res
|
||||||
})
|
})
|
||||||
|
case "hasTime":
|
||||||
|
out.Values[i] = ec._Task_hasTime(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
atomic.AddUint32(&invalids, 1)
|
||||||
|
}
|
||||||
case "complete":
|
case "complete":
|
||||||
out.Values[i] = ec._Task_complete(ctx, field, obj)
|
out.Values[i] = ec._Task_complete(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
|
@ -519,6 +519,7 @@ type UpdateTaskDescriptionInput struct {
|
|||||||
|
|
||||||
type UpdateTaskDueDate struct {
|
type UpdateTaskDueDate struct {
|
||||||
TaskID uuid.UUID `json:"taskID"`
|
TaskID uuid.UUID `json:"taskID"`
|
||||||
|
HasTime bool `json:"hasTime"`
|
||||||
DueDate *time.Time `json:"dueDate"`
|
DueDate *time.Time `json:"dueDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +175,7 @@ type Task {
|
|||||||
position: Float!
|
position: Float!
|
||||||
description: String
|
description: String
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
|
hasTime: Boolean!
|
||||||
complete: Boolean!
|
complete: Boolean!
|
||||||
completedAt: Time
|
completedAt: Time
|
||||||
assigned: [Member!]!
|
assigned: [Member!]!
|
||||||
@ -517,6 +518,7 @@ type UpdateTaskLocationPayload {
|
|||||||
|
|
||||||
input UpdateTaskDueDate {
|
input UpdateTaskDueDate {
|
||||||
taskID: UUID!
|
taskID: UUID!
|
||||||
|
hasTime: Boolean!
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -943,3 +945,4 @@ type DeleteUserAccountPayload {
|
|||||||
ok: Boolean!
|
ok: Boolean!
|
||||||
userAccount: UserAccount!
|
userAccount: UserAccount!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,28 +423,35 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
|
|||||||
activityType = TASK_DUE_DATE_CHANGED
|
activityType = TASK_DUE_DATE_CHANGED
|
||||||
data["PrevDueDate"] = prevTask.DueDate.Time.String()
|
data["PrevDueDate"] = prevTask.DueDate.Time.String()
|
||||||
data["CurDueDate"] = input.DueDate.String()
|
data["CurDueDate"] = input.DueDate.String()
|
||||||
} else {
|
} else if input.DueDate != nil {
|
||||||
data["DueDate"] = input.DueDate.String()
|
data["DueDate"] = input.DueDate.String()
|
||||||
}
|
}
|
||||||
var dueDate sql.NullTime
|
var dueDate sql.NullTime
|
||||||
|
log.WithField("dueDate", input.DueDate).Info("before ptr!")
|
||||||
if input.DueDate == nil {
|
if input.DueDate == nil {
|
||||||
dueDate = sql.NullTime{Valid: false, Time: time.Now()}
|
dueDate = sql.NullTime{Valid: false, Time: time.Now()}
|
||||||
} else {
|
} else {
|
||||||
dueDate = sql.NullTime{Valid: true, Time: *input.DueDate}
|
dueDate = sql.NullTime{Valid: true, Time: *input.DueDate}
|
||||||
}
|
}
|
||||||
task, err := r.Repository.UpdateTaskDueDate(ctx, db.UpdateTaskDueDateParams{
|
var task db.Task
|
||||||
TaskID: input.TaskID,
|
if !(input.DueDate == nil && !prevTask.DueDate.Valid) {
|
||||||
DueDate: dueDate,
|
task, err = r.Repository.UpdateTaskDueDate(ctx, db.UpdateTaskDueDateParams{
|
||||||
})
|
TaskID: input.TaskID,
|
||||||
createdAt := time.Now().UTC()
|
DueDate: dueDate,
|
||||||
d, err := json.Marshal(data)
|
HasTime: input.HasTime,
|
||||||
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
|
})
|
||||||
TaskID: task.TaskID,
|
createdAt := time.Now().UTC()
|
||||||
Data: d,
|
d, _ := json.Marshal(data)
|
||||||
CausedBy: userID,
|
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
|
||||||
CreatedAt: createdAt,
|
TaskID: task.TaskID,
|
||||||
ActivityTypeID: activityType,
|
Data: d,
|
||||||
})
|
CausedBy: userID,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
ActivityTypeID: activityType,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
task, err = r.Repository.GetTaskByID(ctx, input.TaskID)
|
||||||
|
}
|
||||||
|
|
||||||
return &task, err
|
return &task, err
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,7 @@ type Task {
|
|||||||
position: Float!
|
position: Float!
|
||||||
description: String
|
description: String
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
|
hasTime: Boolean!
|
||||||
complete: Boolean!
|
complete: Boolean!
|
||||||
completedAt: Time
|
completedAt: Time
|
||||||
assigned: [Member!]!
|
assigned: [Member!]!
|
||||||
|
@ -49,6 +49,7 @@ type UpdateTaskLocationPayload {
|
|||||||
|
|
||||||
input UpdateTaskDueDate {
|
input UpdateTaskDueDate {
|
||||||
taskID: UUID!
|
taskID: UUID!
|
||||||
|
hasTime: Boolean!
|
||||||
dueDate: Time
|
dueDate: Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
migrations/0063_add-use_time-to-task-due_date.up.sql
Normal file
2
migrations/0063_add-use_time-to-task-due_date.up.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE task ADD COLUMN has_time boolean NOT NULL DEFAULT false;
|
||||||
|
UPDATE task SET has_time = true;
|
Loading…
Reference in New Issue
Block a user