feat: redesign due date manager
This commit is contained in:
parent
df6140a10f
commit
0d00fc7518
@ -61,7 +61,6 @@ const Routes: React.FC = () => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
console.log('loading', loading);
|
|
||||||
if (loading) return null;
|
if (loading) return null;
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
|
@ -9,7 +9,6 @@ const Auth = () => {
|
|||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const location = useLocation<{ redirect: string } | undefined>();
|
const location = useLocation<{ redirect: string } | undefined>();
|
||||||
const { setUser } = useContext(UserContext);
|
const { setUser } = useContext(UserContext);
|
||||||
console.log('auth');
|
|
||||||
const login = (
|
const login = (
|
||||||
data: LoginFormData,
|
data: LoginFormData,
|
||||||
setComplete: (val: boolean) => void,
|
setComplete: (val: boolean) => void,
|
||||||
|
@ -562,13 +562,36 @@ const Projects = () => {
|
|||||||
onCancel={() => null}
|
onCancel={() => null}
|
||||||
onDueDateChange={(task, dueDate, hasTime) => {
|
onDueDateChange={(task, dueDate, hasTime) => {
|
||||||
if (dateEditor.task) {
|
if (dateEditor.task) {
|
||||||
updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate, hasTime } });
|
hidePopup();
|
||||||
setDateEditor((prev) => ({ ...prev, task: { ...task, dueDate: dueDate.toISOString(), hasTime } }));
|
updateTaskDueDate({
|
||||||
|
variables: {
|
||||||
|
taskID: dateEditor.task.id,
|
||||||
|
dueDate,
|
||||||
|
hasTime,
|
||||||
|
deleteNotifications: [],
|
||||||
|
updateNotifications: [],
|
||||||
|
createNotifications: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setDateEditor((prev) => ({
|
||||||
|
...prev,
|
||||||
|
task: { ...task, dueDate: { at: dueDate.toISOString(), notifications: [] }, hasTime },
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onRemoveDueDate={(task) => {
|
onRemoveDueDate={(task) => {
|
||||||
if (dateEditor.task) {
|
if (dateEditor.task) {
|
||||||
updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate: null, hasTime: false } });
|
hidePopup();
|
||||||
|
updateTaskDueDate({
|
||||||
|
variables: {
|
||||||
|
taskID: dateEditor.task.id,
|
||||||
|
dueDate: null,
|
||||||
|
hasTime: false,
|
||||||
|
deleteNotifications: [],
|
||||||
|
updateNotifications: [],
|
||||||
|
createNotifications: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
setDateEditor((prev) => ({ ...prev, task: { ...task, hasTime: false } }));
|
setDateEditor((prev) => ({ ...prev, task: { ...task, hasTime: false } }));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -655,8 +678,8 @@ const Projects = () => {
|
|||||||
if (a.dueDate === null && b.dueDate === null) return 0;
|
if (a.dueDate === null && b.dueDate === null) return 0;
|
||||||
if (a.dueDate === null && b.dueDate !== null) return 1;
|
if (a.dueDate === null && b.dueDate !== null) return 1;
|
||||||
if (a.dueDate !== null && b.dueDate === null) return -1;
|
if (a.dueDate !== null && b.dueDate === null) return -1;
|
||||||
const first = dayjs(a.dueDate);
|
const first = dayjs(a.dueDate.at);
|
||||||
const second = dayjs(b.dueDate);
|
const second = dayjs(b.dueDate.at);
|
||||||
if (first.isSame(second, 'minute')) return 0;
|
if (first.isSame(second, 'minute')) return 0;
|
||||||
if (first.isAfter(second)) return -1;
|
if (first.isAfter(second)) return -1;
|
||||||
return 1;
|
return 1;
|
||||||
@ -792,10 +815,19 @@ const Projects = () => {
|
|||||||
history.push(`${match.url}/c/${task.id}`);
|
history.push(`${match.url}/c/${task.id}`);
|
||||||
}}
|
}}
|
||||||
onRemoveDueDate={() => {
|
onRemoveDueDate={() => {
|
||||||
updateTaskDueDate({ variables: { taskID: task.id, dueDate: null, hasTime: false } });
|
updateTaskDueDate({
|
||||||
|
variables: {
|
||||||
|
taskID: task.id,
|
||||||
|
dueDate: null,
|
||||||
|
hasTime: false,
|
||||||
|
deleteNotifications: [],
|
||||||
|
updateNotifications: [],
|
||||||
|
createNotifications: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
project={projectName ?? 'none'}
|
project={projectName ?? 'none'}
|
||||||
dueDate={task.dueDate}
|
dueDate={task.dueDate.at}
|
||||||
hasTime={task.hasTime ?? false}
|
hasTime={task.hasTime ?? false}
|
||||||
name={task.name}
|
name={task.name}
|
||||||
onEditName={(name) => updateTaskName({ variables: { taskID: task.id, name } })}
|
onEditName={(name) => updateTaskName({ variables: { taskID: task.id, name } })}
|
||||||
@ -821,7 +853,9 @@ const Projects = () => {
|
|||||||
<EditorCell width={120}>
|
<EditorCell width={120}>
|
||||||
<DueDateEditorLabel>
|
<DueDateEditorLabel>
|
||||||
{dateEditor.task.dueDate
|
{dateEditor.task.dueDate
|
||||||
? dayjs(dateEditor.task.dueDate).format(dateEditor.task.hasTime ? 'MMM D [at] h:mm A' : 'MMM D')
|
? dayjs(dateEditor.task.dueDate.at).format(
|
||||||
|
dateEditor.task.hasTime ? 'MMM D [at] h:mm A' : 'MMM D',
|
||||||
|
)
|
||||||
: ''}
|
: ''}
|
||||||
</DueDateEditorLabel>
|
</DueDateEditorLabel>
|
||||||
</EditorCell>
|
</EditorCell>
|
||||||
|
@ -446,7 +446,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
|
|||||||
checklist: null,
|
checklist: null,
|
||||||
},
|
},
|
||||||
position,
|
position,
|
||||||
dueDate: null,
|
dueDate: { at: null },
|
||||||
description: null,
|
description: null,
|
||||||
labels: [],
|
labels: [],
|
||||||
assigned: [],
|
assigned: [],
|
||||||
@ -801,12 +801,30 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
|
|||||||
<DueDateManager
|
<DueDateManager
|
||||||
task={task}
|
task={task}
|
||||||
onRemoveDueDate={(t) => {
|
onRemoveDueDate={(t) => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
|
hidePopup();
|
||||||
// hidePopup();
|
updateTaskDueDate({
|
||||||
|
variables: {
|
||||||
|
taskID: t.id,
|
||||||
|
dueDate: null,
|
||||||
|
hasTime: false,
|
||||||
|
deleteNotifications: [],
|
||||||
|
updateNotifications: [],
|
||||||
|
createNotifications: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onDueDateChange={(t, newDueDate, hasTime) => {
|
onDueDateChange={(t, newDueDate, hasTime) => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate, hasTime } });
|
hidePopup();
|
||||||
// hidePopup();
|
updateTaskDueDate({
|
||||||
|
variables: {
|
||||||
|
taskID: t.id,
|
||||||
|
dueDate: newDueDate,
|
||||||
|
hasTime,
|
||||||
|
deleteNotifications: [],
|
||||||
|
updateNotifications: [],
|
||||||
|
createNotifications: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
onCancel={NOOP}
|
onCancel={NOOP}
|
||||||
/>
|
/>
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
useUpdateTaskChecklistItemLocationMutation,
|
useUpdateTaskChecklistItemLocationMutation,
|
||||||
useCreateTaskChecklistMutation,
|
useCreateTaskChecklistMutation,
|
||||||
useFindTaskQuery,
|
useFindTaskQuery,
|
||||||
|
DueDateNotificationDuration,
|
||||||
useUpdateTaskDueDateMutation,
|
useUpdateTaskDueDateMutation,
|
||||||
useSetTaskCompleteMutation,
|
useSetTaskCompleteMutation,
|
||||||
useAssignTaskMutation,
|
useAssignTaskMutation,
|
||||||
@ -647,12 +648,79 @@ const Details: React.FC<DetailsProps> = ({
|
|||||||
<DueDateManager
|
<DueDateManager
|
||||||
task={task}
|
task={task}
|
||||||
onRemoveDueDate={(t) => {
|
onRemoveDueDate={(t) => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
|
updateTaskDueDate({
|
||||||
// hidePopup();
|
variables: {
|
||||||
|
taskID: t.id,
|
||||||
|
dueDate: null,
|
||||||
|
hasTime: false,
|
||||||
|
deleteNotifications: t.dueDate.notifications
|
||||||
|
? t.dueDate.notifications.map((n) => ({ id: n.id }))
|
||||||
|
: [],
|
||||||
|
updateNotifications: [],
|
||||||
|
createNotifications: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
hidePopup();
|
||||||
}}
|
}}
|
||||||
onDueDateChange={(t, newDueDate, hasTime) => {
|
onDueDateChange={(t, newDueDate, hasTime, notifications) => {
|
||||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate, hasTime } });
|
const updatedNotifications = notifications.current
|
||||||
// hidePopup();
|
.filter((c) => c.externalId !== null)
|
||||||
|
.map((c) => {
|
||||||
|
let duration = DueDateNotificationDuration.Minute;
|
||||||
|
switch (c.duration.value) {
|
||||||
|
case 'hour':
|
||||||
|
duration = DueDateNotificationDuration.Hour;
|
||||||
|
break;
|
||||||
|
case 'day':
|
||||||
|
duration = DueDateNotificationDuration.Day;
|
||||||
|
break;
|
||||||
|
case 'week':
|
||||||
|
duration = DueDateNotificationDuration.Week;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: c.externalId ?? '',
|
||||||
|
period: c.period,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const newNotifications = notifications.current
|
||||||
|
.filter((c) => c.externalId === null)
|
||||||
|
.map((c) => {
|
||||||
|
let duration = DueDateNotificationDuration.Minute;
|
||||||
|
switch (c.duration.value) {
|
||||||
|
case 'hour':
|
||||||
|
duration = DueDateNotificationDuration.Hour;
|
||||||
|
break;
|
||||||
|
case 'day':
|
||||||
|
duration = DueDateNotificationDuration.Day;
|
||||||
|
break;
|
||||||
|
case 'week':
|
||||||
|
duration = DueDateNotificationDuration.Week;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
taskID: task.id,
|
||||||
|
period: c.period,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// const updatedNotifications = notifications.filter(c => c.externalId === null);
|
||||||
|
updateTaskDueDate({
|
||||||
|
variables: {
|
||||||
|
taskID: t.id,
|
||||||
|
dueDate: newDueDate,
|
||||||
|
hasTime,
|
||||||
|
createNotifications: newNotifications,
|
||||||
|
updateNotifications: updatedNotifications,
|
||||||
|
deleteNotifications: notifications.removed.map((n) => ({ id: n })),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
hidePopup();
|
||||||
}}
|
}}
|
||||||
onCancel={NOOP}
|
onCancel={NOOP}
|
||||||
/>
|
/>
|
||||||
|
@ -39,7 +39,6 @@ const UsersRegister = () => {
|
|||||||
.then(async (x) => {
|
.then(async (x) => {
|
||||||
const response = await x.json();
|
const response = await x.json();
|
||||||
const { setup } = response;
|
const { setup } = response;
|
||||||
console.log(response);
|
|
||||||
if (setup) {
|
if (setup) {
|
||||||
history.replace(`/confirm?confirmToken=xxxx`);
|
history.replace(`/confirm?confirmToken=xxxx`);
|
||||||
isRedirected = true;
|
isRedirected = true;
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import styled from 'styled-components';
|
import styled, { css } 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 ControlledInput from 'shared/components/ControlledInput';
|
import ControlledInput from 'shared/components/ControlledInput';
|
||||||
import { Clock } from 'shared/icons';
|
import { Bell, Clock } from 'shared/icons';
|
||||||
|
|
||||||
export const Wrapper = styled.div`
|
export const Wrapper = styled.div`
|
||||||
display: flex
|
display: flex
|
||||||
@ -22,27 +21,27 @@ display: flex
|
|||||||
& .react-datepicker__close-icon::after {
|
& .react-datepicker__close-icon::after {
|
||||||
background: none;
|
background: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
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};
|
||||||
}
|
}
|
||||||
& .react-datepicker__time-list-item {
|
& .react-datepicker__time-list-item {
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
}
|
}
|
||||||
& .react-datepicker__time-container .react-datepicker__time
|
& .react-datepicker__time-container .react-datepicker__time
|
||||||
.react-datepicker__time-box ul.react-datepicker__time-list
|
.react-datepicker__time-box ul.react-datepicker__time-list
|
||||||
li.react-datepicker__time-list-item:hover {
|
li.react-datepicker__time-list-item:hover {
|
||||||
color: ${props => props.theme.colors.text.secondary};
|
color: ${(props) => props.theme.colors.text.secondary};
|
||||||
background: ${props => props.theme.colors.bg.secondary};
|
background: ${(props) => props.theme.colors.bg.secondary};
|
||||||
}
|
}
|
||||||
& .react-datepicker__time-container .react-datepicker__time {
|
& .react-datepicker__time-container .react-datepicker__time {
|
||||||
background: ${props => props.theme.colors.bg.primary};
|
background: ${(props) => props.theme.colors.bg.primary};
|
||||||
}
|
}
|
||||||
& .react-datepicker--time-only {
|
& .react-datepicker--time-only {
|
||||||
background: ${props => props.theme.colors.bg.primary};
|
background: ${(props) => props.theme.colors.bg.primary};
|
||||||
border: 1px solid ${props => props.theme.colors.border};
|
border: 1px solid ${(props) => props.theme.colors.border};
|
||||||
}
|
}
|
||||||
|
|
||||||
& .react-datepicker * {
|
& .react-datepicker * {
|
||||||
@ -82,12 +81,12 @@ display: flex
|
|||||||
}
|
}
|
||||||
& .react-datepicker__day--selected {
|
& .react-datepicker__day--selected {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: ${props => props.theme.colors.primary};
|
background: ${(props) => props.theme.colors.primary};
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
& .react-datepicker__day--selected:hover {
|
& .react-datepicker__day--selected:hover {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: ${props => props.theme.colors.primary};
|
background: ${(props) => props.theme.colors.primary};
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
& .react-datepicker__header {
|
& .react-datepicker__header {
|
||||||
@ -95,12 +94,12 @@ display: flex
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
& .react-datepicker__header--time {
|
& .react-datepicker__header--time {
|
||||||
border-bottom: 1px solid ${props => props.theme.colors.border};
|
border-bottom: 1px solid ${(props) => props.theme.colors.border};
|
||||||
}
|
}
|
||||||
|
|
||||||
& .react-datepicker__input-container input {
|
& .react-datepicker__input-container input {
|
||||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
border-color: ${props => props.theme.colors.alternate};
|
border-color: ${(props) => props.theme.colors.alternate};
|
||||||
background: #262c49;
|
background: #262c49;
|
||||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
|
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
|
||||||
padding: 0.7rem;
|
padding: 0.7rem;
|
||||||
@ -114,7 +113,7 @@ padding: 0.7rem;
|
|||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
|
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
|
||||||
border: 1px solid rgba(115, 103, 240);
|
border: 1px solid rgba(115, 103, 240);
|
||||||
background: ${props => props.theme.colors.bg.primary};
|
background: ${(props) => props.theme.colors.bg.primary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -142,9 +141,9 @@ export const AddDateRange = styled.div`
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
color: ${props => mixin.rgba(props.theme.colors.primary, 0.8)};
|
color: ${(props) => mixin.rgba(props.theme.colors.primary, 0.8)};
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${props => mixin.rgba(props.theme.colors.primary, 1)};
|
color: ${(props) => mixin.rgba(props.theme.colors.primary, 1)};
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -201,18 +200,62 @@ export const ActionsWrapper = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
& .react-datepicker-wrapper {
|
& .react-datepicker-wrapper {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
width: 82px;
|
width: 86px;
|
||||||
}
|
}
|
||||||
& .react-datepicker__input-container input {
|
& .react-datepicker__input-container input {
|
||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& .react-period-select__indicators {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
& .react-period {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 86px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .react-period-select__single-value {
|
||||||
|
color: #c2c6dc;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
& .react-period-select__value-container {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
& .react-period-select__control {
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
min-height: 30px;
|
||||||
|
border-color: rgb(65, 69, 97);
|
||||||
|
background: #262c49;
|
||||||
|
box-shadow: 0 0 0 0 rgb(0 0 0 / 15%);
|
||||||
|
color: #c2c6dc;
|
||||||
|
padding-right: 12px;
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionClock = styled(Clock)`
|
export const ActionClock = styled(Clock)`
|
||||||
align-self: center;
|
align-self: center;
|
||||||
fill: ${props => props.theme.colors.primary};
|
fill: ${(props) => props.theme.colors.primary};
|
||||||
|
margin: 0 8px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionBell = styled(Bell)`
|
||||||
|
align-self: center;
|
||||||
|
fill: ${(props) => props.theme.colors.primary};
|
||||||
margin: 0 8px;
|
margin: 0 8px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
`;
|
`;
|
||||||
@ -222,7 +265,7 @@ export const ActionLabel = styled.div`
|
|||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionIcon = styled.div`
|
export const ActionIcon = styled.div<{ disabled?: boolean }>`
|
||||||
height: 36px;
|
height: 36px;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
min-width: 36px;
|
min-width: 36px;
|
||||||
@ -232,17 +275,25 @@ export const ActionIcon = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
svg {
|
svg {
|
||||||
fill: ${props => props.theme.colors.text.primary};
|
fill: ${(props) => props.theme.colors.text.primary};
|
||||||
transition-duration: 0.2s;
|
transition-duration: 0.2s;
|
||||||
transition-property: background, border, box-shadow, fill;
|
transition-property: background, border, box-shadow, fill;
|
||||||
}
|
}
|
||||||
&:hover svg {
|
&:hover svg {
|
||||||
fill: ${props => props.theme.colors.text.secondary};
|
fill: ${(props) => props.theme.colors.text.secondary};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${(props) =>
|
||||||
|
props.disabled &&
|
||||||
|
css`
|
||||||
|
opacity: 0.8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
`}
|
||||||
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ClearButton = styled.div`
|
export const ClearButton = styled.div`
|
||||||
@ -260,8 +311,38 @@ export const ClearButton = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition-duration: 0.2s;
|
transition-duration: 0.2s;
|
||||||
transition-property: background, border, box-shadow, color, fill;
|
transition-property: background, border, box-shadow, color, fill;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${props => props.theme.colors.text.secondary};
|
color: ${(props) => props.theme.colors.text.secondary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ControlWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RightWrapper = styled.div`
|
||||||
|
flex: 1 1 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LeftWrapper = styled.div`
|
||||||
|
flex: 1 1 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SaveButton = styled(Button)`
|
||||||
|
padding: 6px 12px;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 4px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const RemoveButton = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
@ -3,16 +3,21 @@ import dayjs from 'dayjs';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import DatePicker from 'react-datepicker';
|
import DatePicker from 'react-datepicker';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { colourStyles } from 'shared/components/Select';
|
||||||
|
import produce from 'immer';
|
||||||
|
import Select from 'react-select';
|
||||||
import 'react-datepicker/dist/react-datepicker.css';
|
import 'react-datepicker/dist/react-datepicker.css';
|
||||||
import { getYear, getMonth } from 'date-fns';
|
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 { Clock, Cross } from 'shared/icons';
|
import { Bell, Clock, Cross, Plus, Trash } from 'shared/icons';
|
||||||
import Select from 'react-select/src/Select';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
RemoveDueDate,
|
RemoveDueDate,
|
||||||
|
SaveButton,
|
||||||
|
RightWrapper,
|
||||||
|
LeftWrapper,
|
||||||
DueDateInput,
|
DueDateInput,
|
||||||
DueDatePickerWrapper,
|
DueDatePickerWrapper,
|
||||||
ConfirmAddDueDate,
|
ConfirmAddDueDate,
|
||||||
@ -24,11 +29,19 @@ import {
|
|||||||
ActionsSeparator,
|
ActionsSeparator,
|
||||||
ActionClock,
|
ActionClock,
|
||||||
ActionLabel,
|
ActionLabel,
|
||||||
|
ControlWrapper,
|
||||||
|
RemoveButton,
|
||||||
|
ActionBell,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
|
||||||
type DueDateManagerProps = {
|
type DueDateManagerProps = {
|
||||||
task: Task;
|
task: Task;
|
||||||
onDueDateChange: (task: Task, newDueDate: Date, hasTime: boolean) => void;
|
onDueDateChange: (
|
||||||
|
task: Task,
|
||||||
|
newDueDate: Date,
|
||||||
|
hasTime: boolean,
|
||||||
|
notifications: { current: Array<NotificationInternal>; removed: Array<string> },
|
||||||
|
) => void;
|
||||||
onRemoveDueDate: (task: Task) => void;
|
onRemoveDueDate: (task: Task) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
};
|
};
|
||||||
@ -41,6 +54,39 @@ const FormField = styled.div`
|
|||||||
width: 50%;
|
width: 50%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const NotificationCount = styled.input``;
|
||||||
|
|
||||||
|
const ActionPlus = styled(Plus)`
|
||||||
|
position: absolute;
|
||||||
|
fill: ${(props) => props.theme.colors.bg.primary} !important;
|
||||||
|
stroke: ${(props) => props.theme.colors.bg.primary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ActionInput = styled.input`
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 4px;
|
||||||
|
border-color: rgb(65, 69, 97);
|
||||||
|
background: #262c49;
|
||||||
|
box-shadow: 0 0 0 0 rgb(0 0 0 / 15%);
|
||||||
|
color: #c2c6dc;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 0 12px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
padding-top: 4px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 48px;
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
const HeaderSelectLabel = styled.div`
|
const HeaderSelectLabel = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -131,8 +177,69 @@ const HeaderActions = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const notificationPeriodOptions = [
|
||||||
|
{ value: 'minute', label: 'Minutes' },
|
||||||
|
{ value: 'hour', label: 'Hours' },
|
||||||
|
{ value: 'day', label: 'Days' },
|
||||||
|
{ value: 'week', label: 'Weeks' },
|
||||||
|
];
|
||||||
|
|
||||||
|
type NotificationInternal = {
|
||||||
|
internalId: string;
|
||||||
|
externalId: string | null;
|
||||||
|
period: number;
|
||||||
|
duration: { value: string; label: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
type NotificationEntryProps = {
|
||||||
|
notification: NotificationInternal;
|
||||||
|
onChange: (period: number, duration: { value: string; label: string }) => void;
|
||||||
|
onRemove: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NotificationEntry: React.FC<NotificationEntryProps> = ({ notification, onChange, onRemove }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ActionBell width={16} height={16} />
|
||||||
|
<ActionLabel>Notification</ActionLabel>
|
||||||
|
<ActionInput
|
||||||
|
value={notification.period}
|
||||||
|
onChange={(e) => {
|
||||||
|
onChange(parseInt(e.currentTarget.value, 10), notification.duration);
|
||||||
|
}}
|
||||||
|
onKeyPress={(e) => {
|
||||||
|
const isNumber = /^[0-9]$/i.test(e.key);
|
||||||
|
if (!isNumber && e.key !== 'Backspace') {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
dir="ltr"
|
||||||
|
autoComplete="off"
|
||||||
|
min="0"
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
<Select
|
||||||
|
menuPlacement="top"
|
||||||
|
className="react-period"
|
||||||
|
classNamePrefix="react-period-select"
|
||||||
|
styles={colourStyles}
|
||||||
|
isSearchable={false}
|
||||||
|
defaultValue={notification.duration}
|
||||||
|
options={notificationPeriodOptions}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (e !== null) {
|
||||||
|
onChange(notification.period, e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ActionIcon onClick={() => onRemove()}>
|
||||||
|
<Cross width={16} height={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onRemoveDueDate, onCancel }) => {
|
||||||
const currentDueDate = task.dueDate ? dayjs(task.dueDate).toDate() : null;
|
const currentDueDate = task.dueDate.at ? dayjs(task.dueDate.at).toDate() : null;
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -145,28 +252,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
|||||||
const [startDate, setStartDate] = useState<Date | null>(currentDueDate);
|
const [startDate, setStartDate] = useState<Date | null>(currentDueDate);
|
||||||
const [endDate, setEndDate] = useState<Date | null>(currentDueDate);
|
const [endDate, setEndDate] = useState<Date | null>(currentDueDate);
|
||||||
const [hasTime, enableTime] = useState(task.hasTime ?? false);
|
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(() => {
|
|
||||||
debouncedChange(startDate, hasTime);
|
|
||||||
}, [startDate, hasTime]);
|
|
||||||
const years = _.range(2010, getYear(new Date()) + 10, 1);
|
const years = _.range(2010, getYear(new Date()) + 10, 1);
|
||||||
const months = [
|
const months = [
|
||||||
'January',
|
'January',
|
||||||
@ -183,33 +269,41 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
|||||||
'December',
|
'December',
|
||||||
];
|
];
|
||||||
|
|
||||||
const onChange = (dates: any) => {
|
|
||||||
const [start, end] = dates;
|
|
||||||
setStartDate(start);
|
|
||||||
setEndDate(end);
|
|
||||||
};
|
|
||||||
const [isRange, setIsRange] = useState(false);
|
const [isRange, setIsRange] = useState(false);
|
||||||
|
const [notDuration, setNotDuration] = useState(10);
|
||||||
|
const [removedNotifications, setRemovedNotifications] = useState<Array<string>>([]);
|
||||||
|
const [notifications, setNotifications] = useState<Array<NotificationInternal>>(
|
||||||
|
task.dueDate.notifications
|
||||||
|
? task.dueDate.notifications.map((c, idx) => {
|
||||||
|
const duration =
|
||||||
|
notificationPeriodOptions.find((o) => o.value === c.duration.toLowerCase()) ?? notificationPeriodOptions[0];
|
||||||
|
return {
|
||||||
|
internalId: `n${idx}`,
|
||||||
|
externalId: c.id,
|
||||||
|
period: c.period,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
: [],
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<DateRangeInputs>
|
<DateRangeInputs>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={startDate}
|
selected={startDate}
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
if (!Array.isArray(date)) {
|
if (!Array.isArray(date) && date !== null) {
|
||||||
setStartDate(date);
|
setStartDate(date);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
popperClassName="picker-hidden"
|
popperClassName="picker-hidden"
|
||||||
dateFormat="yyyy-MM-dd"
|
dateFormat="yyyy-MM-dd"
|
||||||
disabledKeyboardNavigation
|
disabledKeyboardNavigation
|
||||||
isClearable
|
|
||||||
placeholderText="Select due date"
|
placeholderText="Select due date"
|
||||||
/>
|
/>
|
||||||
{isRange ? (
|
{isRange ? (
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={startDate}
|
selected={startDate}
|
||||||
isClearable
|
|
||||||
onChange={(date) => {
|
onChange={(date) => {
|
||||||
if (!Array.isArray(date)) {
|
if (!Array.isArray(date)) {
|
||||||
setStartDate(date);
|
setStartDate(date);
|
||||||
@ -299,23 +393,94 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
|||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
</ActionsWrapper>
|
</ActionsWrapper>
|
||||||
)}
|
)}
|
||||||
<ActionsWrapper>
|
{notifications.map((n, idx) => (
|
||||||
{!hasTime && (
|
<ActionsWrapper key={n.internalId}>
|
||||||
<ActionIcon
|
<NotificationEntry
|
||||||
|
notification={n}
|
||||||
|
onChange={(period, duration) => {
|
||||||
|
setNotifications((prev) =>
|
||||||
|
produce(prev, (draft) => {
|
||||||
|
draft[idx].duration = duration;
|
||||||
|
draft[idx].period = period;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onRemove={() => {
|
||||||
|
setNotifications((prev) =>
|
||||||
|
produce(prev, (draft) => {
|
||||||
|
draft.splice(idx, 1);
|
||||||
|
if (n.externalId !== null) {
|
||||||
|
setRemovedNotifications((prev) => {
|
||||||
|
if (n.externalId !== null) {
|
||||||
|
return [...prev, n.externalId];
|
||||||
|
}
|
||||||
|
return prev;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ActionsWrapper>
|
||||||
|
))}
|
||||||
|
<ControlWrapper>
|
||||||
|
<LeftWrapper>
|
||||||
|
<SaveButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (startDate === null) {
|
if (startDate && notifications.findIndex((n) => Number.isNaN(n.period)) === -1) {
|
||||||
const today = new Date();
|
onDueDateChange(task, startDate, hasTime, { current: notifications, removed: removedNotifications });
|
||||||
today.setHours(12, 30, 0);
|
|
||||||
setStartDate(today);
|
|
||||||
}
|
}
|
||||||
enableTime(true);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Clock width={16} height={16} />
|
Save
|
||||||
|
</SaveButton>
|
||||||
|
{currentDueDate !== null && (
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() => {
|
||||||
|
onRemoveDueDate(task);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash width={16} height={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
</LeftWrapper>
|
||||||
|
<RightWrapper>
|
||||||
|
<ActionIcon
|
||||||
|
// disabled={notifications.length === 3}
|
||||||
|
disabled
|
||||||
|
onClick={() => {
|
||||||
|
/*
|
||||||
|
setNotifications((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
externalId: null,
|
||||||
|
internalId: `n${prev.length + 1}`,
|
||||||
|
duration: notificationPeriodOptions[0],
|
||||||
|
period: 10,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
*/
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Bell width={16} height={16} />
|
||||||
|
<ActionPlus width={8} height={8} />
|
||||||
</ActionIcon>
|
</ActionIcon>
|
||||||
)}
|
{!hasTime && (
|
||||||
<ClearButton onClick={() => setStartDate(null)}>{hasTime ? 'Clear all' : 'Clear'}</ClearButton>
|
<ActionIcon
|
||||||
</ActionsWrapper>
|
onClick={() => {
|
||||||
|
if (startDate === null) {
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(12, 30, 0);
|
||||||
|
setStartDate(today);
|
||||||
|
}
|
||||||
|
enableTime(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Clock width={16} height={16} />
|
||||||
|
</ActionIcon>
|
||||||
|
)}
|
||||||
|
</RightWrapper>
|
||||||
|
</ControlWrapper>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -351,10 +351,10 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
description=""
|
description=""
|
||||||
labels={task.labels.map((label) => label.projectLabel)}
|
labels={task.labels.map((label) => label.projectLabel)}
|
||||||
dueDate={
|
dueDate={
|
||||||
task.dueDate
|
task.dueDate.at
|
||||||
? {
|
? {
|
||||||
isPastDue: false,
|
isPastDue: false,
|
||||||
formattedDate: dayjs(task.dueDate).format('MMM D, YYYY'),
|
formattedDate: dayjs(task.dueDate.at).format('MMM D, YYYY'),
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
|||||||
isFiltered = shouldFilter(!(task.dueDate && task.dueDate !== null));
|
isFiltered = shouldFilter(!(task.dueDate && task.dueDate !== null));
|
||||||
}
|
}
|
||||||
if (task.dueDate) {
|
if (task.dueDate) {
|
||||||
const taskDueDate = dayjs(task.dueDate);
|
const taskDueDate = dayjs(task.dueDate.at);
|
||||||
const today = dayjs();
|
const today = dayjs();
|
||||||
let start;
|
let start;
|
||||||
let end;
|
let end;
|
||||||
@ -36,61 +36,31 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
|||||||
isFiltered = shouldFilter(taskDueDate.isSame(today, 'day'));
|
isFiltered = shouldFilter(taskDueDate.isSame(today, 'day'));
|
||||||
break;
|
break;
|
||||||
case DueDateFilterType.TOMORROW:
|
case DueDateFilterType.TOMORROW:
|
||||||
isFiltered = shouldFilter(
|
isFiltered = shouldFilter(taskDueDate.isBefore(today.clone().add(1, 'day').endOf('day')));
|
||||||
taskDueDate.isBefore(
|
|
||||||
today
|
|
||||||
.clone()
|
|
||||||
.add(1, 'day')
|
|
||||||
.endOf('day'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case DueDateFilterType.THIS_WEEK:
|
case DueDateFilterType.THIS_WEEK:
|
||||||
start = today
|
start = today.clone().weekday(0).startOf('day');
|
||||||
.clone()
|
end = today.clone().weekday(6).endOf('day');
|
||||||
.weekday(0)
|
|
||||||
.startOf('day');
|
|
||||||
end = today
|
|
||||||
.clone()
|
|
||||||
.weekday(6)
|
|
||||||
.endOf('day');
|
|
||||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||||
break;
|
break;
|
||||||
case DueDateFilterType.NEXT_WEEK:
|
case DueDateFilterType.NEXT_WEEK:
|
||||||
start = today
|
start = today.clone().weekday(0).add(7, 'day').startOf('day');
|
||||||
.clone()
|
end = today.clone().weekday(6).add(7, 'day').endOf('day');
|
||||||
.weekday(0)
|
|
||||||
.add(7, 'day')
|
|
||||||
.startOf('day');
|
|
||||||
end = today
|
|
||||||
.clone()
|
|
||||||
.weekday(6)
|
|
||||||
.add(7, 'day')
|
|
||||||
.endOf('day');
|
|
||||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||||
break;
|
break;
|
||||||
case DueDateFilterType.ONE_WEEK:
|
case DueDateFilterType.ONE_WEEK:
|
||||||
start = today.clone().startOf('day');
|
start = today.clone().startOf('day');
|
||||||
end = today
|
end = today.clone().add(7, 'day').endOf('day');
|
||||||
.clone()
|
|
||||||
.add(7, 'day')
|
|
||||||
.endOf('day');
|
|
||||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||||
break;
|
break;
|
||||||
case DueDateFilterType.TWO_WEEKS:
|
case DueDateFilterType.TWO_WEEKS:
|
||||||
start = today.clone().startOf('day');
|
start = today.clone().startOf('day');
|
||||||
end = today
|
end = today.clone().add(14, 'day').endOf('day');
|
||||||
.clone()
|
|
||||||
.add(14, 'day')
|
|
||||||
.endOf('day');
|
|
||||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||||
break;
|
break;
|
||||||
case DueDateFilterType.THREE_WEEKS:
|
case DueDateFilterType.THREE_WEEKS:
|
||||||
start = today.clone().startOf('day');
|
start = today.clone().startOf('day');
|
||||||
end = today
|
end = today.clone().add(21, 'day').endOf('day');
|
||||||
.clone()
|
|
||||||
.add(21, 'day')
|
|
||||||
.endOf('day');
|
|
||||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -104,7 +74,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
|||||||
}
|
}
|
||||||
for (const member of filters.members) {
|
for (const member of filters.members) {
|
||||||
if (task.assigned) {
|
if (task.assigned) {
|
||||||
if (task.assigned.findIndex(m => m.id === member.id) !== -1) {
|
if (task.assigned.findIndex((m) => m.id === member.id) !== -1) {
|
||||||
isFiltered = ShouldFilter.VALID;
|
isFiltered = ShouldFilter.VALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +86,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
|||||||
}
|
}
|
||||||
for (const label of filters.labels) {
|
for (const label of filters.labels) {
|
||||||
if (task.labels) {
|
if (task.labels) {
|
||||||
if (task.labels.findIndex(m => m.projectLabel.id === label.id) !== -1) {
|
if (task.labels.findIndex((m) => m.projectLabel.id === label.id) !== -1) {
|
||||||
isFiltered = ShouldFilter.VALID;
|
isFiltered = ShouldFilter.VALID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,13 +9,21 @@ type ActivityMessageProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getVariable(data: Array<TaskActivityData>, name: string) {
|
function getVariable(data: Array<TaskActivityData>, name: string) {
|
||||||
const target = data.find(d => d.name === name);
|
const target = data.find((d) => d.name === name);
|
||||||
return target ? target.value : null;
|
return target ? target.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDate(timestamp: string | null) {
|
function getVariableBool(data: Array<TaskActivityData>, name: string, defaultValue = false) {
|
||||||
|
const target = data.find((d) => d.name === name);
|
||||||
|
return target ? target.value === 'true' : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderDate(timestamp: string | null, hasTime: boolean) {
|
||||||
if (timestamp) {
|
if (timestamp) {
|
||||||
return dayjs(timestamp).format('MMM D [at] h:mm A');
|
if (hasTime) {
|
||||||
|
return dayjs(timestamp).format('MMM D [at] h:mm A');
|
||||||
|
}
|
||||||
|
return dayjs(timestamp).format('MMM D');
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -30,13 +38,19 @@ const ActivityMessage: React.FC<ActivityMessageProps> = ({ type, data }) => {
|
|||||||
message = `moved this task from ${getVariable(data, 'PrevTaskGroup')} to ${getVariable(data, 'CurTaskGroup')}`;
|
message = `moved this task from ${getVariable(data, 'PrevTaskGroup')} to ${getVariable(data, 'CurTaskGroup')}`;
|
||||||
break;
|
break;
|
||||||
case ActivityType.TaskDueDateAdded:
|
case ActivityType.TaskDueDateAdded:
|
||||||
message = `set this task to be due ${renderDate(getVariable(data, 'DueDate'))}`;
|
message = `set this task to be due ${renderDate(
|
||||||
|
getVariable(data, 'DueDate'),
|
||||||
|
getVariableBool(data, 'HasTime', true),
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
case ActivityType.TaskDueDateRemoved:
|
case ActivityType.TaskDueDateRemoved:
|
||||||
message = `removed the due date from this task`;
|
message = `removed the due date from this task`;
|
||||||
break;
|
break;
|
||||||
case ActivityType.TaskDueDateChanged:
|
case ActivityType.TaskDueDateChanged:
|
||||||
message = `changed the due date of this task to ${renderDate(getVariable(data, 'CurDueDate'))}`;
|
message = `changed the due date of this task to ${renderDate(
|
||||||
|
getVariable(data, 'CurDueDate'),
|
||||||
|
getVariableBool(data, 'HasTime', true),
|
||||||
|
)}`;
|
||||||
break;
|
break;
|
||||||
case ActivityType.TaskMarkedComplete:
|
case ActivityType.TaskMarkedComplete:
|
||||||
message = `marked this task complete`;
|
message = `marked this task complete`;
|
||||||
|
@ -332,7 +332,6 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
const saveDescription = () => {
|
const saveDescription = () => {
|
||||||
onTaskDescriptionChange(task, taskDescriptionRef.current);
|
onTaskDescriptionChange(task, taskDescriptionRef.current);
|
||||||
};
|
};
|
||||||
console.log(task.watched);
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<LeftSidebar>
|
<LeftSidebar>
|
||||||
@ -351,9 +350,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{task.dueDate ? (
|
{task.dueDate.at ? (
|
||||||
<SidebarButtonText>
|
<SidebarButtonText>
|
||||||
{dayjs(task.dueDate).format(task.hasTime ? 'MMM D [at] h:mm A' : 'MMMM D')}
|
{dayjs(task.dueDate.at).format(task.hasTime ? 'MMM D [at] h:mm A' : 'MMMM D')}
|
||||||
</SidebarButtonText>
|
</SidebarButtonText>
|
||||||
) : (
|
) : (
|
||||||
<SidebarButtonText>No due date</SidebarButtonText>
|
<SidebarButtonText>No due date</SidebarButtonText>
|
||||||
@ -632,6 +631,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
{activityStream.map((stream) =>
|
{activityStream.map((stream) =>
|
||||||
stream.data.type === 'comment' ? (
|
stream.data.type === 'comment' ? (
|
||||||
<StreamComment
|
<StreamComment
|
||||||
|
key={stream.id}
|
||||||
onExtraActions={onCommentShowActions}
|
onExtraActions={onCommentShowActions}
|
||||||
onCancelCommentEdit={onCancelCommentEdit}
|
onCancelCommentEdit={onCancelCommentEdit}
|
||||||
onUpdateComment={(message) => onUpdateComment(stream.id, message)}
|
onUpdateComment={(message) => onUpdateComment(stream.id, message)}
|
||||||
@ -640,6 +640,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StreamActivity
|
<StreamActivity
|
||||||
|
key={stream.id}
|
||||||
activity={task.activity && task.activity.find((activity) => activity.id === stream.id)}
|
activity={task.activity && task.activity.find((activity) => activity.id === stream.id)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -30,19 +30,16 @@ function plugin(options) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getEmoji(match) {
|
function getEmoji(match) {
|
||||||
console.log(match);
|
|
||||||
const got = emoji.get(match);
|
const got = emoji.get(match);
|
||||||
if (pad && got !== match) {
|
if (pad && got !== match) {
|
||||||
return `${got} `;
|
return `${got} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(got);
|
|
||||||
return ReactDOMServer.renderToStaticMarkup(<Emoji set="google" emoji={match} size={16} />);
|
return ReactDOMServer.renderToStaticMarkup(<Emoji set="google" emoji={match} size={16} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformer(tree) {
|
function transformer(tree) {
|
||||||
visit(tree, 'paragraph', function (node) {
|
visit(tree, 'paragraph', function (node) {
|
||||||
console.log(tree);
|
|
||||||
// node.value = node.value.replace(RE_EMOJI, getEmoji);
|
// node.value = node.value.replace(RE_EMOJI, getEmoji);
|
||||||
// jnode.type = 'html';
|
// jnode.type = 'html';
|
||||||
// jnode.tagName = 'div';
|
// jnode.tagName = 'div';
|
||||||
@ -58,7 +55,6 @@ function plugin(options) {
|
|||||||
if (emoticonEnable) {
|
if (emoticonEnable) {
|
||||||
// node.value = node.value.replace(RE_SHORT, getEmojiByShortCode);
|
// node.value = node.value.replace(RE_SHORT, getEmojiByShortCode);
|
||||||
}
|
}
|
||||||
console.log(node);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ const NavBar: React.FC<NavBarProps> = ({
|
|||||||
<ListUnordered width={20} height={20} />
|
<ListUnordered width={20} height={20} />
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
<IconContainer onClick={onNotificationClick}>
|
<IconContainer onClick={onNotificationClick}>
|
||||||
<Bell color="#c2c6dc" size={20} />
|
<Bell width={20} height={20} />
|
||||||
{hasUnread && <NotificationCount />}
|
{hasUnread && <NotificationCount />}
|
||||||
</IconContainer>
|
</IconContainer>
|
||||||
<IconContainer disabled onClick={NOOP}>
|
<IconContainer disabled onClick={NOOP}>
|
||||||
|
@ -107,6 +107,17 @@ export type CreateTaskCommentPayload = {
|
|||||||
comment: TaskComment;
|
comment: TaskComment;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateTaskDueDateNotification = {
|
||||||
|
taskID: Scalars['UUID'];
|
||||||
|
period: Scalars['Int'];
|
||||||
|
duration: DueDateNotificationDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateTaskDueDateNotificationsResult = {
|
||||||
|
__typename?: 'CreateTaskDueDateNotificationsResult';
|
||||||
|
notifications: Array<DueDateNotification>;
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateTeamMember = {
|
export type CreateTeamMember = {
|
||||||
userID: Scalars['UUID'];
|
userID: Scalars['UUID'];
|
||||||
teamID: Scalars['UUID'];
|
teamID: Scalars['UUID'];
|
||||||
@ -200,6 +211,15 @@ export type DeleteTaskCommentPayload = {
|
|||||||
commentID: Scalars['UUID'];
|
commentID: Scalars['UUID'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DeleteTaskDueDateNotification = {
|
||||||
|
id: Scalars['UUID'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteTaskDueDateNotificationsResult = {
|
||||||
|
__typename?: 'DeleteTaskDueDateNotificationsResult';
|
||||||
|
notifications: Array<Scalars['UUID']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type DeleteTaskGroupInput = {
|
export type DeleteTaskGroupInput = {
|
||||||
taskGroupID: Scalars['UUID'];
|
taskGroupID: Scalars['UUID'];
|
||||||
};
|
};
|
||||||
@ -265,6 +285,26 @@ export type DeleteUserAccountPayload = {
|
|||||||
userAccount: UserAccount;
|
userAccount: UserAccount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DueDate = {
|
||||||
|
__typename?: 'DueDate';
|
||||||
|
at?: Maybe<Scalars['Time']>;
|
||||||
|
notifications: Array<DueDateNotification>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DueDateNotification = {
|
||||||
|
__typename?: 'DueDateNotification';
|
||||||
|
id: Scalars['ID'];
|
||||||
|
period: Scalars['Int'];
|
||||||
|
duration: DueDateNotificationDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum DueDateNotificationDuration {
|
||||||
|
Minute = 'MINUTE',
|
||||||
|
Hour = 'HOUR',
|
||||||
|
Day = 'DAY',
|
||||||
|
Week = 'WEEK'
|
||||||
|
}
|
||||||
|
|
||||||
export type DuplicateTaskGroup = {
|
export type DuplicateTaskGroup = {
|
||||||
projectID: Scalars['UUID'];
|
projectID: Scalars['UUID'];
|
||||||
taskGroupID: Scalars['UUID'];
|
taskGroupID: Scalars['UUID'];
|
||||||
@ -393,6 +433,7 @@ export type Mutation = {
|
|||||||
createTaskChecklist: TaskChecklist;
|
createTaskChecklist: TaskChecklist;
|
||||||
createTaskChecklistItem: TaskChecklistItem;
|
createTaskChecklistItem: TaskChecklistItem;
|
||||||
createTaskComment: CreateTaskCommentPayload;
|
createTaskComment: CreateTaskCommentPayload;
|
||||||
|
createTaskDueDateNotifications: CreateTaskDueDateNotificationsResult;
|
||||||
createTaskGroup: TaskGroup;
|
createTaskGroup: TaskGroup;
|
||||||
createTeam: Team;
|
createTeam: Team;
|
||||||
createTeamMember: CreateTeamMemberPayload;
|
createTeamMember: CreateTeamMemberPayload;
|
||||||
@ -406,6 +447,7 @@ export type Mutation = {
|
|||||||
deleteTaskChecklist: DeleteTaskChecklistPayload;
|
deleteTaskChecklist: DeleteTaskChecklistPayload;
|
||||||
deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
|
deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
|
||||||
deleteTaskComment: DeleteTaskCommentPayload;
|
deleteTaskComment: DeleteTaskCommentPayload;
|
||||||
|
deleteTaskDueDateNotifications: DeleteTaskDueDateNotificationsResult;
|
||||||
deleteTaskGroup: DeleteTaskGroupPayload;
|
deleteTaskGroup: DeleteTaskGroupPayload;
|
||||||
deleteTaskGroupTasks: DeleteTaskGroupTasksPayload;
|
deleteTaskGroupTasks: DeleteTaskGroupTasksPayload;
|
||||||
deleteTeam: DeleteTeamPayload;
|
deleteTeam: DeleteTeamPayload;
|
||||||
@ -435,6 +477,7 @@ export type Mutation = {
|
|||||||
updateTaskComment: UpdateTaskCommentPayload;
|
updateTaskComment: UpdateTaskCommentPayload;
|
||||||
updateTaskDescription: Task;
|
updateTaskDescription: Task;
|
||||||
updateTaskDueDate: Task;
|
updateTaskDueDate: Task;
|
||||||
|
updateTaskDueDateNotifications: UpdateTaskDueDateNotificationsResult;
|
||||||
updateTaskGroupLocation: TaskGroup;
|
updateTaskGroupLocation: TaskGroup;
|
||||||
updateTaskGroupName: TaskGroup;
|
updateTaskGroupName: TaskGroup;
|
||||||
updateTaskLocation: UpdateTaskLocationPayload;
|
updateTaskLocation: UpdateTaskLocationPayload;
|
||||||
@ -486,6 +529,11 @@ export type MutationCreateTaskCommentArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationCreateTaskDueDateNotificationsArgs = {
|
||||||
|
input: Array<CreateTaskDueDateNotification>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationCreateTaskGroupArgs = {
|
export type MutationCreateTaskGroupArgs = {
|
||||||
input: NewTaskGroup;
|
input: NewTaskGroup;
|
||||||
};
|
};
|
||||||
@ -551,6 +599,11 @@ export type MutationDeleteTaskCommentArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationDeleteTaskDueDateNotificationsArgs = {
|
||||||
|
input: Array<DeleteTaskDueDateNotification>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationDeleteTaskGroupArgs = {
|
export type MutationDeleteTaskGroupArgs = {
|
||||||
input: DeleteTaskGroupInput;
|
input: DeleteTaskGroupInput;
|
||||||
};
|
};
|
||||||
@ -696,6 +749,11 @@ export type MutationUpdateTaskDueDateArgs = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export type MutationUpdateTaskDueDateNotificationsArgs = {
|
||||||
|
input: Array<UpdateTaskDueDateNotification>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type MutationUpdateTaskGroupLocationArgs = {
|
export type MutationUpdateTaskGroupLocationArgs = {
|
||||||
input: NewTaskGroupLocation;
|
input: NewTaskGroupLocation;
|
||||||
};
|
};
|
||||||
@ -1078,7 +1136,7 @@ export type Task = {
|
|||||||
position: Scalars['Float'];
|
position: Scalars['Float'];
|
||||||
description?: Maybe<Scalars['String']>;
|
description?: Maybe<Scalars['String']>;
|
||||||
watched: Scalars['Boolean'];
|
watched: Scalars['Boolean'];
|
||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate: DueDate;
|
||||||
hasTime: Scalars['Boolean'];
|
hasTime: Scalars['Boolean'];
|
||||||
complete: Scalars['Boolean'];
|
complete: Scalars['Boolean'];
|
||||||
completedAt?: Maybe<Scalars['Time']>;
|
completedAt?: Maybe<Scalars['Time']>;
|
||||||
@ -1302,6 +1360,17 @@ export type UpdateTaskDueDate = {
|
|||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate?: Maybe<Scalars['Time']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UpdateTaskDueDateNotification = {
|
||||||
|
id: Scalars['UUID'];
|
||||||
|
period: Scalars['Int'];
|
||||||
|
duration: DueDateNotificationDuration;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateTaskDueDateNotificationsResult = {
|
||||||
|
__typename?: 'UpdateTaskDueDateNotificationsResult';
|
||||||
|
notifications: Array<DueDateNotification>;
|
||||||
|
};
|
||||||
|
|
||||||
export type UpdateTaskGroupName = {
|
export type UpdateTaskGroupName = {
|
||||||
taskGroupID: Scalars['UUID'];
|
taskGroupID: Scalars['UUID'];
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
@ -1596,8 +1665,15 @@ export type FindTaskQuery = (
|
|||||||
{ __typename?: 'Query' }
|
{ __typename?: 'Query' }
|
||||||
& { findTask: (
|
& { findTask: (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'watched' | 'description' | 'dueDate' | 'position' | 'complete' | 'hasTime'>
|
& Pick<Task, 'id' | 'shortId' | 'name' | 'watched' | 'description' | 'position' | 'complete' | 'hasTime'>
|
||||||
& { taskGroup: (
|
& { dueDate: (
|
||||||
|
{ __typename?: 'DueDate' }
|
||||||
|
& Pick<DueDate, 'at'>
|
||||||
|
& { notifications: Array<(
|
||||||
|
{ __typename?: 'DueDateNotification' }
|
||||||
|
& Pick<DueDateNotification, 'id' | 'period' | 'duration'>
|
||||||
|
)> }
|
||||||
|
), taskGroup: (
|
||||||
{ __typename?: 'TaskGroup' }
|
{ __typename?: 'TaskGroup' }
|
||||||
& Pick<TaskGroup, 'id' | 'name'>
|
& Pick<TaskGroup, 'id' | 'name'>
|
||||||
), comments: Array<(
|
), comments: Array<(
|
||||||
@ -1672,8 +1748,11 @@ export type FindTaskQuery = (
|
|||||||
|
|
||||||
export type TaskFieldsFragment = (
|
export type TaskFieldsFragment = (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'description' | 'dueDate' | 'hasTime' | 'complete' | 'watched' | 'completedAt' | 'position'>
|
& Pick<Task, 'id' | 'shortId' | 'name' | 'description' | 'hasTime' | 'complete' | 'watched' | 'completedAt' | 'position'>
|
||||||
& { badges: (
|
& { dueDate: (
|
||||||
|
{ __typename?: 'DueDate' }
|
||||||
|
& Pick<DueDate, 'at'>
|
||||||
|
), badges: (
|
||||||
{ __typename?: 'TaskBadges' }
|
{ __typename?: 'TaskBadges' }
|
||||||
& { checklist?: Maybe<(
|
& { checklist?: Maybe<(
|
||||||
{ __typename?: 'ChecklistBadge' }
|
{ __typename?: 'ChecklistBadge' }
|
||||||
@ -1789,10 +1868,13 @@ export type MyTasksQuery = (
|
|||||||
{ __typename?: 'MyTasksPayload' }
|
{ __typename?: 'MyTasksPayload' }
|
||||||
& { tasks: Array<(
|
& { tasks: Array<(
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'dueDate' | 'hasTime' | 'complete' | 'completedAt'>
|
& Pick<Task, 'id' | 'shortId' | 'name' | 'hasTime' | 'complete' | 'completedAt'>
|
||||||
& { taskGroup: (
|
& { taskGroup: (
|
||||||
{ __typename?: 'TaskGroup' }
|
{ __typename?: 'TaskGroup' }
|
||||||
& Pick<TaskGroup, 'id' | 'name'>
|
& Pick<TaskGroup, 'id' | 'name'>
|
||||||
|
), dueDate: (
|
||||||
|
{ __typename?: 'DueDate' }
|
||||||
|
& Pick<DueDate, 'at'>
|
||||||
) }
|
) }
|
||||||
)>, projects: Array<(
|
)>, projects: Array<(
|
||||||
{ __typename?: 'ProjectTaskMapping' }
|
{ __typename?: 'ProjectTaskMapping' }
|
||||||
@ -2624,6 +2706,9 @@ export type UpdateTaskDueDateMutationVariables = Exact<{
|
|||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
dueDate?: Maybe<Scalars['Time']>;
|
dueDate?: Maybe<Scalars['Time']>;
|
||||||
hasTime: Scalars['Boolean'];
|
hasTime: Scalars['Boolean'];
|
||||||
|
createNotifications: Array<CreateTaskDueDateNotification> | CreateTaskDueDateNotification;
|
||||||
|
updateNotifications: Array<UpdateTaskDueDateNotification> | UpdateTaskDueDateNotification;
|
||||||
|
deleteNotifications: Array<DeleteTaskDueDateNotification> | DeleteTaskDueDateNotification;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
@ -2631,7 +2716,26 @@ export type UpdateTaskDueDateMutation = (
|
|||||||
{ __typename?: 'Mutation' }
|
{ __typename?: 'Mutation' }
|
||||||
& { updateTaskDueDate: (
|
& { updateTaskDueDate: (
|
||||||
{ __typename?: 'Task' }
|
{ __typename?: 'Task' }
|
||||||
& Pick<Task, 'id' | 'dueDate' | 'hasTime'>
|
& Pick<Task, 'id' | 'hasTime'>
|
||||||
|
& { dueDate: (
|
||||||
|
{ __typename?: 'DueDate' }
|
||||||
|
& Pick<DueDate, 'at'>
|
||||||
|
) }
|
||||||
|
), createTaskDueDateNotifications: (
|
||||||
|
{ __typename?: 'CreateTaskDueDateNotificationsResult' }
|
||||||
|
& { notifications: Array<(
|
||||||
|
{ __typename?: 'DueDateNotification' }
|
||||||
|
& Pick<DueDateNotification, 'id' | 'period' | 'duration'>
|
||||||
|
)> }
|
||||||
|
), updateTaskDueDateNotifications: (
|
||||||
|
{ __typename?: 'UpdateTaskDueDateNotificationsResult' }
|
||||||
|
& { notifications: Array<(
|
||||||
|
{ __typename?: 'DueDateNotification' }
|
||||||
|
& Pick<DueDateNotification, 'id' | 'period' | 'duration'>
|
||||||
|
)> }
|
||||||
|
), deleteTaskDueDateNotifications: (
|
||||||
|
{ __typename?: 'DeleteTaskDueDateNotificationsResult' }
|
||||||
|
& Pick<DeleteTaskDueDateNotificationsResult, 'notifications'>
|
||||||
) }
|
) }
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2866,7 +2970,9 @@ export const TaskFieldsFragmentDoc = gql`
|
|||||||
shortId
|
shortId
|
||||||
name
|
name
|
||||||
description
|
description
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
}
|
||||||
hasTime
|
hasTime
|
||||||
complete
|
complete
|
||||||
watched
|
watched
|
||||||
@ -3346,7 +3452,14 @@ export const FindTaskDocument = gql`
|
|||||||
name
|
name
|
||||||
watched
|
watched
|
||||||
description
|
description
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
period
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
position
|
position
|
||||||
complete
|
complete
|
||||||
hasTime
|
hasTime
|
||||||
@ -3640,7 +3753,9 @@ export const MyTasksDocument = gql`
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
name
|
name
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
}
|
||||||
hasTime
|
hasTime
|
||||||
complete
|
complete
|
||||||
completedAt
|
completedAt
|
||||||
@ -5423,14 +5538,33 @@ export type UpdateTaskDescriptionMutationHookResult = ReturnType<typeof useUpdat
|
|||||||
export type UpdateTaskDescriptionMutationResult = Apollo.MutationResult<UpdateTaskDescriptionMutation>;
|
export type UpdateTaskDescriptionMutationResult = Apollo.MutationResult<UpdateTaskDescriptionMutation>;
|
||||||
export type UpdateTaskDescriptionMutationOptions = Apollo.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
export type UpdateTaskDescriptionMutationOptions = Apollo.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
||||||
export const UpdateTaskDueDateDocument = gql`
|
export const UpdateTaskDueDateDocument = gql`
|
||||||
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!) {
|
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!, $createNotifications: [CreateTaskDueDateNotification!]!, $updateNotifications: [UpdateTaskDueDateNotification!]!, $deleteNotifications: [DeleteTaskDueDateNotification!]!) {
|
||||||
updateTaskDueDate(
|
updateTaskDueDate(
|
||||||
input: {taskID: $taskID, dueDate: $dueDate, hasTime: $hasTime}
|
input: {taskID: $taskID, dueDate: $dueDate, hasTime: $hasTime}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
}
|
||||||
hasTime
|
hasTime
|
||||||
}
|
}
|
||||||
|
createTaskDueDateNotifications(input: $createNotifications) {
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
period
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTaskDueDateNotifications(input: $updateNotifications) {
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
period
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteTaskDueDateNotifications(input: $deleteNotifications) {
|
||||||
|
notifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export type UpdateTaskDueDateMutationFn = Apollo.MutationFunction<UpdateTaskDueDateMutation, UpdateTaskDueDateMutationVariables>;
|
export type UpdateTaskDueDateMutationFn = Apollo.MutationFunction<UpdateTaskDueDateMutation, UpdateTaskDueDateMutationVariables>;
|
||||||
@ -5451,6 +5585,9 @@ export type UpdateTaskDueDateMutationFn = Apollo.MutationFunction<UpdateTaskDueD
|
|||||||
* taskID: // value for 'taskID'
|
* taskID: // value for 'taskID'
|
||||||
* dueDate: // value for 'dueDate'
|
* dueDate: // value for 'dueDate'
|
||||||
* hasTime: // value for 'hasTime'
|
* hasTime: // value for 'hasTime'
|
||||||
|
* createNotifications: // value for 'createNotifications'
|
||||||
|
* updateNotifications: // value for 'updateNotifications'
|
||||||
|
* deleteNotifications: // value for 'deleteNotifications'
|
||||||
* },
|
* },
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
|
@ -5,7 +5,14 @@ query findTask($taskID: String!) {
|
|||||||
name
|
name
|
||||||
watched
|
watched
|
||||||
description
|
description
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
period
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
position
|
position
|
||||||
complete
|
complete
|
||||||
hasTime
|
hasTime
|
||||||
|
@ -6,7 +6,9 @@ const TASK_FRAGMENT = gql`
|
|||||||
shortId
|
shortId
|
||||||
name
|
name
|
||||||
description
|
description
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
}
|
||||||
hasTime
|
hasTime
|
||||||
complete
|
complete
|
||||||
watched
|
watched
|
||||||
|
@ -12,7 +12,9 @@ query myTasks($status: MyTasksStatus!, $sort: MyTasksSort!) {
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
name
|
name
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
}
|
||||||
hasTime
|
hasTime
|
||||||
complete
|
complete
|
||||||
completedAt
|
completedAt
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!) {
|
mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!,
|
||||||
|
$createNotifications: [CreateTaskDueDateNotification!]!,
|
||||||
|
$updateNotifications: [UpdateTaskDueDateNotification!]!
|
||||||
|
$deleteNotifications: [DeleteTaskDueDateNotification!]!
|
||||||
|
) {
|
||||||
updateTaskDueDate (
|
updateTaskDueDate (
|
||||||
input: {
|
input: {
|
||||||
taskID: $taskID
|
taskID: $taskID
|
||||||
@ -7,7 +11,26 @@ mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!) {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
dueDate
|
dueDate {
|
||||||
|
at
|
||||||
|
}
|
||||||
hasTime
|
hasTime
|
||||||
}
|
}
|
||||||
|
createTaskDueDateNotifications(input: $createNotifications) {
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
period
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTaskDueDateNotifications(input: $updateNotifications) {
|
||||||
|
notifications {
|
||||||
|
id
|
||||||
|
period
|
||||||
|
duration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deleteTaskDueDateNotifications(input: $deleteNotifications) {
|
||||||
|
notifications
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ function parseJSON<T>(value: string | null): T | undefined {
|
|||||||
try {
|
try {
|
||||||
return value === 'undefined' ? undefined : JSON.parse(value ?? '');
|
return value === 'undefined' ? undefined : JSON.parse(value ?? '');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('parsing error on', { value });
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import Icon, { IconProps } from './Icon';
|
||||||
|
|
||||||
type Props = {
|
const Bell: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||||
size: number | string;
|
|
||||||
color: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Bell = ({ size, color }: Props) => {
|
|
||||||
return (
|
return (
|
||||||
<svg fill={color} xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 448 512">
|
<Icon width={width} height={height} className={className} viewBox="0 0 448 512">
|
||||||
<path d="M224 512c35.32 0 63.97-28.65 63.97-64H160.03c0 35.35 28.65 64 63.97 64zm215.39-149.71c-19.32-20.76-55.47-51.99-55.47-154.29 0-77.7-54.48-139.9-127.94-155.16V32c0-17.67-14.32-32-31.98-32s-31.98 14.33-31.98 32v20.84C118.56 68.1 64.08 130.3 64.08 208c0 102.3-36.15 133.53-55.47 154.29-6 6.45-8.66 14.16-8.61 21.71.11 16.4 12.98 32 32.1 32h383.8c19.12 0 32-15.6 32.1-32 .05-7.55-2.61-15.27-8.61-21.71z" />
|
<path d="M224 512c35.32 0 63.97-28.65 63.97-64H160.03c0 35.35 28.65 64 63.97 64zm215.39-149.71c-19.32-20.76-55.47-51.99-55.47-154.29 0-77.7-54.48-139.9-127.94-155.16V32c0-17.67-14.32-32-31.98-32s-31.98 14.33-31.98 32v20.84C118.56 68.1 64.08 130.3 64.08 208c0 102.3-36.15 133.53-55.47 154.29-6 6.45-8.66 14.16-8.61 21.71.11 16.4 12.98 32 32.1 32h383.8c19.12 0 32-15.6 32.1-32 .05-7.55-2.61-15.27-8.61-21.71z" />
|
||||||
</svg>
|
</Icon>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export function sortTasks(a: Task, b: Task, taskSorting: TaskSorting) {
|
|||||||
if (b.dueDate && !a.dueDate) {
|
if (b.dueDate && !a.dueDate) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return dayjs(a.dueDate).diff(dayjs(b.dueDate));
|
return dayjs(a.dueDate.at).diff(dayjs(b.dueDate.at));
|
||||||
}
|
}
|
||||||
if (taskSorting.type === TaskSortingType.COMPLETE) {
|
if (taskSorting.type === TaskSortingType.COMPLETE) {
|
||||||
if (a.complete && !b.complete) {
|
if (a.complete && !b.complete) {
|
||||||
|
2
frontend/src/types.d.ts
vendored
2
frontend/src/types.d.ts
vendored
@ -110,7 +110,7 @@ type Task = {
|
|||||||
badges?: TaskBadges;
|
badges?: TaskBadges;
|
||||||
position: number;
|
position: number;
|
||||||
hasTime?: boolean;
|
hasTime?: boolean;
|
||||||
dueDate?: string;
|
dueDate: { at?: string; notifications?: Array<{ id: string; period: number; duration: string }> };
|
||||||
complete?: boolean;
|
complete?: boolean;
|
||||||
completedAt?: string | null;
|
completedAt?: string | null;
|
||||||
labels: TaskLabel[];
|
labels: TaskLabel[];
|
||||||
|
@ -187,6 +187,17 @@ type TaskComment struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TaskDueDateReminder struct {
|
||||||
|
DueDateReminderID uuid.UUID `json:"due_date_reminder_id"`
|
||||||
|
TaskID uuid.UUID `json:"task_id"`
|
||||||
|
Period int32 `json:"period"`
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TaskDueDateReminderDuration struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
type TaskGroup struct {
|
type TaskGroup struct {
|
||||||
TaskGroupID uuid.UUID `json:"task_group_id"`
|
TaskGroupID uuid.UUID `json:"task_group_id"`
|
||||||
ProjectID uuid.UUID `json:"project_id"`
|
ProjectID uuid.UUID `json:"project_id"`
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
type Querier interface {
|
type Querier interface {
|
||||||
CreateAuthToken(ctx context.Context, arg CreateAuthTokenParams) (AuthToken, error)
|
CreateAuthToken(ctx context.Context, arg CreateAuthTokenParams) (AuthToken, error)
|
||||||
CreateConfirmToken(ctx context.Context, email string) (UserAccountConfirmToken, error)
|
CreateConfirmToken(ctx context.Context, email string) (UserAccountConfirmToken, error)
|
||||||
|
CreateDueDateReminder(ctx context.Context, arg CreateDueDateReminderParams) (TaskDueDateReminder, error)
|
||||||
CreateInvitedProjectMember(ctx context.Context, arg CreateInvitedProjectMemberParams) (ProjectMemberInvited, error)
|
CreateInvitedProjectMember(ctx context.Context, arg CreateInvitedProjectMemberParams) (ProjectMemberInvited, error)
|
||||||
CreateInvitedUser(ctx context.Context, email string) (UserAccountInvited, error)
|
CreateInvitedUser(ctx context.Context, email string) (UserAccountInvited, error)
|
||||||
CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error)
|
CreateLabelColor(ctx context.Context, arg CreateLabelColorParams) (LabelColor, error)
|
||||||
@ -40,6 +41,7 @@ type Querier interface {
|
|||||||
DeleteAuthTokenByID(ctx context.Context, tokenID uuid.UUID) error
|
DeleteAuthTokenByID(ctx context.Context, tokenID uuid.UUID) error
|
||||||
DeleteAuthTokenByUserID(ctx context.Context, userID uuid.UUID) error
|
DeleteAuthTokenByUserID(ctx context.Context, userID uuid.UUID) error
|
||||||
DeleteConfirmTokenForEmail(ctx context.Context, email string) error
|
DeleteConfirmTokenForEmail(ctx context.Context, email string) error
|
||||||
|
DeleteDueDateReminder(ctx context.Context, dueDateReminderID uuid.UUID) error
|
||||||
DeleteExpiredTokens(ctx context.Context) error
|
DeleteExpiredTokens(ctx context.Context) error
|
||||||
DeleteInvitedProjectMemberByID(ctx context.Context, projectMemberInvitedID uuid.UUID) error
|
DeleteInvitedProjectMemberByID(ctx context.Context, projectMemberInvitedID uuid.UUID) error
|
||||||
DeleteInvitedUserAccount(ctx context.Context, userAccountInvitedID uuid.UUID) (UserAccountInvited, error)
|
DeleteInvitedUserAccount(ctx context.Context, userAccountInvitedID uuid.UUID) (UserAccountInvited, error)
|
||||||
@ -80,6 +82,7 @@ type Querier interface {
|
|||||||
GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskComment, error)
|
GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskComment, error)
|
||||||
GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
|
GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
|
||||||
GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
|
GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
|
||||||
|
GetDueDateRemindersForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskDueDateReminder, error)
|
||||||
GetInvitedMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]GetInvitedMembersForProjectIDRow, error)
|
GetInvitedMembersForProjectID(ctx context.Context, projectID uuid.UUID) ([]GetInvitedMembersForProjectIDRow, error)
|
||||||
GetInvitedUserAccounts(ctx context.Context) ([]UserAccountInvited, error)
|
GetInvitedUserAccounts(ctx context.Context) ([]UserAccountInvited, error)
|
||||||
GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error)
|
GetInvitedUserByEmail(ctx context.Context, email string) (UserAccountInvited, error)
|
||||||
@ -150,6 +153,7 @@ type Querier interface {
|
|||||||
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
|
SetTaskGroupName(ctx context.Context, arg SetTaskGroupNameParams) (TaskGroup, error)
|
||||||
SetUserActiveByEmail(ctx context.Context, email string) (UserAccount, error)
|
SetUserActiveByEmail(ctx context.Context, email string) (UserAccount, error)
|
||||||
SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
|
SetUserPassword(ctx context.Context, arg SetUserPasswordParams) (UserAccount, error)
|
||||||
|
UpdateDueDateReminder(ctx context.Context, arg UpdateDueDateReminderParams) (TaskDueDateReminder, error)
|
||||||
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
|
UpdateProjectLabel(ctx context.Context, arg UpdateProjectLabelParams) (ProjectLabel, error)
|
||||||
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
|
UpdateProjectLabelColor(ctx context.Context, arg UpdateProjectLabelColorParams) (ProjectLabel, error)
|
||||||
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
UpdateProjectLabelName(ctx context.Context, arg UpdateProjectLabelNameParams) (ProjectLabel, error)
|
||||||
|
@ -116,3 +116,16 @@ SELECT task.* FROM task_assigned
|
|||||||
|
|
||||||
-- name: GetCommentCountForTask :one
|
-- name: GetCommentCountForTask :one
|
||||||
SELECT COUNT(*) FROM task_comment WHERE task_id = $1;
|
SELECT COUNT(*) FROM task_comment WHERE task_id = $1;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: CreateDueDateReminder :one
|
||||||
|
INSERT INTO task_due_date_reminder (task_id, period, duration) VALUES ($1, $2, $3) RETURNING *;
|
||||||
|
|
||||||
|
-- name: UpdateDueDateReminder :one
|
||||||
|
UPDATE task_due_date_reminder SET period = $2, duration = $3 WHERE due_date_reminder_id = $1 RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetDueDateRemindersForTaskID :many
|
||||||
|
SELECT * FROM task_due_date_reminder WHERE task_id = $1;
|
||||||
|
|
||||||
|
-- name: DeleteDueDateReminder :exec
|
||||||
|
DELETE FROM task_due_date_reminder WHERE due_date_reminder_id = $1;
|
||||||
|
@ -12,6 +12,28 @@ import (
|
|||||||
"github.com/lib/pq"
|
"github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const createDueDateReminder = `-- name: CreateDueDateReminder :one
|
||||||
|
INSERT INTO task_due_date_reminder (task_id, period, duration) VALUES ($1, $2, $3) RETURNING due_date_reminder_id, task_id, period, duration
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateDueDateReminderParams struct {
|
||||||
|
TaskID uuid.UUID `json:"task_id"`
|
||||||
|
Period int32 `json:"period"`
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateDueDateReminder(ctx context.Context, arg CreateDueDateReminderParams) (TaskDueDateReminder, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createDueDateReminder, arg.TaskID, arg.Period, arg.Duration)
|
||||||
|
var i TaskDueDateReminder
|
||||||
|
err := row.Scan(
|
||||||
|
&i.DueDateReminderID,
|
||||||
|
&i.TaskID,
|
||||||
|
&i.Period,
|
||||||
|
&i.Duration,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
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, has_time, short_id
|
VALUES($1, $2, $3, $4) RETURNING task_id, task_group_id, created_at, name, position, description, due_date, complete, completed_at, has_time, short_id
|
||||||
@ -144,6 +166,15 @@ func (q *Queries) CreateTaskWatcher(ctx context.Context, arg CreateTaskWatcherPa
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deleteDueDateReminder = `-- name: DeleteDueDateReminder :exec
|
||||||
|
DELETE FROM task_due_date_reminder WHERE due_date_reminder_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteDueDateReminder(ctx context.Context, dueDateReminderID uuid.UUID) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteDueDateReminder, dueDateReminderID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const deleteTaskByID = `-- name: DeleteTaskByID :exec
|
const deleteTaskByID = `-- name: DeleteTaskByID :exec
|
||||||
DELETE FROM task WHERE task_id = $1
|
DELETE FROM task WHERE task_id = $1
|
||||||
`
|
`
|
||||||
@ -403,6 +434,38 @@ func (q *Queries) GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDueDateRemindersForTaskID = `-- name: GetDueDateRemindersForTaskID :many
|
||||||
|
SELECT due_date_reminder_id, task_id, period, duration FROM task_due_date_reminder WHERE task_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetDueDateRemindersForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskDueDateReminder, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, getDueDateRemindersForTaskID, taskID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []TaskDueDateReminder
|
||||||
|
for rows.Next() {
|
||||||
|
var i TaskDueDateReminder
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.DueDateReminderID,
|
||||||
|
&i.TaskID,
|
||||||
|
&i.Period,
|
||||||
|
&i.Duration,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
const getProjectIDForTask = `-- name: GetProjectIDForTask :one
|
const getProjectIDForTask = `-- name: GetProjectIDForTask :one
|
||||||
SELECT project_id FROM task
|
SELECT project_id FROM task
|
||||||
INNER JOIN task_group ON task_group.task_group_id = task.task_group_id
|
INNER JOIN task_group ON task_group.task_group_id = task.task_group_id
|
||||||
@ -651,6 +714,28 @@ func (q *Queries) SetTaskComplete(ctx context.Context, arg SetTaskCompleteParams
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateDueDateReminder = `-- name: UpdateDueDateReminder :one
|
||||||
|
UPDATE task_due_date_reminder SET period = $2, duration = $3 WHERE due_date_reminder_id = $1 RETURNING due_date_reminder_id, task_id, period, duration
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateDueDateReminderParams struct {
|
||||||
|
DueDateReminderID uuid.UUID `json:"due_date_reminder_id"`
|
||||||
|
Period int32 `json:"period"`
|
||||||
|
Duration string `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateDueDateReminder(ctx context.Context, arg UpdateDueDateReminderParams) (TaskDueDateReminder, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateDueDateReminder, arg.DueDateReminderID, arg.Period, arg.Duration)
|
||||||
|
var i TaskDueDateReminder
|
||||||
|
err := row.Scan(
|
||||||
|
&i.DueDateReminderID,
|
||||||
|
&i.TaskID,
|
||||||
|
&i.Period,
|
||||||
|
&i.Duration,
|
||||||
|
)
|
||||||
|
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 = $3 WHERE task_comment_id = $1 RETURNING task_comment_id, task_id, created_at, updated_at, created_by, pinned, message
|
||||||
`
|
`
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -60,6 +60,16 @@ type CreateTaskCommentPayload struct {
|
|||||||
Comment *db.TaskComment `json:"comment"`
|
Comment *db.TaskComment `json:"comment"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateTaskDueDateNotification struct {
|
||||||
|
TaskID uuid.UUID `json:"taskID"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
Duration DueDateNotificationDuration `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTaskDueDateNotificationsResult struct {
|
||||||
|
Notifications []DueDateNotification `json:"notifications"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateTeamMember struct {
|
type CreateTeamMember struct {
|
||||||
UserID uuid.UUID `json:"userID"`
|
UserID uuid.UUID `json:"userID"`
|
||||||
TeamID uuid.UUID `json:"teamID"`
|
TeamID uuid.UUID `json:"teamID"`
|
||||||
@ -144,6 +154,14 @@ type DeleteTaskCommentPayload struct {
|
|||||||
CommentID uuid.UUID `json:"commentID"`
|
CommentID uuid.UUID `json:"commentID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteTaskDueDateNotification struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTaskDueDateNotificationsResult struct {
|
||||||
|
Notifications []uuid.UUID `json:"notifications"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteTaskGroupInput struct {
|
type DeleteTaskGroupInput struct {
|
||||||
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
||||||
}
|
}
|
||||||
@ -203,6 +221,17 @@ type DeleteUserAccountPayload struct {
|
|||||||
UserAccount *db.UserAccount `json:"userAccount"`
|
UserAccount *db.UserAccount `json:"userAccount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DueDate struct {
|
||||||
|
At *time.Time `json:"at"`
|
||||||
|
Notifications []DueDateNotification `json:"notifications"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DueDateNotification struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
Duration DueDateNotificationDuration `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
type DuplicateTaskGroup struct {
|
type DuplicateTaskGroup struct {
|
||||||
ProjectID uuid.UUID `json:"projectID"`
|
ProjectID uuid.UUID `json:"projectID"`
|
||||||
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
||||||
@ -599,6 +628,16 @@ type UpdateTaskDueDate struct {
|
|||||||
DueDate *time.Time `json:"dueDate"`
|
DueDate *time.Time `json:"dueDate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateTaskDueDateNotification struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
|
Period int `json:"period"`
|
||||||
|
Duration DueDateNotificationDuration `json:"duration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTaskDueDateNotificationsResult struct {
|
||||||
|
Notifications []DueDateNotification `json:"notifications"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateTaskGroupName struct {
|
type UpdateTaskGroupName struct {
|
||||||
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
TaskGroupID uuid.UUID `json:"taskGroupID"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -821,6 +860,51 @@ func (e ActivityType) MarshalGQL(w io.Writer) {
|
|||||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DueDateNotificationDuration string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DueDateNotificationDurationMinute DueDateNotificationDuration = "MINUTE"
|
||||||
|
DueDateNotificationDurationHour DueDateNotificationDuration = "HOUR"
|
||||||
|
DueDateNotificationDurationDay DueDateNotificationDuration = "DAY"
|
||||||
|
DueDateNotificationDurationWeek DueDateNotificationDuration = "WEEK"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllDueDateNotificationDuration = []DueDateNotificationDuration{
|
||||||
|
DueDateNotificationDurationMinute,
|
||||||
|
DueDateNotificationDurationHour,
|
||||||
|
DueDateNotificationDurationDay,
|
||||||
|
DueDateNotificationDurationWeek,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e DueDateNotificationDuration) IsValid() bool {
|
||||||
|
switch e {
|
||||||
|
case DueDateNotificationDurationMinute, DueDateNotificationDurationHour, DueDateNotificationDurationDay, DueDateNotificationDurationWeek:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e DueDateNotificationDuration) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DueDateNotificationDuration) UnmarshalGQL(v interface{}) error {
|
||||||
|
str, ok := v.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("enums must be strings")
|
||||||
|
}
|
||||||
|
|
||||||
|
*e = DueDateNotificationDuration(str)
|
||||||
|
if !e.IsValid() {
|
||||||
|
return fmt.Errorf("%s is not a valid DueDateNotificationDuration", str)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e DueDateNotificationDuration) MarshalGQL(w io.Writer) {
|
||||||
|
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||||
|
}
|
||||||
|
|
||||||
type MyTasksSort string
|
type MyTasksSort string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
enum DueDateNotificationDuration {
|
||||||
|
MINUTE
|
||||||
|
HOUR
|
||||||
|
DAY
|
||||||
|
WEEK
|
||||||
|
}
|
||||||
|
|
||||||
type TaskLabel {
|
type TaskLabel {
|
||||||
id: ID!
|
id: ID!
|
||||||
@ -20,6 +26,18 @@ type TaskBadges {
|
|||||||
comments: CommentsBadge
|
comments: CommentsBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DueDateNotification {
|
||||||
|
id: ID!
|
||||||
|
period: Int!
|
||||||
|
duration: DueDateNotificationDuration!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DueDate {
|
||||||
|
at: Time
|
||||||
|
notifications: [DueDateNotification!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Task {
|
type Task {
|
||||||
id: ID!
|
id: ID!
|
||||||
shortId: String!
|
shortId: String!
|
||||||
@ -29,7 +47,7 @@ type Task {
|
|||||||
position: Float!
|
position: Float!
|
||||||
description: String
|
description: String
|
||||||
watched: Boolean!
|
watched: Boolean!
|
||||||
dueDate: Time
|
dueDate: DueDate!
|
||||||
hasTime: Boolean!
|
hasTime: Boolean!
|
||||||
complete: Boolean!
|
complete: Boolean!
|
||||||
completedAt: Time
|
completedAt: Time
|
||||||
@ -371,8 +389,45 @@ extend type Mutation {
|
|||||||
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
||||||
unassignTask(input: UnassignTaskInput):
|
unassignTask(input: UnassignTaskInput):
|
||||||
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
||||||
|
|
||||||
|
|
||||||
|
createTaskDueDateNotifications(input: [CreateTaskDueDateNotification!]!):
|
||||||
|
CreateTaskDueDateNotificationsResult!
|
||||||
|
updateTaskDueDateNotifications(input: [UpdateTaskDueDateNotification!]!):
|
||||||
|
UpdateTaskDueDateNotificationsResult!
|
||||||
|
deleteTaskDueDateNotifications(input: [DeleteTaskDueDateNotification!]!):
|
||||||
|
DeleteTaskDueDateNotificationsResult!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input DeleteTaskDueDateNotification {
|
||||||
|
id: UUID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTaskDueDateNotificationsResult {
|
||||||
|
notifications: [UUID!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateTaskDueDateNotification {
|
||||||
|
id: UUID!
|
||||||
|
period: Int!
|
||||||
|
duration: DueDateNotificationDuration!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTaskDueDateNotificationsResult {
|
||||||
|
notifications: [DueDateNotification!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateTaskDueDateNotification {
|
||||||
|
taskID: UUID!
|
||||||
|
period: Int!
|
||||||
|
duration: DueDateNotificationDuration!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTaskDueDateNotificationsResult {
|
||||||
|
notifications: [DueDateNotification!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
input ToggleTaskWatch {
|
input ToggleTaskWatch {
|
||||||
taskID: UUID!
|
taskID: UUID!
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
enum DueDateNotificationDuration {
|
||||||
|
MINUTE
|
||||||
|
HOUR
|
||||||
|
DAY
|
||||||
|
WEEK
|
||||||
|
}
|
||||||
|
|
||||||
type TaskLabel {
|
type TaskLabel {
|
||||||
id: ID!
|
id: ID!
|
||||||
@ -20,6 +26,18 @@ type TaskBadges {
|
|||||||
comments: CommentsBadge
|
comments: CommentsBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DueDateNotification {
|
||||||
|
id: ID!
|
||||||
|
period: Int!
|
||||||
|
duration: DueDateNotificationDuration!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DueDate {
|
||||||
|
at: Time
|
||||||
|
notifications: [DueDateNotification!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type Task {
|
type Task {
|
||||||
id: ID!
|
id: ID!
|
||||||
shortId: String!
|
shortId: String!
|
||||||
@ -29,7 +47,7 @@ type Task {
|
|||||||
position: Float!
|
position: Float!
|
||||||
description: String
|
description: String
|
||||||
watched: Boolean!
|
watched: Boolean!
|
||||||
dueDate: Time
|
dueDate: DueDate!
|
||||||
hasTime: Boolean!
|
hasTime: Boolean!
|
||||||
complete: Boolean!
|
complete: Boolean!
|
||||||
completedAt: Time
|
completedAt: Time
|
||||||
|
@ -31,8 +31,45 @@ extend type Mutation {
|
|||||||
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
||||||
unassignTask(input: UnassignTaskInput):
|
unassignTask(input: UnassignTaskInput):
|
||||||
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
Task! @hasRole(roles: [ADMIN, MEMBER], level: PROJECT, type: TASK)
|
||||||
|
|
||||||
|
|
||||||
|
createTaskDueDateNotifications(input: [CreateTaskDueDateNotification!]!):
|
||||||
|
CreateTaskDueDateNotificationsResult!
|
||||||
|
updateTaskDueDateNotifications(input: [UpdateTaskDueDateNotification!]!):
|
||||||
|
UpdateTaskDueDateNotificationsResult!
|
||||||
|
deleteTaskDueDateNotifications(input: [DeleteTaskDueDateNotification!]!):
|
||||||
|
DeleteTaskDueDateNotificationsResult!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input DeleteTaskDueDateNotification {
|
||||||
|
id: UUID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTaskDueDateNotificationsResult {
|
||||||
|
notifications: [UUID!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateTaskDueDateNotification {
|
||||||
|
id: UUID!
|
||||||
|
period: Int!
|
||||||
|
duration: DueDateNotificationDuration!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTaskDueDateNotificationsResult {
|
||||||
|
notifications: [DueDateNotification!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateTaskDueDateNotification {
|
||||||
|
taskID: UUID!
|
||||||
|
period: Int!
|
||||||
|
duration: DueDateNotificationDuration!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateTaskDueDateNotificationsResult {
|
||||||
|
notifications: [DueDateNotification!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
input ToggleTaskWatch {
|
input ToggleTaskWatch {
|
||||||
taskID: UUID!
|
taskID: UUID!
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -501,8 +501,20 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &db.Task{}, err
|
return &db.Task{}, err
|
||||||
}
|
}
|
||||||
|
isSame := false
|
||||||
|
if prevTask.DueDate.Valid && input.DueDate != nil {
|
||||||
|
if prevTask.DueDate.Time == *input.DueDate && prevTask.HasTime == input.HasTime {
|
||||||
|
isSame = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"isSame": isSame,
|
||||||
|
"prev": prevTask.HasTime,
|
||||||
|
"new": input.HasTime,
|
||||||
|
}).Info("chekcing same")
|
||||||
data := map[string]string{}
|
data := map[string]string{}
|
||||||
var activityType = TASK_DUE_DATE_ADDED
|
var activityType = TASK_DUE_DATE_ADDED
|
||||||
|
data["HasTime"] = strconv.FormatBool(input.HasTime)
|
||||||
if input.DueDate == nil && prevTask.DueDate.Valid {
|
if input.DueDate == nil && prevTask.DueDate.Valid {
|
||||||
activityType = TASK_DUE_DATE_REMOVED
|
activityType = TASK_DUE_DATE_REMOVED
|
||||||
data["PrevDueDate"] = prevTask.DueDate.Time.String()
|
data["PrevDueDate"] = prevTask.DueDate.Time.String()
|
||||||
@ -529,13 +541,15 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
|
|||||||
})
|
})
|
||||||
createdAt := time.Now().UTC()
|
createdAt := time.Now().UTC()
|
||||||
d, _ := json.Marshal(data)
|
d, _ := json.Marshal(data)
|
||||||
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
|
if !isSame {
|
||||||
TaskID: task.TaskID,
|
_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
|
||||||
Data: d,
|
TaskID: task.TaskID,
|
||||||
CausedBy: userID,
|
Data: d,
|
||||||
CreatedAt: createdAt,
|
CausedBy: userID,
|
||||||
ActivityTypeID: activityType,
|
CreatedAt: createdAt,
|
||||||
})
|
ActivityTypeID: activityType,
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
task, err = r.Repository.GetTaskByID(ctx, input.TaskID)
|
task, err = r.Repository.GetTaskByID(ctx, input.TaskID)
|
||||||
}
|
}
|
||||||
@ -670,6 +684,73 @@ func (r *mutationResolver) UnassignTask(ctx context.Context, input *UnassignTask
|
|||||||
return &task, nil
|
return &task, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) CreateTaskDueDateNotifications(ctx context.Context, input []CreateTaskDueDateNotification) (*CreateTaskDueDateNotificationsResult, error) {
|
||||||
|
reminders := []DueDateNotification{}
|
||||||
|
for _, in := range input {
|
||||||
|
n, err := r.Repository.CreateDueDateReminder(ctx, db.CreateDueDateReminderParams{
|
||||||
|
TaskID: in.TaskID,
|
||||||
|
Period: int32(in.Period),
|
||||||
|
Duration: in.Duration.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return &CreateTaskDueDateNotificationsResult{}, err
|
||||||
|
}
|
||||||
|
duration := DueDateNotificationDuration(n.Duration)
|
||||||
|
if !duration.IsValid() {
|
||||||
|
log.WithField("duration", n.Duration).Error("invalid duration found")
|
||||||
|
return &CreateTaskDueDateNotificationsResult{}, errors.New("invalid duration")
|
||||||
|
}
|
||||||
|
reminders = append(reminders, DueDateNotification{
|
||||||
|
ID: n.DueDateReminderID,
|
||||||
|
Period: int(n.Period),
|
||||||
|
Duration: duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &CreateTaskDueDateNotificationsResult{
|
||||||
|
Notifications: reminders,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) UpdateTaskDueDateNotifications(ctx context.Context, input []UpdateTaskDueDateNotification) (*UpdateTaskDueDateNotificationsResult, error) {
|
||||||
|
reminders := []DueDateNotification{}
|
||||||
|
for _, in := range input {
|
||||||
|
n, err := r.Repository.UpdateDueDateReminder(ctx, db.UpdateDueDateReminderParams{
|
||||||
|
DueDateReminderID: in.ID,
|
||||||
|
Period: int32(in.Period),
|
||||||
|
Duration: in.Duration.String(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return &UpdateTaskDueDateNotificationsResult{}, err
|
||||||
|
}
|
||||||
|
duration := DueDateNotificationDuration(n.Duration)
|
||||||
|
if !duration.IsValid() {
|
||||||
|
log.WithField("duration", n.Duration).Error("invalid duration found")
|
||||||
|
return &UpdateTaskDueDateNotificationsResult{}, errors.New("invalid duration")
|
||||||
|
}
|
||||||
|
reminders = append(reminders, DueDateNotification{
|
||||||
|
ID: n.DueDateReminderID,
|
||||||
|
Period: int(n.Period),
|
||||||
|
Duration: duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &UpdateTaskDueDateNotificationsResult{
|
||||||
|
Notifications: reminders,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) DeleteTaskDueDateNotifications(ctx context.Context, input []DeleteTaskDueDateNotification) (*DeleteTaskDueDateNotificationsResult, error) {
|
||||||
|
ids := []uuid.UUID{}
|
||||||
|
for _, n := range input {
|
||||||
|
err := r.Repository.DeleteDueDateReminder(ctx, n.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("error while deleting task due date notification")
|
||||||
|
return &DeleteTaskDueDateNotificationsResult{}, err
|
||||||
|
}
|
||||||
|
ids = append(ids, n.ID)
|
||||||
|
}
|
||||||
|
return &DeleteTaskDueDateNotificationsResult{Notifications: ids}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task, error) {
|
func (r *queryResolver) FindTask(ctx context.Context, input FindTask) (*db.Task, error) {
|
||||||
var taskID uuid.UUID
|
var taskID uuid.UUID
|
||||||
var err error
|
var err error
|
||||||
@ -724,11 +805,34 @@ func (r *taskResolver) Watched(ctx context.Context, obj *db.Task) (bool, error)
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *taskResolver) DueDate(ctx context.Context, obj *db.Task) (*time.Time, error) {
|
func (r *taskResolver) DueDate(ctx context.Context, obj *db.Task) (*DueDate, error) {
|
||||||
if obj.DueDate.Valid {
|
nots, err := r.Repository.GetDueDateRemindersForTaskID(ctx, obj.TaskID)
|
||||||
return &obj.DueDate.Time, nil
|
if err != nil {
|
||||||
|
log.WithError(err).Error("error while fetching due date reminders")
|
||||||
|
return &DueDate{}, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
reminders := []DueDateNotification{}
|
||||||
|
for _, n := range nots {
|
||||||
|
duration := DueDateNotificationDuration(n.Duration)
|
||||||
|
if !duration.IsValid() {
|
||||||
|
log.WithField("duration", n.Duration).Error("invalid duration found")
|
||||||
|
return &DueDate{}, errors.New("invalid duration")
|
||||||
|
}
|
||||||
|
reminders = append(reminders, DueDateNotification{
|
||||||
|
ID: n.DueDateReminderID,
|
||||||
|
Period: int(n.Period),
|
||||||
|
Duration: duration,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var time *time.Time
|
||||||
|
if obj.DueDate.Valid {
|
||||||
|
time = &obj.DueDate.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DueDate{
|
||||||
|
At: time,
|
||||||
|
Notifications: reminders,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *taskResolver) CompletedAt(ctx context.Context, obj *db.Task) (*time.Time, error) {
|
func (r *taskResolver) CompletedAt(ctx context.Context, obj *db.Task) (*time.Time, error) {
|
||||||
@ -904,7 +1008,10 @@ func (r *taskChecklistItemResolver) ID(ctx context.Context, obj *db.TaskChecklis
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *taskChecklistItemResolver) DueDate(ctx context.Context, obj *db.TaskChecklistItem) (*time.Time, error) {
|
func (r *taskChecklistItemResolver) DueDate(ctx context.Context, obj *db.TaskChecklistItem) (*time.Time, error) {
|
||||||
panic(fmt.Errorf("not implemented"))
|
if obj.DueDate.Valid {
|
||||||
|
return &obj.DueDate.Time, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *taskCommentResolver) ID(ctx context.Context, obj *db.TaskComment) (uuid.UUID, error) {
|
func (r *taskCommentResolver) ID(ctx context.Context, obj *db.TaskComment) (uuid.UUID, error) {
|
||||||
|
15
migrations/0070_add-task_due_date_notification.up.sql
Normal file
15
migrations/0070_add-task_due_date_notification.up.sql
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE task_due_date_reminder_duration (
|
||||||
|
code text PRIMARY KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO task_due_date_reminder_duration VALUES ('MINUTE');
|
||||||
|
INSERT INTO task_due_date_reminder_duration VALUES ('HOUR');
|
||||||
|
INSERT INTO task_due_date_reminder_duration VALUES ('DAY');
|
||||||
|
INSERT INTO task_due_date_reminder_duration VALUES ('WEEK');
|
||||||
|
|
||||||
|
CREATE TABLE task_due_date_reminder (
|
||||||
|
due_date_reminder_id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
task_id uuid NOT NULL REFERENCES task(task_id) ON DELETE CASCADE,
|
||||||
|
period int NOT NULL,
|
||||||
|
duration text NOT NULL REFERENCES task_due_date_reminder_duration(code) ON DELETE CASCADE
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user