2020-06-23 23:55:17 +02:00
|
|
|
// LOC830
|
2020-07-17 02:40:23 +02:00
|
|
|
import React, { useState, useRef, useEffect, useContext } from 'react';
|
2020-06-23 23:55:17 +02:00
|
|
|
import updateApolloCache from 'shared/utils/cache';
|
2020-07-17 02:40:23 +02:00
|
|
|
import GlobalTopNavbar, { ProjectPopup } from 'App/TopNavbar';
|
2020-07-12 09:06:11 +02:00
|
|
|
import styled from 'styled-components/macro';
|
2020-10-21 01:52:09 +02:00
|
|
|
import AsyncSelect from 'react-select/async';
|
2020-07-17 02:40:23 +02:00
|
|
|
import { usePopup, Popup } from 'shared/components/PopupMenu';
|
|
|
|
import {
|
|
|
|
useParams,
|
|
|
|
Route,
|
|
|
|
useRouteMatch,
|
|
|
|
useHistory,
|
|
|
|
RouteComponentProps,
|
|
|
|
useLocation,
|
|
|
|
Redirect,
|
|
|
|
} from 'react-router-dom';
|
2020-04-10 05:27:57 +02:00
|
|
|
import {
|
2020-07-05 01:02:57 +02:00
|
|
|
useUpdateProjectMemberRoleMutation,
|
2020-10-21 01:52:09 +02:00
|
|
|
useInviteProjectMembersMutation,
|
2020-07-05 01:02:57 +02:00
|
|
|
useDeleteProjectMemberMutation,
|
2020-05-31 06:11:19 +02:00
|
|
|
useToggleTaskLabelMutation,
|
|
|
|
useUpdateProjectNameMutation,
|
2020-04-10 05:27:57 +02:00
|
|
|
useFindProjectQuery,
|
2020-10-21 01:52:09 +02:00
|
|
|
useDeleteInvitedProjectMemberMutation,
|
2020-04-10 05:27:57 +02:00
|
|
|
useUpdateTaskNameMutation,
|
|
|
|
useCreateTaskMutation,
|
|
|
|
useDeleteTaskMutation,
|
|
|
|
useUpdateTaskLocationMutation,
|
2020-04-11 04:53:03 +02:00
|
|
|
useUpdateTaskGroupLocationMutation,
|
2020-04-11 04:22:58 +02:00
|
|
|
useCreateTaskGroupMutation,
|
2020-04-20 05:02:55 +02:00
|
|
|
useUpdateTaskDescriptionMutation,
|
2020-05-27 02:53:31 +02:00
|
|
|
FindProjectDocument,
|
2020-06-23 22:20:53 +02:00
|
|
|
FindProjectQuery,
|
2020-04-10 05:27:57 +02:00
|
|
|
} from 'shared/generated/graphql';
|
2020-04-10 04:40:22 +02:00
|
|
|
|
2020-05-27 02:53:31 +02:00
|
|
|
import produce from 'immer';
|
2020-08-01 03:01:14 +02:00
|
|
|
import UserContext, { useCurrentUser } from 'App/context';
|
2020-07-05 01:02:57 +02:00
|
|
|
import Input from 'shared/components/Input';
|
|
|
|
import Member from 'shared/components/Member';
|
2020-08-23 19:27:56 +02:00
|
|
|
import EmptyBoard from 'shared/components/EmptyBoard';
|
|
|
|
import NOOP from 'shared/utils/noop';
|
2020-10-21 01:52:09 +02:00
|
|
|
import { Lock, Cross } from 'shared/icons';
|
|
|
|
import Button from 'shared/components/Button';
|
|
|
|
import { useApolloClient } from '@apollo/react-hooks';
|
|
|
|
import TaskAssignee from 'shared/components/TaskAssignee';
|
|
|
|
import gql from 'graphql-tag';
|
|
|
|
import { colourStyles } from 'shared/components/Select';
|
2020-08-01 03:01:14 +02:00
|
|
|
import Board, { BoardLoading } from './Board';
|
2020-07-17 02:40:23 +02:00
|
|
|
import Details from './Details';
|
2020-08-23 19:27:56 +02:00
|
|
|
import LabelManagerEditor from './LabelManagerEditor';
|
2020-07-17 02:40:23 +02:00
|
|
|
|
|
|
|
const CARD_LABEL_VARIANT_STORAGE_KEY = 'card_label_variant';
|
|
|
|
|
2020-10-21 01:52:09 +02:00
|
|
|
const RFC2822_EMAIL = /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
|
|
|
|
|
2020-07-17 02:40:23 +02:00
|
|
|
const useStateWithLocalStorage = (localStorageKey: string): [string, React.Dispatch<React.SetStateAction<string>>] => {
|
|
|
|
const [value, setValue] = React.useState<string>(localStorage.getItem(localStorageKey) || '');
|
|
|
|
|
|
|
|
React.useEffect(() => {
|
|
|
|
localStorage.setItem(localStorageKey, value);
|
|
|
|
}, [value]);
|
|
|
|
|
|
|
|
return [value, setValue];
|
|
|
|
};
|
2020-05-28 03:12:50 +02:00
|
|
|
|
2020-07-05 01:02:57 +02:00
|
|
|
const SearchInput = styled(Input)`
|
|
|
|
margin: 0;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const UserMember = styled(Member)`
|
|
|
|
padding: 4px 0;
|
|
|
|
cursor: pointer;
|
|
|
|
&:hover {
|
|
|
|
background: rgba(${props => props.theme.colors.bg.primary}, 0.4);
|
|
|
|
}
|
|
|
|
border-radius: 6px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const MemberList = styled.div`
|
|
|
|
margin: 8px 0;
|
|
|
|
`;
|
|
|
|
|
2020-10-21 01:52:09 +02:00
|
|
|
type InviteUserData = {
|
|
|
|
email?: string;
|
|
|
|
suerID?: string;
|
|
|
|
};
|
2020-07-05 01:02:57 +02:00
|
|
|
type UserManagementPopupProps = {
|
2020-10-21 01:52:09 +02:00
|
|
|
projectID: string;
|
2020-07-05 01:02:57 +02:00
|
|
|
users: Array<User>;
|
|
|
|
projectMembers: Array<TaskUser>;
|
2020-10-21 01:52:09 +02:00
|
|
|
onInviteProjectMembers: (data: Array<InviteUserData>) => void;
|
|
|
|
};
|
|
|
|
|
|
|
|
const VisibiltyPrivateIcon = styled(Lock)`
|
|
|
|
padding-right: 4px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const VisibiltyButtonText = styled.span`
|
|
|
|
color: rgba(${props => props.theme.colors.text.primary});
|
|
|
|
`;
|
|
|
|
|
|
|
|
const ShareActions = styled.div`
|
|
|
|
border-top: 1px solid #414561;
|
|
|
|
margin-top: 8px;
|
|
|
|
padding-top: 8px;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: space-between;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const VisibiltyButton = styled.button`
|
|
|
|
cursor: pointer;
|
|
|
|
margin: 2px 4px;
|
|
|
|
padding: 2px 4px;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
border-bottom: 1px solid transparent;
|
|
|
|
&:hover ${VisibiltyButtonText} {
|
|
|
|
color: rgba(${props => props.theme.colors.text.secondary});
|
|
|
|
}
|
|
|
|
&:hover ${VisibiltyPrivateIcon} {
|
|
|
|
fill: rgba(${props => props.theme.colors.text.secondary});
|
|
|
|
stroke: rgba(${props => props.theme.colors.text.secondary});
|
|
|
|
}
|
|
|
|
&:hover {
|
|
|
|
border-bottom: 1px solid rgba(${props => props.theme.colors.primary});
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
type MemberFilterOptions = {
|
|
|
|
projectID?: null | string;
|
|
|
|
teamID?: null | string;
|
|
|
|
organization?: boolean;
|
2020-05-28 03:12:50 +02:00
|
|
|
};
|
|
|
|
|
2020-10-21 01:52:09 +02:00
|
|
|
const fetchMembers = async (client: any, projectID: string, options: MemberFilterOptions, input: string, cb: any) => {
|
|
|
|
console.log(input.trim().length < 3);
|
|
|
|
if (input && input.trim().length < 3) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
const res = await client.query({
|
|
|
|
query: gql`
|
|
|
|
query {
|
|
|
|
searchMembers(input: {searchFilter:"${input}", projectID:"${projectID}"}) {
|
|
|
|
id
|
|
|
|
similarity
|
|
|
|
status
|
|
|
|
user {
|
|
|
|
id
|
|
|
|
fullName
|
|
|
|
email
|
|
|
|
profileIcon {
|
|
|
|
url
|
|
|
|
initials
|
|
|
|
bgColor
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
`,
|
|
|
|
});
|
|
|
|
|
|
|
|
let results: any = [];
|
|
|
|
const emails: Array<string> = [];
|
|
|
|
console.log(res.data && res.data.searchMembers);
|
|
|
|
if (res.data && res.data.searchMembers) {
|
|
|
|
results = [
|
|
|
|
...res.data.searchMembers.map((m: any) => {
|
|
|
|
if (m.status === 'INVITED') {
|
|
|
|
console.log(`${m.id} is added`);
|
|
|
|
return {
|
|
|
|
label: m.id,
|
|
|
|
value: {
|
|
|
|
id: m.id,
|
|
|
|
type: 2,
|
|
|
|
profileIcon: {
|
|
|
|
bgColor: '#ccc',
|
|
|
|
initials: m.id.charAt(0),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
console.log(`${m.user.email} is added`);
|
|
|
|
emails.push(m.user.email);
|
|
|
|
return {
|
|
|
|
label: m.user.fullName,
|
|
|
|
value: { id: m.user.id, type: 0, profileIcon: m.user.profileIcon },
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
];
|
|
|
|
console.log(results);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (RFC2822_EMAIL.test(input) && !emails.find(e => e === input)) {
|
|
|
|
results = [
|
|
|
|
...results,
|
|
|
|
{
|
|
|
|
label: input,
|
|
|
|
value: {
|
|
|
|
id: input,
|
|
|
|
type: 1,
|
|
|
|
profileIcon: {
|
|
|
|
bgColor: '#ccc',
|
|
|
|
initials: input.charAt(0),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
};
|
|
|
|
|
|
|
|
type UserOptionProps = {
|
|
|
|
innerProps: any;
|
|
|
|
isDisabled: boolean;
|
|
|
|
isFocused: boolean;
|
|
|
|
label: string;
|
|
|
|
data: any;
|
|
|
|
getValue: any;
|
|
|
|
};
|
|
|
|
|
|
|
|
const OptionWrapper = styled.div<{ isFocused: boolean }>`
|
|
|
|
cursor: pointer;
|
|
|
|
padding: 4px 8px;
|
|
|
|
${props => props.isFocused && `background: rgba(${props.theme.colors.primary});`}
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
`;
|
|
|
|
const OptionContent = styled.div`
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
margin-left: 12px;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const OptionLabel = styled.span<{ fontSize: number; quiet: boolean }>`
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
font-size: ${p => p.fontSize}px;
|
|
|
|
color: rgba(${p => (p.quiet ? p.theme.colors.text.primary : p.theme.colors.text.primary)});
|
|
|
|
`;
|
|
|
|
|
|
|
|
const UserOption: React.FC<UserOptionProps> = ({ isDisabled, isFocused, innerProps, label, data }) => {
|
|
|
|
console.log(data);
|
|
|
|
return !isDisabled ? (
|
|
|
|
<OptionWrapper {...innerProps} isFocused={isFocused}>
|
|
|
|
<TaskAssignee
|
|
|
|
size={32}
|
|
|
|
member={{
|
|
|
|
id: '',
|
|
|
|
fullName: data.value.label,
|
|
|
|
profileIcon: data.value.profileIcon,
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<OptionContent>
|
|
|
|
<OptionLabel fontSize={16} quiet={false}>
|
|
|
|
{label}
|
|
|
|
</OptionLabel>
|
|
|
|
{data.value.type === 2 && (
|
|
|
|
<OptionLabel fontSize={14} quiet>
|
|
|
|
Joined
|
|
|
|
</OptionLabel>
|
|
|
|
)}
|
|
|
|
</OptionContent>
|
|
|
|
</OptionWrapper>
|
|
|
|
) : null;
|
|
|
|
};
|
|
|
|
|
|
|
|
const OptionValueWrapper = styled.div`
|
|
|
|
background: rgba(${props => props.theme.colors.bg.primary});
|
|
|
|
border-radius: 4px;
|
|
|
|
margin: 2px;
|
|
|
|
padding: 3px 6px 3px 4px;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const OptionValueLabel = styled.span`
|
|
|
|
font-size: 12px;
|
|
|
|
color: rgba(${props => props.theme.colors.text.secondary});
|
|
|
|
`;
|
|
|
|
|
|
|
|
const OptionValueRemove = styled.button`
|
|
|
|
cursor: pointer;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
background: none;
|
|
|
|
border: none;
|
|
|
|
outline: none;
|
|
|
|
padding: 0;
|
|
|
|
margin: 0;
|
|
|
|
margin-left: 4px;
|
|
|
|
`;
|
|
|
|
const OptionValue = ({ data, removeProps }: any) => {
|
|
|
|
return (
|
|
|
|
<OptionValueWrapper>
|
|
|
|
<OptionValueLabel>{data.label}</OptionValueLabel>
|
|
|
|
<OptionValueRemove {...removeProps}>
|
|
|
|
<Cross width={14} height={14} />
|
|
|
|
</OptionValueRemove>
|
|
|
|
</OptionValueWrapper>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const InviteButton = styled(Button)`
|
|
|
|
margin-top: 12px;
|
|
|
|
height: 32px;
|
|
|
|
padding: 4px 12px;
|
|
|
|
width: 100%;
|
|
|
|
justify-content: center;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const InviteContainer = styled.div`
|
|
|
|
min-height: 300px;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
`;
|
|
|
|
|
|
|
|
const UserManagementPopup: React.FC<UserManagementPopupProps> = ({
|
|
|
|
projectID,
|
|
|
|
users,
|
|
|
|
projectMembers,
|
|
|
|
onInviteProjectMembers,
|
|
|
|
}) => {
|
|
|
|
const client = useApolloClient();
|
|
|
|
const [invitedUsers, setInvitedUsers] = useState<Array<any> | null>(null);
|
2020-07-05 01:02:57 +02:00
|
|
|
return (
|
|
|
|
<Popup tab={0} title="Invite a user">
|
2020-10-21 01:52:09 +02:00
|
|
|
<InviteContainer>
|
|
|
|
<AsyncSelect
|
|
|
|
getOptionValue={option => option.value.id}
|
|
|
|
placeholder="Email address or username"
|
|
|
|
noOptionsMessage={() => null}
|
|
|
|
onChange={(e: any) => {
|
|
|
|
setInvitedUsers(e);
|
|
|
|
}}
|
|
|
|
isMulti
|
|
|
|
autoFocus
|
|
|
|
cacheOptions
|
|
|
|
styles={colourStyles}
|
|
|
|
defaultOption
|
|
|
|
components={{
|
|
|
|
MultiValue: OptionValue,
|
|
|
|
Option: UserOption,
|
|
|
|
IndicatorSeparator: null,
|
|
|
|
DropdownIndicator: null,
|
|
|
|
}}
|
|
|
|
loadOptions={(i, cb) => fetchMembers(client, projectID, {}, i, cb)}
|
|
|
|
/>
|
|
|
|
</InviteContainer>
|
|
|
|
<InviteButton
|
|
|
|
onClick={() => {
|
|
|
|
if (invitedUsers) {
|
|
|
|
onInviteProjectMembers(
|
|
|
|
invitedUsers.map(user => {
|
|
|
|
if (user.value.type === 0) {
|
|
|
|
return {
|
|
|
|
userID: user.value.id,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
email: user.value.id,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
disabled={invitedUsers === null}
|
|
|
|
hoverVariant="none"
|
|
|
|
fontSize="16px"
|
|
|
|
>
|
|
|
|
Send Invite
|
|
|
|
</InviteButton>
|
2020-07-05 01:02:57 +02:00
|
|
|
</Popup>
|
|
|
|
);
|
2020-05-28 03:12:50 +02:00
|
|
|
};
|
2020-04-10 04:40:22 +02:00
|
|
|
|
2020-04-16 22:05:12 +02:00
|
|
|
type TaskRouteProps = {
|
|
|
|
taskID: string;
|
|
|
|
};
|
2020-04-10 04:40:22 +02:00
|
|
|
|
|
|
|
interface QuickCardEditorState {
|
|
|
|
isOpen: boolean;
|
2020-06-24 03:08:48 +02:00
|
|
|
target: React.RefObject<HTMLElement> | null;
|
2020-05-31 06:51:22 +02:00
|
|
|
taskID: string | null;
|
|
|
|
taskGroupID: string | null;
|
2020-04-10 04:40:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface ProjectParams {
|
2020-05-28 03:12:50 +02:00
|
|
|
projectID: string;
|
2020-04-10 04:40:22 +02:00
|
|
|
}
|
|
|
|
|
2020-05-31 06:51:22 +02:00
|
|
|
const initialQuickCardEditorState: QuickCardEditorState = {
|
|
|
|
taskID: null,
|
|
|
|
taskGroupID: null,
|
|
|
|
isOpen: false,
|
2020-06-24 03:08:48 +02:00
|
|
|
target: null,
|
2020-05-31 06:51:22 +02:00
|
|
|
};
|
2020-04-10 04:40:22 +02:00
|
|
|
|
|
|
|
const Project = () => {
|
2020-07-17 02:40:23 +02:00
|
|
|
const { projectID } = useParams<ProjectParams>();
|
2020-05-31 06:11:19 +02:00
|
|
|
const history = useHistory();
|
2020-04-16 22:05:12 +02:00
|
|
|
const match = useRouteMatch();
|
|
|
|
|
2020-04-20 05:02:55 +02:00
|
|
|
const [updateTaskDescription] = useUpdateTaskDescriptionMutation();
|
2020-08-23 19:27:56 +02:00
|
|
|
const taskLabelsRef = useRef<Array<TaskLabel>>([]);
|
2020-05-31 06:11:19 +02:00
|
|
|
const [toggleTaskLabel] = useToggleTaskLabelMutation({
|
|
|
|
onCompleted: newTaskLabel => {
|
|
|
|
taskLabelsRef.current = newTaskLabel.toggleTaskLabel.task.labels;
|
2020-04-10 04:40:22 +02:00
|
|
|
},
|
|
|
|
});
|
2020-07-17 02:40:23 +02:00
|
|
|
|
|
|
|
const [value, setValue] = useStateWithLocalStorage(CARD_LABEL_VARIANT_STORAGE_KEY);
|
2020-07-12 09:06:11 +02:00
|
|
|
const [updateProjectMemberRole] = useUpdateProjectMemberRoleMutation();
|
2020-04-10 04:40:22 +02:00
|
|
|
|
2020-09-20 00:26:02 +02:00
|
|
|
const [deleteTask] = useDeleteTaskMutation({
|
|
|
|
update: (client, resp) =>
|
|
|
|
updateApolloCache<FindProjectQuery>(
|
|
|
|
client,
|
|
|
|
FindProjectDocument,
|
|
|
|
cache =>
|
|
|
|
produce(cache, draftCache => {
|
|
|
|
const taskGroupIdx = draftCache.findProject.taskGroups.findIndex(
|
|
|
|
tg => tg.tasks.findIndex(t => t.id === resp.data.deleteTask.taskID) !== -1,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (taskGroupIdx !== -1) {
|
|
|
|
draftCache.findProject.taskGroups[taskGroupIdx].tasks = cache.findProject.taskGroups[
|
|
|
|
taskGroupIdx
|
|
|
|
].tasks.filter(t => t.id !== resp.data.deleteTask.taskID);
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
{ projectID },
|
|
|
|
),
|
|
|
|
});
|
2020-04-20 05:02:55 +02:00
|
|
|
|
2020-07-12 09:06:11 +02:00
|
|
|
const [updateTaskName] = useUpdateTaskNameMutation();
|
2020-06-19 01:12:15 +02:00
|
|
|
|
2020-09-20 03:20:36 +02:00
|
|
|
const { loading, data, error } = useFindProjectQuery({
|
2020-08-01 03:01:14 +02:00
|
|
|
variables: { projectID },
|
2020-07-12 09:06:11 +02:00
|
|
|
});
|
2020-06-19 01:12:15 +02:00
|
|
|
|
2020-06-01 04:20:03 +02:00
|
|
|
const [updateProjectName] = useUpdateProjectNameMutation({
|
|
|
|
update: (client, newName) => {
|
2020-06-23 23:55:17 +02:00
|
|
|
updateApolloCache<FindProjectQuery>(
|
|
|
|
client,
|
|
|
|
FindProjectDocument,
|
|
|
|
cache =>
|
|
|
|
produce(cache, draftCache => {
|
|
|
|
draftCache.findProject.name = newName.data.updateProjectName.name;
|
|
|
|
}),
|
2020-08-01 03:01:14 +02:00
|
|
|
{ projectID },
|
2020-06-23 23:55:17 +02:00
|
|
|
);
|
2020-06-01 04:20:03 +02:00
|
|
|
},
|
|
|
|
});
|
2020-05-31 06:11:19 +02:00
|
|
|
|
2020-10-21 01:52:09 +02:00
|
|
|
const [inviteProjectMembers] = useInviteProjectMembersMutation({
|
2020-07-05 01:02:57 +02:00
|
|
|
update: (client, response) => {
|
|
|
|
updateApolloCache<FindProjectQuery>(
|
|
|
|
client,
|
|
|
|
FindProjectDocument,
|
|
|
|
cache =>
|
|
|
|
produce(cache, draftCache => {
|
2020-10-21 01:52:09 +02:00
|
|
|
draftCache.findProject.members = [
|
|
|
|
...cache.findProject.members,
|
|
|
|
...response.data.inviteProjectMembers.members,
|
|
|
|
];
|
|
|
|
draftCache.findProject.invitedMembers = [
|
|
|
|
...cache.findProject.invitedMembers,
|
|
|
|
...response.data.inviteProjectMembers.invitedMembers,
|
|
|
|
];
|
|
|
|
}),
|
|
|
|
{ projectID },
|
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const [deleteInvitedProjectMember] = useDeleteInvitedProjectMemberMutation({
|
|
|
|
update: (client, response) => {
|
|
|
|
updateApolloCache<FindProjectQuery>(
|
|
|
|
client,
|
|
|
|
FindProjectDocument,
|
|
|
|
cache =>
|
|
|
|
produce(cache, draftCache => {
|
|
|
|
draftCache.findProject.invitedMembers = cache.findProject.invitedMembers.filter(
|
|
|
|
m => m.email !== response.data.deleteInvitedProjectMember.invitedMember.email,
|
|
|
|
);
|
2020-07-05 01:02:57 +02:00
|
|
|
}),
|
2020-08-01 03:01:14 +02:00
|
|
|
{ projectID },
|
2020-07-05 01:02:57 +02:00
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const [deleteProjectMember] = useDeleteProjectMemberMutation({
|
|
|
|
update: (client, response) => {
|
|
|
|
updateApolloCache<FindProjectQuery>(
|
|
|
|
client,
|
|
|
|
FindProjectDocument,
|
|
|
|
cache =>
|
|
|
|
produce(cache, draftCache => {
|
|
|
|
draftCache.findProject.members = cache.findProject.members.filter(
|
|
|
|
m => m.id !== response.data.deleteProjectMember.member.id,
|
|
|
|
);
|
|
|
|
}),
|
2020-08-01 03:01:14 +02:00
|
|
|
{ projectID },
|
2020-07-05 01:02:57 +02:00
|
|
|
);
|
|
|
|
},
|
|
|
|
});
|
2020-06-19 01:12:15 +02:00
|
|
|
|
2020-08-01 03:01:14 +02:00
|
|
|
const { user } = useCurrentUser();
|
2020-07-05 01:02:57 +02:00
|
|
|
const location = useLocation();
|
2020-05-31 06:11:19 +02:00
|
|
|
|
2020-07-17 02:40:23 +02:00
|
|
|
const { showPopup, hidePopup } = usePopup();
|
2020-05-27 02:53:31 +02:00
|
|
|
const $labelsRef = useRef<HTMLDivElement>(null);
|
2020-05-31 06:11:19 +02:00
|
|
|
const labelsRef = useRef<Array<ProjectLabel>>([]);
|
2020-06-13 00:21:58 +02:00
|
|
|
useEffect(() => {
|
|
|
|
if (data) {
|
2020-08-07 03:50:35 +02:00
|
|
|
document.title = `${data.findProject.name} | Taskcafé`;
|
2020-06-13 00:21:58 +02:00
|
|
|
}
|
|
|
|
}, [data]);
|
2020-04-10 04:40:22 +02:00
|
|
|
if (loading) {
|
2020-05-27 02:53:31 +02:00
|
|
|
return (
|
|
|
|
<>
|
2020-08-23 19:27:56 +02:00
|
|
|
<GlobalTopNavbar onSaveProjectName={NOOP} name="" projectID={null} />
|
2020-08-01 03:01:14 +02:00
|
|
|
<BoardLoading />
|
2020-05-27 02:53:31 +02:00
|
|
|
</>
|
|
|
|
);
|
2020-04-10 04:40:22 +02:00
|
|
|
}
|
2020-09-20 03:20:36 +02:00
|
|
|
if (error) {
|
|
|
|
history.push('/projects');
|
|
|
|
}
|
2020-04-10 04:40:22 +02:00
|
|
|
if (data) {
|
2020-05-31 06:11:19 +02:00
|
|
|
labelsRef.current = data.findProject.labels;
|
|
|
|
|
2020-04-10 04:40:22 +02:00
|
|
|
return (
|
|
|
|
<>
|
2020-05-31 06:11:19 +02:00
|
|
|
<GlobalTopNavbar
|
2020-07-05 01:02:57 +02:00
|
|
|
onChangeRole={(userID, roleCode) => {
|
2020-07-17 02:40:23 +02:00
|
|
|
updateProjectMemberRole({ variables: { userID, roleCode, projectID } });
|
2020-07-05 01:02:57 +02:00
|
|
|
}}
|
|
|
|
onChangeProjectOwner={uid => {
|
|
|
|
hidePopup();
|
|
|
|
}}
|
|
|
|
onRemoveFromBoard={userID => {
|
2020-07-17 02:40:23 +02:00
|
|
|
deleteProjectMember({ variables: { userID, projectID } });
|
2020-07-05 01:02:57 +02:00
|
|
|
hidePopup();
|
|
|
|
}}
|
2020-10-21 01:52:09 +02:00
|
|
|
onRemoveInvitedFromBoard={email => {
|
|
|
|
deleteInvitedProjectMember({ variables: { projectID, email } });
|
|
|
|
hidePopup();
|
|
|
|
}}
|
2020-05-31 06:11:19 +02:00
|
|
|
onSaveProjectName={projectName => {
|
2020-07-17 02:40:23 +02:00
|
|
|
updateProjectName({ variables: { projectID, name: projectName } });
|
2020-05-31 06:11:19 +02:00
|
|
|
}}
|
2020-07-05 01:02:57 +02:00
|
|
|
onInviteUser={$target => {
|
|
|
|
showPopup(
|
|
|
|
$target,
|
|
|
|
<UserManagementPopup
|
2020-10-21 01:52:09 +02:00
|
|
|
projectID={projectID}
|
|
|
|
onInviteProjectMembers={members => {
|
|
|
|
inviteProjectMembers({ variables: { projectID, members } });
|
|
|
|
hidePopup();
|
2020-07-05 01:02:57 +02:00
|
|
|
}}
|
|
|
|
users={data.users}
|
|
|
|
projectMembers={data.findProject.members}
|
|
|
|
/>,
|
|
|
|
);
|
|
|
|
}}
|
2020-06-23 22:20:53 +02:00
|
|
|
popupContent={<ProjectPopup history={history} name={data.findProject.name} projectID={projectID} />}
|
2020-07-17 02:40:23 +02:00
|
|
|
menuType={[{ name: 'Board', link: location.pathname }]}
|
2020-07-05 01:02:57 +02:00
|
|
|
currentTab={0}
|
2020-05-31 06:11:19 +02:00
|
|
|
projectMembers={data.findProject.members}
|
2020-10-21 01:52:09 +02:00
|
|
|
projectInvitedMembers={data.findProject.invitedMembers}
|
2020-06-21 00:49:11 +02:00
|
|
|
projectID={projectID}
|
2020-09-20 03:20:36 +02:00
|
|
|
teamID={data.findProject.team ? data.findProject.team.id : null}
|
2020-05-31 06:11:19 +02:00
|
|
|
name={data.findProject.name}
|
|
|
|
/>
|
2020-07-17 02:40:23 +02:00
|
|
|
<Route path={`${match.path}`} exact render={() => <Redirect to={`${match.url}/board`} />} />
|
2020-07-12 09:06:11 +02:00
|
|
|
<Route
|
|
|
|
path={`${match.path}/board`}
|
|
|
|
render={() => (
|
2020-07-17 02:40:23 +02:00
|
|
|
<Board
|
2020-07-17 04:57:20 +02:00
|
|
|
cardLabelVariant={value === 'small' ? 'small' : 'large'}
|
2020-07-17 02:40:23 +02:00
|
|
|
onCardLabelClick={() => {
|
|
|
|
const variant = value === 'small' ? 'large' : 'small';
|
|
|
|
setValue(() => variant);
|
|
|
|
}}
|
|
|
|
projectID={projectID}
|
|
|
|
/>
|
2020-07-12 09:06:11 +02:00
|
|
|
)}
|
2020-04-20 05:02:55 +02:00
|
|
|
/>
|
2020-04-16 22:05:12 +02:00
|
|
|
<Route
|
2020-07-12 09:06:11 +02:00
|
|
|
path={`${match.path}/board/c/:taskID`}
|
2020-04-16 22:05:12 +02:00
|
|
|
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
|
2020-04-20 05:02:55 +02:00
|
|
|
<Details
|
2020-08-23 19:27:56 +02:00
|
|
|
refreshCache={NOOP}
|
2020-05-31 06:11:19 +02:00
|
|
|
availableMembers={data.findProject.members}
|
2020-07-12 09:06:11 +02:00
|
|
|
projectURL={`${match.url}/board`}
|
2020-04-20 05:02:55 +02:00
|
|
|
taskID={routeProps.match.params.taskID}
|
|
|
|
onTaskNameChange={(updatedTask, newName) => {
|
2020-07-17 02:40:23 +02:00
|
|
|
updateTaskName({ variables: { taskID: updatedTask.id, name: newName } });
|
2020-04-20 05:02:55 +02:00
|
|
|
}}
|
|
|
|
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
2020-09-03 03:25:28 +02:00
|
|
|
updateTaskDescription({
|
|
|
|
variables: { taskID: updatedTask.id, description: newDescription },
|
|
|
|
optimisticResponse: {
|
|
|
|
__typename: 'Mutation',
|
|
|
|
updateTaskDescription: {
|
|
|
|
__typename: 'Task',
|
|
|
|
id: updatedTask.id,
|
|
|
|
description: newDescription,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
2020-04-16 22:05:12 +02:00
|
|
|
}}
|
2020-04-20 05:02:55 +02:00
|
|
|
onDeleteTask={deletedTask => {
|
2020-07-17 02:40:23 +02:00
|
|
|
deleteTask({ variables: { taskID: deletedTask.id } });
|
2020-09-20 00:26:02 +02:00
|
|
|
history.push(`${match.url}/board`);
|
2020-05-31 06:11:19 +02:00
|
|
|
}}
|
|
|
|
onOpenAddLabelPopup={(task, $targetRef) => {
|
|
|
|
taskLabelsRef.current = task.labels;
|
|
|
|
showPopup(
|
|
|
|
$targetRef,
|
|
|
|
<LabelManagerEditor
|
|
|
|
onLabelToggle={labelID => {
|
2020-07-17 02:40:23 +02:00
|
|
|
toggleTaskLabel({ variables: { taskID: task.id, projectLabelID: labelID } });
|
2020-05-31 06:11:19 +02:00
|
|
|
}}
|
|
|
|
labelColors={data.labelColors}
|
|
|
|
labels={labelsRef}
|
|
|
|
taskLabels={taskLabelsRef}
|
|
|
|
projectID={projectID}
|
|
|
|
/>,
|
|
|
|
);
|
2020-04-16 22:05:12 +02:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
/>
|
2020-04-10 04:40:22 +02:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
}
|
2020-04-20 05:02:55 +02:00
|
|
|
return <div>Error</div>;
|
2020-04-10 04:40:22 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
export default Project;
|