feat: redesign due date manager
This commit is contained in:
@ -61,7 +61,6 @@ const Routes: React.FC = () => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
console.log('loading', loading);
|
||||
if (loading) return null;
|
||||
return (
|
||||
<Switch>
|
||||
|
@ -9,7 +9,6 @@ const Auth = () => {
|
||||
const history = useHistory();
|
||||
const location = useLocation<{ redirect: string } | undefined>();
|
||||
const { setUser } = useContext(UserContext);
|
||||
console.log('auth');
|
||||
const login = (
|
||||
data: LoginFormData,
|
||||
setComplete: (val: boolean) => void,
|
||||
|
@ -562,13 +562,36 @@ const Projects = () => {
|
||||
onCancel={() => null}
|
||||
onDueDateChange={(task, dueDate, hasTime) => {
|
||||
if (dateEditor.task) {
|
||||
updateTaskDueDate({ variables: { taskID: dateEditor.task.id, dueDate, hasTime } });
|
||||
setDateEditor((prev) => ({ ...prev, task: { ...task, dueDate: dueDate.toISOString(), hasTime } }));
|
||||
hidePopup();
|
||||
updateTaskDueDate({
|
||||
variables: {
|
||||
taskID: dateEditor.task.id,
|
||||
dueDate,
|
||||
hasTime,
|
||||
deleteNotifications: [],
|
||||
updateNotifications: [],
|
||||
createNotifications: [],
|
||||
},
|
||||
});
|
||||
setDateEditor((prev) => ({
|
||||
...prev,
|
||||
task: { ...task, dueDate: { at: dueDate.toISOString(), notifications: [] }, hasTime },
|
||||
}));
|
||||
}
|
||||
}}
|
||||
onRemoveDueDate={(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 } }));
|
||||
}
|
||||
}}
|
||||
@ -655,8 +678,8 @@ const Projects = () => {
|
||||
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;
|
||||
const first = dayjs(a.dueDate);
|
||||
const second = dayjs(b.dueDate);
|
||||
const first = dayjs(a.dueDate.at);
|
||||
const second = dayjs(b.dueDate.at);
|
||||
if (first.isSame(second, 'minute')) return 0;
|
||||
if (first.isAfter(second)) return -1;
|
||||
return 1;
|
||||
@ -792,10 +815,19 @@ const Projects = () => {
|
||||
history.push(`${match.url}/c/${task.id}`);
|
||||
}}
|
||||
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'}
|
||||
dueDate={task.dueDate}
|
||||
dueDate={task.dueDate.at}
|
||||
hasTime={task.hasTime ?? false}
|
||||
name={task.name}
|
||||
onEditName={(name) => updateTaskName({ variables: { taskID: task.id, name } })}
|
||||
@ -821,7 +853,9 @@ const Projects = () => {
|
||||
<EditorCell width={120}>
|
||||
<DueDateEditorLabel>
|
||||
{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>
|
||||
</EditorCell>
|
||||
|
@ -446,7 +446,7 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
|
||||
checklist: null,
|
||||
},
|
||||
position,
|
||||
dueDate: null,
|
||||
dueDate: { at: null },
|
||||
description: null,
|
||||
labels: [],
|
||||
assigned: [],
|
||||
@ -801,12 +801,30 @@ const ProjectBoard: React.FC<ProjectBoardProps> = ({ projectID, onCardLabelClick
|
||||
<DueDateManager
|
||||
task={task}
|
||||
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) => {
|
||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate, hasTime } });
|
||||
// hidePopup();
|
||||
hidePopup();
|
||||
updateTaskDueDate({
|
||||
variables: {
|
||||
taskID: t.id,
|
||||
dueDate: newDueDate,
|
||||
hasTime,
|
||||
deleteNotifications: [],
|
||||
updateNotifications: [],
|
||||
createNotifications: [],
|
||||
},
|
||||
});
|
||||
}}
|
||||
onCancel={NOOP}
|
||||
/>
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
useUpdateTaskChecklistItemLocationMutation,
|
||||
useCreateTaskChecklistMutation,
|
||||
useFindTaskQuery,
|
||||
DueDateNotificationDuration,
|
||||
useUpdateTaskDueDateMutation,
|
||||
useSetTaskCompleteMutation,
|
||||
useAssignTaskMutation,
|
||||
@ -647,12 +648,79 @@ const Details: React.FC<DetailsProps> = ({
|
||||
<DueDateManager
|
||||
task={task}
|
||||
onRemoveDueDate={(t) => {
|
||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: null, hasTime: false } });
|
||||
// hidePopup();
|
||||
updateTaskDueDate({
|
||||
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) => {
|
||||
updateTaskDueDate({ variables: { taskID: t.id, dueDate: newDueDate, hasTime } });
|
||||
// hidePopup();
|
||||
onDueDateChange={(t, newDueDate, hasTime, notifications) => {
|
||||
const updatedNotifications = 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 {
|
||||
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}
|
||||
/>
|
||||
|
@ -39,7 +39,6 @@ const UsersRegister = () => {
|
||||
.then(async (x) => {
|
||||
const response = await x.json();
|
||||
const { setup } = response;
|
||||
console.log(response);
|
||||
if (setup) {
|
||||
history.replace(`/confirm?confirmToken=xxxx`);
|
||||
isRedirected = true;
|
||||
|
@ -1,9 +1,8 @@
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
import Button from 'shared/components/Button';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import Input from 'shared/components/Input';
|
||||
import ControlledInput from 'shared/components/ControlledInput';
|
||||
import { Clock } from 'shared/icons';
|
||||
import { Bell, Clock } from 'shared/icons';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
display: flex
|
||||
@ -22,27 +21,27 @@ display: flex
|
||||
& .react-datepicker__close-icon::after {
|
||||
background: none;
|
||||
font-size: 16px;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
}
|
||||
|
||||
& .react-datepicker-time__header {
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
}
|
||||
& .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-box ul.react-datepicker__time-list
|
||||
li.react-datepicker__time-list-item:hover {
|
||||
color: ${props => props.theme.colors.text.secondary};
|
||||
background: ${props => props.theme.colors.bg.secondary};
|
||||
color: ${(props) => props.theme.colors.text.secondary};
|
||||
background: ${(props) => props.theme.colors.bg.secondary};
|
||||
}
|
||||
& .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 {
|
||||
background: ${props => props.theme.colors.bg.primary};
|
||||
border: 1px solid ${props => props.theme.colors.border};
|
||||
background: ${(props) => props.theme.colors.bg.primary};
|
||||
border: 1px solid ${(props) => props.theme.colors.border};
|
||||
}
|
||||
|
||||
& .react-datepicker * {
|
||||
@ -82,12 +81,12 @@ display: flex
|
||||
}
|
||||
& .react-datepicker__day--selected {
|
||||
border-radius: 50%;
|
||||
background: ${props => props.theme.colors.primary};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: #fff;
|
||||
}
|
||||
& .react-datepicker__day--selected:hover {
|
||||
border-radius: 50%;
|
||||
background: ${props => props.theme.colors.primary};
|
||||
background: ${(props) => props.theme.colors.primary};
|
||||
color: #fff;
|
||||
}
|
||||
& .react-datepicker__header {
|
||||
@ -95,12 +94,12 @@ display: flex
|
||||
border: none;
|
||||
}
|
||||
& .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 {
|
||||
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;
|
||||
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
|
||||
padding: 0.7rem;
|
||||
@ -114,7 +113,7 @@ padding: 0.7rem;
|
||||
&:focus {
|
||||
box-shadow: 0 3px 10px 0 rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid rgba(115, 103, 240);
|
||||
background: ${props => props.theme.colors.bg.primary};
|
||||
background: ${(props) => props.theme.colors.bg.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
@ -142,9 +141,9 @@ export const AddDateRange = styled.div`
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: ${props => mixin.rgba(props.theme.colors.primary, 0.8)};
|
||||
color: ${(props) => mixin.rgba(props.theme.colors.primary, 0.8)};
|
||||
&:hover {
|
||||
color: ${props => mixin.rgba(props.theme.colors.primary, 1)};
|
||||
color: ${(props) => mixin.rgba(props.theme.colors.primary, 1)};
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
@ -201,18 +200,62 @@ export const ActionsWrapper = styled.div`
|
||||
align-items: center;
|
||||
& .react-datepicker-wrapper {
|
||||
margin-left: auto;
|
||||
width: 82px;
|
||||
width: 86px;
|
||||
}
|
||||
& .react-datepicker__input-container input {
|
||||
padding-bottom: 4px;
|
||||
padding-top: 4px;
|
||||
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)`
|
||||
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;
|
||||
flex: 0 0 auto;
|
||||
`;
|
||||
@ -222,7 +265,7 @@ export const ActionLabel = styled.div`
|
||||
line-height: 14px;
|
||||
`;
|
||||
|
||||
export const ActionIcon = styled.div`
|
||||
export const ActionIcon = styled.div<{ disabled?: boolean }>`
|
||||
height: 36px;
|
||||
min-height: 36px;
|
||||
min-width: 36px;
|
||||
@ -232,17 +275,25 @@ export const ActionIcon = styled.div`
|
||||
cursor: pointer;
|
||||
margin-right: 8px;
|
||||
svg {
|
||||
fill: ${props => props.theme.colors.text.primary};
|
||||
fill: ${(props) => props.theme.colors.text.primary};
|
||||
transition-duration: 0.2s;
|
||||
transition-property: background, border, box-shadow, fill;
|
||||
}
|
||||
&:hover svg {
|
||||
fill: ${props => props.theme.colors.text.secondary};
|
||||
fill: ${(props) => props.theme.colors.text.secondary};
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.disabled &&
|
||||
css`
|
||||
opacity: 0.8;
|
||||
cursor: not-allowed;
|
||||
`}
|
||||
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
export const ClearButton = styled.div`
|
||||
@ -260,8 +311,38 @@ export const ClearButton = styled.div`
|
||||
justify-content: center;
|
||||
transition-duration: 0.2s;
|
||||
transition-property: background, border, box-shadow, color, fill;
|
||||
color: ${props => props.theme.colors.text.primary};
|
||||
color: ${(props) => props.theme.colors.text.primary};
|
||||
&: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 DatePicker from 'react-datepicker';
|
||||
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 { getYear, getMonth } from 'date-fns';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import NOOP from 'shared/utils/noop';
|
||||
import { Clock, Cross } from 'shared/icons';
|
||||
import Select from 'react-select/src/Select';
|
||||
import { Bell, Clock, Cross, Plus, Trash } from 'shared/icons';
|
||||
|
||||
import {
|
||||
Wrapper,
|
||||
RemoveDueDate,
|
||||
SaveButton,
|
||||
RightWrapper,
|
||||
LeftWrapper,
|
||||
DueDateInput,
|
||||
DueDatePickerWrapper,
|
||||
ConfirmAddDueDate,
|
||||
@ -24,11 +29,19 @@ import {
|
||||
ActionsSeparator,
|
||||
ActionClock,
|
||||
ActionLabel,
|
||||
ControlWrapper,
|
||||
RemoveButton,
|
||||
ActionBell,
|
||||
} from './Styles';
|
||||
|
||||
type DueDateManagerProps = {
|
||||
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;
|
||||
onCancel: () => void;
|
||||
};
|
||||
@ -41,6 +54,39 @@ const FormField = styled.div`
|
||||
width: 50%;
|
||||
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`
|
||||
display: inline-block;
|
||||
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 currentDueDate = task.dueDate ? dayjs(task.dueDate).toDate() : null;
|
||||
const currentDueDate = task.dueDate.at ? dayjs(task.dueDate.at).toDate() : null;
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
@ -145,28 +252,7 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
const [startDate, setStartDate] = useState<Date | null>(currentDueDate);
|
||||
const [endDate, setEndDate] = useState<Date | null>(currentDueDate);
|
||||
const [hasTime, enableTime] = useState(task.hasTime ?? false);
|
||||
const firstRun = useRef<boolean>(true);
|
||||
|
||||
const debouncedFunctionRef = useRef((newDate: Date | null, nowHasTime: boolean) => {
|
||||
if (!firstRun.current) {
|
||||
if (newDate) {
|
||||
onDueDateChange(task, newDate, nowHasTime);
|
||||
} else {
|
||||
onRemoveDueDate(task);
|
||||
enableTime(false);
|
||||
}
|
||||
} else {
|
||||
firstRun.current = false;
|
||||
}
|
||||
});
|
||||
const debouncedChange = useCallback(
|
||||
_.debounce((newDate, nowHasTime) => debouncedFunctionRef.current(newDate, nowHasTime), 500),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
debouncedChange(startDate, hasTime);
|
||||
}, [startDate, hasTime]);
|
||||
const years = _.range(2010, getYear(new Date()) + 10, 1);
|
||||
const months = [
|
||||
'January',
|
||||
@ -183,33 +269,41 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
'December',
|
||||
];
|
||||
|
||||
const onChange = (dates: any) => {
|
||||
const [start, end] = dates;
|
||||
setStartDate(start);
|
||||
setEndDate(end);
|
||||
};
|
||||
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 (
|
||||
<Wrapper>
|
||||
<DateRangeInputs>
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
onChange={(date) => {
|
||||
if (!Array.isArray(date)) {
|
||||
if (!Array.isArray(date) && date !== null) {
|
||||
setStartDate(date);
|
||||
}
|
||||
}}
|
||||
popperClassName="picker-hidden"
|
||||
dateFormat="yyyy-MM-dd"
|
||||
disabledKeyboardNavigation
|
||||
isClearable
|
||||
placeholderText="Select due date"
|
||||
/>
|
||||
{isRange ? (
|
||||
<DatePicker
|
||||
selected={startDate}
|
||||
isClearable
|
||||
onChange={(date) => {
|
||||
if (!Array.isArray(date)) {
|
||||
setStartDate(date);
|
||||
@ -299,23 +393,94 @@ const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange,
|
||||
</ActionIcon>
|
||||
</ActionsWrapper>
|
||||
)}
|
||||
<ActionsWrapper>
|
||||
{!hasTime && (
|
||||
<ActionIcon
|
||||
{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 === null) {
|
||||
const today = new Date();
|
||||
today.setHours(12, 30, 0);
|
||||
setStartDate(today);
|
||||
if (startDate && notifications.findIndex((n) => Number.isNaN(n.period)) === -1) {
|
||||
onDueDateChange(task, startDate, hasTime, { current: notifications, removed: removedNotifications });
|
||||
}
|
||||
enableTime(true);
|
||||
}}
|
||||
>
|
||||
<Clock width={16} height={16} />
|
||||
Save
|
||||
</SaveButton>
|
||||
{currentDueDate !== null && (
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
onRemoveDueDate(task);
|
||||
}}
|
||||
>
|
||||
<Trash width={16} height={16} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</LeftWrapper>
|
||||
<RightWrapper>
|
||||
<ActionIcon
|
||||
// disabled={notifications.length === 3}
|
||||
disabled
|
||||
onClick={() => {
|
||||
/*
|
||||
setNotifications((prev) => [
|
||||
...prev,
|
||||
{
|
||||
externalId: null,
|
||||
internalId: `n${prev.length + 1}`,
|
||||
duration: notificationPeriodOptions[0],
|
||||
period: 10,
|
||||
},
|
||||
]);
|
||||
*/
|
||||
}}
|
||||
>
|
||||
<Bell width={16} height={16} />
|
||||
<ActionPlus width={8} height={8} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
<ClearButton onClick={() => setStartDate(null)}>{hasTime ? 'Clear all' : 'Clear'}</ClearButton>
|
||||
</ActionsWrapper>
|
||||
{!hasTime && (
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
if (startDate === null) {
|
||||
const today = new Date();
|
||||
today.setHours(12, 30, 0);
|
||||
setStartDate(today);
|
||||
}
|
||||
enableTime(true);
|
||||
}}
|
||||
>
|
||||
<Clock width={16} height={16} />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</RightWrapper>
|
||||
</ControlWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
@ -351,10 +351,10 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
||||
description=""
|
||||
labels={task.labels.map((label) => label.projectLabel)}
|
||||
dueDate={
|
||||
task.dueDate
|
||||
task.dueDate.at
|
||||
? {
|
||||
isPastDue: false,
|
||||
formattedDate: dayjs(task.dueDate).format('MMM D, YYYY'),
|
||||
formattedDate: dayjs(task.dueDate.at).format('MMM D, YYYY'),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
isFiltered = shouldFilter(!(task.dueDate && task.dueDate !== null));
|
||||
}
|
||||
if (task.dueDate) {
|
||||
const taskDueDate = dayjs(task.dueDate);
|
||||
const taskDueDate = dayjs(task.dueDate.at);
|
||||
const today = dayjs();
|
||||
let start;
|
||||
let end;
|
||||
@ -36,61 +36,31 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
isFiltered = shouldFilter(taskDueDate.isSame(today, 'day'));
|
||||
break;
|
||||
case DueDateFilterType.TOMORROW:
|
||||
isFiltered = shouldFilter(
|
||||
taskDueDate.isBefore(
|
||||
today
|
||||
.clone()
|
||||
.add(1, 'day')
|
||||
.endOf('day'),
|
||||
),
|
||||
);
|
||||
isFiltered = shouldFilter(taskDueDate.isBefore(today.clone().add(1, 'day').endOf('day')));
|
||||
break;
|
||||
case DueDateFilterType.THIS_WEEK:
|
||||
start = today
|
||||
.clone()
|
||||
.weekday(0)
|
||||
.startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.weekday(6)
|
||||
.endOf('day');
|
||||
start = today.clone().weekday(0).startOf('day');
|
||||
end = today.clone().weekday(6).endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
case DueDateFilterType.NEXT_WEEK:
|
||||
start = today
|
||||
.clone()
|
||||
.weekday(0)
|
||||
.add(7, 'day')
|
||||
.startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.weekday(6)
|
||||
.add(7, 'day')
|
||||
.endOf('day');
|
||||
start = today.clone().weekday(0).add(7, 'day').startOf('day');
|
||||
end = today.clone().weekday(6).add(7, 'day').endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
case DueDateFilterType.ONE_WEEK:
|
||||
start = today.clone().startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.add(7, 'day')
|
||||
.endOf('day');
|
||||
end = today.clone().add(7, 'day').endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
case DueDateFilterType.TWO_WEEKS:
|
||||
start = today.clone().startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.add(14, 'day')
|
||||
.endOf('day');
|
||||
end = today.clone().add(14, 'day').endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
case DueDateFilterType.THREE_WEEKS:
|
||||
start = today.clone().startOf('day');
|
||||
end = today
|
||||
.clone()
|
||||
.add(21, 'day')
|
||||
.endOf('day');
|
||||
end = today.clone().add(21, 'day').endOf('day');
|
||||
isFiltered = shouldFilter(taskDueDate.isBetween(start, end));
|
||||
break;
|
||||
default:
|
||||
@ -104,7 +74,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
}
|
||||
for (const member of filters.members) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -116,7 +86,7 @@ export default function shouldMetaFilter(task: Task, filters: TaskMetaFilters) {
|
||||
}
|
||||
for (const label of filters.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;
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,21 @@ type ActivityMessageProps = {
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return dayjs(timestamp).format('MMM D [at] h:mm A');
|
||||
if (hasTime) {
|
||||
return dayjs(timestamp).format('MMM D [at] h:mm A');
|
||||
}
|
||||
return dayjs(timestamp).format('MMM D');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -30,13 +38,19 @@ const ActivityMessage: React.FC<ActivityMessageProps> = ({ type, data }) => {
|
||||
message = `moved this task from ${getVariable(data, 'PrevTaskGroup')} to ${getVariable(data, 'CurTaskGroup')}`;
|
||||
break;
|
||||
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;
|
||||
case ActivityType.TaskDueDateRemoved:
|
||||
message = `removed the due date from this task`;
|
||||
break;
|
||||
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;
|
||||
case ActivityType.TaskMarkedComplete:
|
||||
message = `marked this task complete`;
|
||||
|
@ -332,7 +332,6 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
const saveDescription = () => {
|
||||
onTaskDescriptionChange(task, taskDescriptionRef.current);
|
||||
};
|
||||
console.log(task.watched);
|
||||
return (
|
||||
<Container>
|
||||
<LeftSidebar>
|
||||
@ -351,9 +350,9 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{task.dueDate ? (
|
||||
{task.dueDate.at ? (
|
||||
<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>No due date</SidebarButtonText>
|
||||
@ -632,6 +631,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
{activityStream.map((stream) =>
|
||||
stream.data.type === 'comment' ? (
|
||||
<StreamComment
|
||||
key={stream.id}
|
||||
onExtraActions={onCommentShowActions}
|
||||
onCancelCommentEdit={onCancelCommentEdit}
|
||||
onUpdateComment={(message) => onUpdateComment(stream.id, message)}
|
||||
@ -640,6 +640,7 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
/>
|
||||
) : (
|
||||
<StreamActivity
|
||||
key={stream.id}
|
||||
activity={task.activity && task.activity.find((activity) => activity.id === stream.id)}
|
||||
/>
|
||||
),
|
||||
|
@ -30,19 +30,16 @@ function plugin(options) {
|
||||
}
|
||||
|
||||
function getEmoji(match) {
|
||||
console.log(match);
|
||||
const got = emoji.get(match);
|
||||
if (pad && got !== match) {
|
||||
return `${got} `;
|
||||
}
|
||||
|
||||
console.log(got);
|
||||
return ReactDOMServer.renderToStaticMarkup(<Emoji set="google" emoji={match} size={16} />);
|
||||
}
|
||||
|
||||
function transformer(tree) {
|
||||
visit(tree, 'paragraph', function (node) {
|
||||
console.log(tree);
|
||||
// node.value = node.value.replace(RE_EMOJI, getEmoji);
|
||||
// jnode.type = 'html';
|
||||
// jnode.tagName = 'div';
|
||||
@ -58,7 +55,6 @@ function plugin(options) {
|
||||
if (emoticonEnable) {
|
||||
// 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} />
|
||||
</IconContainer>
|
||||
<IconContainer onClick={onNotificationClick}>
|
||||
<Bell color="#c2c6dc" size={20} />
|
||||
<Bell width={20} height={20} />
|
||||
{hasUnread && <NotificationCount />}
|
||||
</IconContainer>
|
||||
<IconContainer disabled onClick={NOOP}>
|
||||
|
@ -107,6 +107,17 @@ export type CreateTaskCommentPayload = {
|
||||
comment: TaskComment;
|
||||
};
|
||||
|
||||
export type CreateTaskDueDateNotification = {
|
||||
taskID: Scalars['UUID'];
|
||||
period: Scalars['Int'];
|
||||
duration: DueDateNotificationDuration;
|
||||
};
|
||||
|
||||
export type CreateTaskDueDateNotificationsResult = {
|
||||
__typename?: 'CreateTaskDueDateNotificationsResult';
|
||||
notifications: Array<DueDateNotification>;
|
||||
};
|
||||
|
||||
export type CreateTeamMember = {
|
||||
userID: Scalars['UUID'];
|
||||
teamID: Scalars['UUID'];
|
||||
@ -200,6 +211,15 @@ export type DeleteTaskCommentPayload = {
|
||||
commentID: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteTaskDueDateNotification = {
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type DeleteTaskDueDateNotificationsResult = {
|
||||
__typename?: 'DeleteTaskDueDateNotificationsResult';
|
||||
notifications: Array<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type DeleteTaskGroupInput = {
|
||||
taskGroupID: Scalars['UUID'];
|
||||
};
|
||||
@ -265,6 +285,26 @@ export type DeleteUserAccountPayload = {
|
||||
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 = {
|
||||
projectID: Scalars['UUID'];
|
||||
taskGroupID: Scalars['UUID'];
|
||||
@ -393,6 +433,7 @@ export type Mutation = {
|
||||
createTaskChecklist: TaskChecklist;
|
||||
createTaskChecklistItem: TaskChecklistItem;
|
||||
createTaskComment: CreateTaskCommentPayload;
|
||||
createTaskDueDateNotifications: CreateTaskDueDateNotificationsResult;
|
||||
createTaskGroup: TaskGroup;
|
||||
createTeam: Team;
|
||||
createTeamMember: CreateTeamMemberPayload;
|
||||
@ -406,6 +447,7 @@ export type Mutation = {
|
||||
deleteTaskChecklist: DeleteTaskChecklistPayload;
|
||||
deleteTaskChecklistItem: DeleteTaskChecklistItemPayload;
|
||||
deleteTaskComment: DeleteTaskCommentPayload;
|
||||
deleteTaskDueDateNotifications: DeleteTaskDueDateNotificationsResult;
|
||||
deleteTaskGroup: DeleteTaskGroupPayload;
|
||||
deleteTaskGroupTasks: DeleteTaskGroupTasksPayload;
|
||||
deleteTeam: DeleteTeamPayload;
|
||||
@ -435,6 +477,7 @@ export type Mutation = {
|
||||
updateTaskComment: UpdateTaskCommentPayload;
|
||||
updateTaskDescription: Task;
|
||||
updateTaskDueDate: Task;
|
||||
updateTaskDueDateNotifications: UpdateTaskDueDateNotificationsResult;
|
||||
updateTaskGroupLocation: TaskGroup;
|
||||
updateTaskGroupName: TaskGroup;
|
||||
updateTaskLocation: UpdateTaskLocationPayload;
|
||||
@ -486,6 +529,11 @@ export type MutationCreateTaskCommentArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateTaskDueDateNotificationsArgs = {
|
||||
input: Array<CreateTaskDueDateNotification>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateTaskGroupArgs = {
|
||||
input: NewTaskGroup;
|
||||
};
|
||||
@ -551,6 +599,11 @@ export type MutationDeleteTaskCommentArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteTaskDueDateNotificationsArgs = {
|
||||
input: Array<DeleteTaskDueDateNotification>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteTaskGroupArgs = {
|
||||
input: DeleteTaskGroupInput;
|
||||
};
|
||||
@ -696,6 +749,11 @@ export type MutationUpdateTaskDueDateArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateTaskDueDateNotificationsArgs = {
|
||||
input: Array<UpdateTaskDueDateNotification>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateTaskGroupLocationArgs = {
|
||||
input: NewTaskGroupLocation;
|
||||
};
|
||||
@ -1078,7 +1136,7 @@ export type Task = {
|
||||
position: Scalars['Float'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
watched: Scalars['Boolean'];
|
||||
dueDate?: Maybe<Scalars['Time']>;
|
||||
dueDate: DueDate;
|
||||
hasTime: Scalars['Boolean'];
|
||||
complete: Scalars['Boolean'];
|
||||
completedAt?: Maybe<Scalars['Time']>;
|
||||
@ -1302,6 +1360,17 @@ export type UpdateTaskDueDate = {
|
||||
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 = {
|
||||
taskGroupID: Scalars['UUID'];
|
||||
name: Scalars['String'];
|
||||
@ -1596,8 +1665,15 @@ export type FindTaskQuery = (
|
||||
{ __typename?: 'Query' }
|
||||
& { findTask: (
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'watched' | 'description' | 'dueDate' | 'position' | 'complete' | 'hasTime'>
|
||||
& { taskGroup: (
|
||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'watched' | 'description' | 'position' | 'complete' | 'hasTime'>
|
||||
& { dueDate: (
|
||||
{ __typename?: 'DueDate' }
|
||||
& Pick<DueDate, 'at'>
|
||||
& { notifications: Array<(
|
||||
{ __typename?: 'DueDateNotification' }
|
||||
& Pick<DueDateNotification, 'id' | 'period' | 'duration'>
|
||||
)> }
|
||||
), taskGroup: (
|
||||
{ __typename?: 'TaskGroup' }
|
||||
& Pick<TaskGroup, 'id' | 'name'>
|
||||
), comments: Array<(
|
||||
@ -1672,8 +1748,11 @@ export type FindTaskQuery = (
|
||||
|
||||
export type TaskFieldsFragment = (
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'description' | 'dueDate' | 'hasTime' | 'complete' | 'watched' | 'completedAt' | 'position'>
|
||||
& { badges: (
|
||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'description' | 'hasTime' | 'complete' | 'watched' | 'completedAt' | 'position'>
|
||||
& { dueDate: (
|
||||
{ __typename?: 'DueDate' }
|
||||
& Pick<DueDate, 'at'>
|
||||
), badges: (
|
||||
{ __typename?: 'TaskBadges' }
|
||||
& { checklist?: Maybe<(
|
||||
{ __typename?: 'ChecklistBadge' }
|
||||
@ -1789,10 +1868,13 @@ export type MyTasksQuery = (
|
||||
{ __typename?: 'MyTasksPayload' }
|
||||
& { tasks: Array<(
|
||||
{ __typename?: 'Task' }
|
||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'dueDate' | 'hasTime' | 'complete' | 'completedAt'>
|
||||
& Pick<Task, 'id' | 'shortId' | 'name' | 'hasTime' | 'complete' | 'completedAt'>
|
||||
& { taskGroup: (
|
||||
{ __typename?: 'TaskGroup' }
|
||||
& Pick<TaskGroup, 'id' | 'name'>
|
||||
), dueDate: (
|
||||
{ __typename?: 'DueDate' }
|
||||
& Pick<DueDate, 'at'>
|
||||
) }
|
||||
)>, projects: Array<(
|
||||
{ __typename?: 'ProjectTaskMapping' }
|
||||
@ -2624,6 +2706,9 @@ export type UpdateTaskDueDateMutationVariables = Exact<{
|
||||
taskID: Scalars['UUID'];
|
||||
dueDate?: Maybe<Scalars['Time']>;
|
||||
hasTime: Scalars['Boolean'];
|
||||
createNotifications: Array<CreateTaskDueDateNotification> | CreateTaskDueDateNotification;
|
||||
updateNotifications: Array<UpdateTaskDueDateNotification> | UpdateTaskDueDateNotification;
|
||||
deleteNotifications: Array<DeleteTaskDueDateNotification> | DeleteTaskDueDateNotification;
|
||||
}>;
|
||||
|
||||
|
||||
@ -2631,7 +2716,26 @@ export type UpdateTaskDueDateMutation = (
|
||||
{ __typename?: 'Mutation' }
|
||||
& { updateTaskDueDate: (
|
||||
{ __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
|
||||
name
|
||||
description
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
}
|
||||
hasTime
|
||||
complete
|
||||
watched
|
||||
@ -3346,7 +3452,14 @@ export const FindTaskDocument = gql`
|
||||
name
|
||||
watched
|
||||
description
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
notifications {
|
||||
id
|
||||
period
|
||||
duration
|
||||
}
|
||||
}
|
||||
position
|
||||
complete
|
||||
hasTime
|
||||
@ -3640,7 +3753,9 @@ export const MyTasksDocument = gql`
|
||||
name
|
||||
}
|
||||
name
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
}
|
||||
hasTime
|
||||
complete
|
||||
completedAt
|
||||
@ -5423,14 +5538,33 @@ export type UpdateTaskDescriptionMutationHookResult = ReturnType<typeof useUpdat
|
||||
export type UpdateTaskDescriptionMutationResult = Apollo.MutationResult<UpdateTaskDescriptionMutation>;
|
||||
export type UpdateTaskDescriptionMutationOptions = Apollo.BaseMutationOptions<UpdateTaskDescriptionMutation, UpdateTaskDescriptionMutationVariables>;
|
||||
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(
|
||||
input: {taskID: $taskID, dueDate: $dueDate, hasTime: $hasTime}
|
||||
) {
|
||||
id
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
}
|
||||
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>;
|
||||
@ -5451,6 +5585,9 @@ export type UpdateTaskDueDateMutationFn = Apollo.MutationFunction<UpdateTaskDueD
|
||||
* taskID: // value for 'taskID'
|
||||
* dueDate: // value for 'dueDate'
|
||||
* 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
|
||||
watched
|
||||
description
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
notifications {
|
||||
id
|
||||
period
|
||||
duration
|
||||
}
|
||||
}
|
||||
position
|
||||
complete
|
||||
hasTime
|
||||
|
@ -6,7 +6,9 @@ const TASK_FRAGMENT = gql`
|
||||
shortId
|
||||
name
|
||||
description
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
}
|
||||
hasTime
|
||||
complete
|
||||
watched
|
||||
|
@ -12,7 +12,9 @@ query myTasks($status: MyTasksStatus!, $sort: MyTasksSort!) {
|
||||
name
|
||||
}
|
||||
name
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
}
|
||||
hasTime
|
||||
complete
|
||||
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 (
|
||||
input: {
|
||||
taskID: $taskID
|
||||
@ -7,7 +11,26 @@ mutation updateTaskDueDate($taskID: UUID!, $dueDate: Time, $hasTime: Boolean!) {
|
||||
}
|
||||
) {
|
||||
id
|
||||
dueDate
|
||||
dueDate {
|
||||
at
|
||||
}
|
||||
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 {
|
||||
return value === 'undefined' ? undefined : JSON.parse(value ?? '');
|
||||
} catch (error) {
|
||||
console.log('parsing error on', { value });
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,11 @@
|
||||
import React from 'react';
|
||||
import Icon, { IconProps } from './Icon';
|
||||
|
||||
type Props = {
|
||||
size: number | string;
|
||||
color: string;
|
||||
};
|
||||
|
||||
const Bell = ({ size, color }: Props) => {
|
||||
const Bell: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||
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" />
|
||||
</svg>
|
||||
</Icon>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -46,7 +46,7 @@ export function sortTasks(a: Task, b: Task, taskSorting: TaskSorting) {
|
||||
if (b.dueDate && !a.dueDate) {
|
||||
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 (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;
|
||||
position: number;
|
||||
hasTime?: boolean;
|
||||
dueDate?: string;
|
||||
dueDate: { at?: string; notifications?: Array<{ id: string; period: number; duration: string }> };
|
||||
complete?: boolean;
|
||||
completedAt?: string | null;
|
||||
labels: TaskLabel[];
|
||||
|
Reference in New Issue
Block a user