feat: redesign due date manager
This commit is contained in:
		@@ -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,7 +393,78 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
 | 
				
			|||||||
          </ActionIcon>
 | 
					          </ActionIcon>
 | 
				
			||||||
        </ActionsWrapper>
 | 
					        </ActionsWrapper>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      <ActionsWrapper>
 | 
					      {notifications.map((n, idx) => (
 | 
				
			||||||
 | 
					        <ActionsWrapper key={n.internalId}>
 | 
				
			||||||
 | 
					          <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={() => {
 | 
				
			||||||
 | 
					              if (startDate && notifications.findIndex((n) => Number.isNaN(n.period)) === -1) {
 | 
				
			||||||
 | 
					                onDueDateChange(task, startDate, hasTime, { current: notifications, removed: removedNotifications });
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }}
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            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>
 | 
				
			||||||
          {!hasTime && (
 | 
					          {!hasTime && (
 | 
				
			||||||
            <ActionIcon
 | 
					            <ActionIcon
 | 
				
			||||||
              onClick={() => {
 | 
					              onClick={() => {
 | 
				
			||||||
@@ -314,8 +479,8 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
 | 
				
			|||||||
              <Clock width={16} height={16} />
 | 
					              <Clock width={16} height={16} />
 | 
				
			||||||
            </ActionIcon>
 | 
					            </ActionIcon>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
        <ClearButton onClick={() => setStartDate(null)}>{hasTime ? 'Clear all' : 'Clear'}</ClearButton>
 | 
					        </RightWrapper>
 | 
				
			||||||
      </ActionsWrapper>
 | 
					      </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,14 +9,22 @@ 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) {
 | 
				
			||||||
 | 
					    if (hasTime) {
 | 
				
			||||||
      return dayjs(timestamp).format('MMM D [at] h:mm A');
 | 
					      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,6 +541,7 @@ 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)
 | 
				
			||||||
 | 
							if !isSame {
 | 
				
			||||||
			_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
 | 
								_, err = r.Repository.CreateTaskActivity(ctx, db.CreateTaskActivityParams{
 | 
				
			||||||
				TaskID:         task.TaskID,
 | 
									TaskID:         task.TaskID,
 | 
				
			||||||
				Data:           d,
 | 
									Data:           d,
 | 
				
			||||||
@@ -536,6 +549,7 @@ func (r *mutationResolver) UpdateTaskDueDate(ctx context.Context, input UpdateTa
 | 
				
			|||||||
				CreatedAt:      createdAt,
 | 
									CreatedAt:      createdAt,
 | 
				
			||||||
				ActivityTypeID: activityType,
 | 
									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
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
		Reference in New Issue
	
	Block a user