feature: add ability to delete & update project labels

This commit is contained in:
Jordan Knott
2020-05-27 20:12:50 -05:00
parent cbcd8c5f82
commit 539259effd
32 changed files with 1139 additions and 143 deletions

View File

@ -168,7 +168,9 @@ const Card = React.forwardRef(
</ListCardBadges>
<CardMembers>
{members &&
members.map(member => <Member taskID={taskID} member={member} onCardMemberClick={onCardMemberClick} />)}
members.map(member => (
<Member key={member.userID} taskID={taskID} member={member} onCardMemberClick={onCardMemberClick} />
))}
</CardMembers>
</ListCardDetails>
</ListCardInnerContainer>

View File

@ -74,8 +74,6 @@ const Lists: React.FC<Props> = ({
}),
);
console.log(beforeDropDraggables);
console.log(destination);
const afterDropDraggables = getAfterDropDraggableList(
beforeDropDraggables,
droppedDraggable,
@ -83,19 +81,16 @@ const Lists: React.FC<Props> = ({
isSameList,
destination,
);
console.log(afterDropDraggables);
const newPosition = getNewDraggablePosition(afterDropDraggables, destination.index);
if (isList) {
const droppedList = columns[droppedDraggable.id];
console.log(`is list ${droppedList}`);
onListDrop({
...droppedList,
position: newPosition,
});
} else {
const droppedCard = tasks[droppedDraggable.id];
console.log(`is card ${droppedCard}`);
const newCard = {
...droppedCard,
position: newPosition,
@ -112,6 +107,7 @@ const Lists: React.FC<Props> = ({
return { id: column.taskGroupID, position: column.position };
}),
);
console.log(orderedColumns);
const [currentComposer, setCurrentComposer] = useState('');
return (
@ -134,7 +130,7 @@ const Lists: React.FC<Props> = ({
name={column.name}
onOpenComposer={id => setCurrentComposer(id)}
isComposerOpen={currentComposer === column.taskGroupID}
onSaveName={name => console.log(name)}
onSaveName={name => {}}
tasks={columnCards}
ref={columnDragProvided.innerRef}
wrapperProps={columnDragProvided.draggableProps}

View File

@ -23,7 +23,6 @@ import {
const Login = ({ onSubmit }: LoginProps) => {
const [isComplete, setComplete] = useState(true);
const { register, handleSubmit, errors, setError, formState } = useForm<LoginFormData>();
console.log(formState);
const loginSubmit = (data: LoginFormData) => {
setComplete(false);
onSubmit(data, setComplete, setError);

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import LabelColors from 'shared/constants/labelColors';
import { Checkmark } from 'shared/icons';
import { SaveButton, DeleteButton, LabelBox, EditLabelForm, FieldLabel, FieldName } from './Styles';
@ -7,16 +7,25 @@ type Props = {
labelColors: Array<LabelColor>;
label: Label | null;
onLabelEdit: (labelId: string | null, labelName: string, labelColor: LabelColor) => void;
onLabelDelete?: (labelId: string) => void;
};
const LabelManager = ({ labelColors, label, onLabelEdit }: Props) => {
console.log(label);
const LabelManager = ({ labelColors, label, onLabelEdit, onLabelDelete }: Props) => {
const $fieldName = useRef<HTMLInputElement>(null);
const [currentLabel, setCurrentLabel] = useState(label ? label.name : '');
const [currentColor, setCurrentColor] = useState<LabelColor | null>(label ? label.labelColor : null);
useEffect(() => {
if ($fieldName.current) {
$fieldName.current.focus();
}
}, []);
return (
<EditLabelForm>
<FieldLabel>Name</FieldLabel>
<FieldName
ref={$fieldName}
id="labelName"
type="text"
name="name"
@ -29,6 +38,7 @@ const LabelManager = ({ labelColors, label, onLabelEdit }: Props) => {
<div>
{labelColors.map((labelColor: LabelColor) => (
<LabelBox
key={labelColor.id}
color={labelColor.colorHex}
onClick={() => {
setCurrentColor(labelColor);
@ -40,6 +50,8 @@ const LabelManager = ({ labelColors, label, onLabelEdit }: Props) => {
</div>
<div>
<SaveButton
value="Save"
type="submit"
onClick={e => {
e.preventDefault();
console.log(currentColor);
@ -47,10 +59,17 @@ const LabelManager = ({ labelColors, label, onLabelEdit }: Props) => {
onLabelEdit(label ? label.labelId : null, currentLabel, currentColor);
}
}}
type="submit"
value="Save"
/>
<DeleteButton type="submit" value="Delete" />
{label && onLabelDelete && (
<DeleteButton
value="Delete"
type="submit"
onClick={e => {
e.preventDefault();
onLabelDelete(label.labelId);
}}
/>
)}
</div>
</EditLabelForm>
);

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { Pencil, Checkmark } from 'shared/icons';
import {
@ -20,12 +20,19 @@ type Props = {
onLabelCreate: () => void;
};
const LabelManager: React.FC<Props> = ({ labels, onLabelToggle, onLabelEdit, onLabelCreate }) => {
const $fieldName = useRef<HTMLInputElement>(null);
const [currentLabel, setCurrentLabel] = useState('');
const [currentSearch, setCurrentSearch] = useState('');
useEffect(() => {
if ($fieldName.current) {
$fieldName.current.focus();
}
}, []);
return (
<>
<LabelSearch
type="text"
ref={$fieldName}
placeholder="search labels..."
onChange={e => {
setCurrentSearch(e.currentTarget.value);

View File

@ -248,36 +248,47 @@ export const LabelBox = styled.span<{ color: string }>`
`;
export const SaveButton = styled.input`
cursor: pointer;
background-color: #5aac44;
background: rgb(115, 103, 240);
box-shadow: none;
border: none;
color: #fff;
padding-left: 24px;
padding-right: 24px;
ursor: pointer;
cursor: pointer;
display: inline-block;
font-weight: 400;
line-height: 20px;
margin: 8px 4px 0 0;
margin-right: 4px;
padding: 6px 12px;
text-align: center;
border-radius: 3px;
`;
export const DeleteButton = styled.input`
background-color: #cf513d;
box-shadow: none;
border: none;
color: #fff;
cursor: pointer;
type="submit"font-weight: 400;
line-height: 20px;
margin: 8px 4px 0 0;
padding: 6px 12px;
text-align: center;
border-radius: 3px;
float: right;
outline: none;
border: none;
line-height: 20px;
padding: 6px 12px;
background-color: transparent;
text-align: center;
color: #c2c6dc;
font-weight: 400;
line-height: 20px;
cursor: pointer;
margin: 0 0 0 8px;
border-radius: 3px;
border-width: 1px;
border-style: solid;
border-color: transparent;
border-image: initial;
border-color: #414561;
&:hover {
color: #fff;
background: rgb(115, 103, 240);
border-color: transparent;
}
`;
export const CreateLabelButton = styled.button`

View File

@ -18,6 +18,7 @@ type PopupContextState = {
show: (target: RefObject<HTMLElement>, content: JSX.Element) => void;
setTab: (newTab: number) => void;
getCurrentTab: () => number;
hide: () => void;
};
type PopupProps = {
@ -46,11 +47,12 @@ const PopupContext = createContext<PopupContextState>({
show: () => {},
setTab: () => {},
getCurrentTab: () => 0,
hide: () => {},
});
export const usePopup = () => {
const ctx = useContext<PopupContextState>(PopupContext);
return { showPopup: ctx.show, setTab: ctx.setTab, getCurrentTab: ctx.getCurrentTab };
return { showPopup: ctx.show, setTab: ctx.setTab, getCurrentTab: ctx.getCurrentTab, hidePopup: ctx.hide };
};
type PopupState = {
@ -80,11 +82,9 @@ const defaultState = {
export const PopupProvider: React.FC = ({ children }) => {
const [currentState, setState] = useState<PopupState>(defaultState);
const show = (target: RefObject<HTMLElement>, content: JSX.Element) => {
console.log(target);
if (target && target.current) {
const bounds = target.current.getBoundingClientRect();
if (bounds.left + 304 + 30 > window.innerWidth) {
console.log('open!');
setState({
isOpen: true,
left: bounds.left + bounds.width,
@ -95,7 +95,6 @@ export const PopupProvider: React.FC = ({ children }) => {
content,
});
} else {
console.log('open NOT INVERT!');
setState({
isOpen: true,
left: bounds.left,
@ -108,6 +107,17 @@ export const PopupProvider: React.FC = ({ children }) => {
}
}
};
const hide = () => {
setState({
isOpen: false,
left: 0,
top: 0,
invert: true,
currentTab: 0,
previousTab: 0,
content: null,
});
};
const portalTarget = canUseDOM ? document.body : null; // appease flow
const setTab = (newTab: number) => {
@ -125,7 +135,7 @@ export const PopupProvider: React.FC = ({ children }) => {
};
return (
<Provider value={{ show, setTab, getCurrentTab }}>
<Provider value={{ hide, show, setTab, getCurrentTab }}>
{portalTarget &&
currentState.isOpen &&
createPortal(

View File

@ -146,6 +146,14 @@ export const TaskDetailsMarkdown = styled.div`
width: 100%;
cursor: pointer;
color: #c2c6dc;
p {
margin: 0 0 8px;
}
strong {
font-weight: 700;
}
`;
export const TaskDetailsControls = styled.div`

View File

@ -1,6 +1,7 @@
import React, { useState, useRef, useEffect } from 'react';
import { Bin, Cross, Plus } from 'shared/icons';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import ReactMarkdown from 'react-markdown';
import TaskAssignee from 'shared/components/TaskAssignee';
import {
@ -48,7 +49,9 @@ const TaskContent: React.FC<TaskContentProps> = ({ description, onEditContent })
return description === '' ? (
<TaskDetailsAddDetailsButton onClick={onEditContent}>Add a more detailed description</TaskDetailsAddDetailsButton>
) : (
<TaskDetailsMarkdown onClick={onEditContent}>{description}</TaskDetailsMarkdown>
<TaskDetailsMarkdown onClick={onEditContent}>
<ReactMarkdown source={description} />
</TaskDetailsMarkdown>
);
};
@ -142,7 +145,6 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
const onAddLabel = () => {
onOpenAddLabelPopup(task, $addLabelRef);
};
console.log(task);
return (
<>
<TaskActions>

View File

@ -50,7 +50,6 @@ const NavBar: React.FC<NavBarProps> = ({
}) => {
const $profileRef: any = useRef(null);
const handleProfileClick = () => {
console.log('click');
const boundingRect = $profileRef.current.getBoundingClientRect();
onProfileClick(boundingRect.bottom, boundingRect.right);
};
@ -95,7 +94,7 @@ const NavBar: React.FC<NavBarProps> = ({
{projectMembers && (
<ProjectMembers>
{projectMembers.map(member => (
<TaskAssignee size={28} member={member} onMemberProfile={onMemberProfile} />
<TaskAssignee key={member.userID} size={28} member={member} onMemberProfile={onMemberProfile} />
))}
<InviteButton>Invite</InviteButton>
</ProjectMembers>

View File

@ -269,6 +269,26 @@ export type NewProjectLabel = {
name?: Maybe<Scalars['String']>;
};
export type DeleteProjectLabel = {
projectLabelID: Scalars['UUID'];
};
export type UpdateProjectLabelName = {
projectLabelID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateProjectLabel = {
projectLabelID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateProjectLabelColor = {
projectLabelID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
@ -276,6 +296,10 @@ export type Mutation = {
createTeam: Team;
createProject: Project;
createProjectLabel: ProjectLabel;
deleteProjectLabel: ProjectLabel;
updateProjectLabel: ProjectLabel;
updateProjectLabelName: ProjectLabel;
updateProjectLabelColor: ProjectLabel;
createTaskGroup: TaskGroup;
updateTaskGroupLocation: TaskGroup;
deleteTaskGroup: DeleteTaskGroupPayload;
@ -317,6 +341,26 @@ export type MutationCreateProjectLabelArgs = {
};
export type MutationDeleteProjectLabelArgs = {
input: DeleteProjectLabel;
};
export type MutationUpdateProjectLabelArgs = {
input: UpdateProjectLabel;
};
export type MutationUpdateProjectLabelNameArgs = {
input: UpdateProjectLabelName;
};
export type MutationUpdateProjectLabelColorArgs = {
input: UpdateProjectLabelColor;
};
export type MutationCreateTaskGroupArgs = {
input: NewTaskGroup;
};
@ -413,7 +457,7 @@ export type CreateProjectLabelMutation = (
& Pick<ProjectLabel, 'id' | 'createdDate' | 'name'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex'>
& Pick<LabelColor, 'id' | 'colorHex' | 'name' | 'position'>
) }
) }
);
@ -459,6 +503,19 @@ export type CreateTaskGroupMutation = (
) }
);
export type DeleteProjectLabelMutationVariables = {
projectLabelID: Scalars['UUID'];
};
export type DeleteProjectLabelMutation = (
{ __typename?: 'Mutation' }
& { deleteProjectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id'>
) }
);
export type DeleteTaskMutationVariables = {
taskID: Scalars['String'];
};
@ -611,6 +668,25 @@ export type UnassignTaskMutation = (
) }
);
export type UpdateProjectLabelMutationVariables = {
projectLabelID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
name: Scalars['String'];
};
export type UpdateProjectLabelMutation = (
{ __typename?: 'Mutation' }
& { updateProjectLabel: (
{ __typename?: 'ProjectLabel' }
& Pick<ProjectLabel, 'id' | 'createdDate' | 'name'>
& { labelColor: (
{ __typename?: 'LabelColor' }
& Pick<LabelColor, 'id' | 'colorHex' | 'name' | 'position'>
) }
) }
);
export type UpdateTaskDescriptionMutationVariables = {
taskID: Scalars['UUID'];
description: Scalars['String'];
@ -715,6 +791,8 @@ export const CreateProjectLabelDocument = gql`
labelColor {
id
colorHex
name
position
}
name
}
@ -833,6 +911,38 @@ export function useCreateTaskGroupMutation(baseOptions?: ApolloReactHooks.Mutati
export type CreateTaskGroupMutationHookResult = ReturnType<typeof useCreateTaskGroupMutation>;
export type CreateTaskGroupMutationResult = ApolloReactCommon.MutationResult<CreateTaskGroupMutation>;
export type CreateTaskGroupMutationOptions = ApolloReactCommon.BaseMutationOptions<CreateTaskGroupMutation, CreateTaskGroupMutationVariables>;
export const DeleteProjectLabelDocument = gql`
mutation deleteProjectLabel($projectLabelID: UUID!) {
deleteProjectLabel(input: {projectLabelID: $projectLabelID}) {
id
}
}
`;
export type DeleteProjectLabelMutationFn = ApolloReactCommon.MutationFunction<DeleteProjectLabelMutation, DeleteProjectLabelMutationVariables>;
/**
* __useDeleteProjectLabelMutation__
*
* To run a mutation, you first call `useDeleteProjectLabelMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteProjectLabelMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteProjectLabelMutation, { data, loading, error }] = useDeleteProjectLabelMutation({
* variables: {
* projectLabelID: // value for 'projectLabelID'
* },
* });
*/
export function useDeleteProjectLabelMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<DeleteProjectLabelMutation, DeleteProjectLabelMutationVariables>) {
return ApolloReactHooks.useMutation<DeleteProjectLabelMutation, DeleteProjectLabelMutationVariables>(DeleteProjectLabelDocument, baseOptions);
}
export type DeleteProjectLabelMutationHookResult = ReturnType<typeof useDeleteProjectLabelMutation>;
export type DeleteProjectLabelMutationResult = ApolloReactCommon.MutationResult<DeleteProjectLabelMutation>;
export type DeleteProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<DeleteProjectLabelMutation, DeleteProjectLabelMutationVariables>;
export const DeleteTaskDocument = gql`
mutation deleteTask($taskID: String!) {
deleteTask(input: {taskID: $taskID}) {
@ -1147,6 +1257,48 @@ export function useUnassignTaskMutation(baseOptions?: ApolloReactHooks.MutationH
export type UnassignTaskMutationHookResult = ReturnType<typeof useUnassignTaskMutation>;
export type UnassignTaskMutationResult = ApolloReactCommon.MutationResult<UnassignTaskMutation>;
export type UnassignTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<UnassignTaskMutation, UnassignTaskMutationVariables>;
export const UpdateProjectLabelDocument = gql`
mutation updateProjectLabel($projectLabelID: UUID!, $labelColorID: UUID!, $name: String!) {
updateProjectLabel(input: {projectLabelID: $projectLabelID, labelColorID: $labelColorID, name: $name}) {
id
createdDate
labelColor {
id
colorHex
name
position
}
name
}
}
`;
export type UpdateProjectLabelMutationFn = ApolloReactCommon.MutationFunction<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>;
/**
* __useUpdateProjectLabelMutation__
*
* To run a mutation, you first call `useUpdateProjectLabelMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateProjectLabelMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateProjectLabelMutation, { data, loading, error }] = useUpdateProjectLabelMutation({
* variables: {
* projectLabelID: // value for 'projectLabelID'
* labelColorID: // value for 'labelColorID'
* name: // value for 'name'
* },
* });
*/
export function useUpdateProjectLabelMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>) {
return ApolloReactHooks.useMutation<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>(UpdateProjectLabelDocument, baseOptions);
}
export type UpdateProjectLabelMutationHookResult = ReturnType<typeof useUpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationResult = ApolloReactCommon.MutationResult<UpdateProjectLabelMutation>;
export type UpdateProjectLabelMutationOptions = ApolloReactCommon.BaseMutationOptions<UpdateProjectLabelMutation, UpdateProjectLabelMutationVariables>;
export const UpdateTaskDescriptionDocument = gql`
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
updateTaskDescription(input: {taskID: $taskID, description: $description}) {

View File

@ -5,6 +5,8 @@ mutation createProjectLabel($projectID: UUID!, $labelColorID: UUID!, $name: Stri
labelColor {
id
colorHex
name
position
}
name
}

View File

@ -0,0 +1,5 @@
mutation deleteProjectLabel($projectLabelID: UUID!) {
deleteProjectLabel(input: { projectLabelID:$projectLabelID }) {
id
}
}

View File

@ -0,0 +1,13 @@
mutation updateProjectLabel($projectLabelID: UUID!, $labelColorID: UUID!, $name: String!) {
updateProjectLabel(input:{projectLabelID:$projectLabelID, labelColorID: $labelColorID, name: $name}) {
id
createdDate
labelColor {
id
colorHex
name
position
}
name
}
}

View File

@ -9,9 +9,7 @@ export const moveItemWithinArray = (arr: Array<DraggableElement>, item: Draggabl
export const insertItemIntoArray = (arr: Array<DraggableElement>, item: DraggableElement, index: number) => {
const arrClone = [...arr];
console.log(arrClone, index, item);
arrClone.splice(index, 0, item);
console.log(arrClone);
return arrClone;
};
@ -32,21 +30,12 @@ export const getNewDraggablePosition = (afterDropDraggables: Array<DraggableElem
return 65535;
}
if (!prevDraggable) {
console.log(
`in front of list [n/a : ${nextDraggable.id}]: ${nextDraggable.position} / 2.0 = ${nextDraggable.position / 2.0}`,
);
return nextDraggable.position / 2.0;
}
if (!nextDraggable) {
console.log(
`end of list [${prevDraggable.id} : n/a] : ${prevDraggable.position} * 2.0 = ${prevDraggable.position * 2.0}`,
);
return prevDraggable.position * 2.0;
}
const newPos = (prevDraggable.position + nextDraggable.position) / 2.0;
console.log(
`middle of two cards [${prevDraggable.id} : ${nextDraggable.id}] : ${prevDraggable.position} + ${nextDraggable.position} / 2.0 = ${newPos}`,
);
return newPos;
};
@ -58,7 +47,6 @@ export const isPositionChanged = (source: DraggableLocation, destination: Dragga
if (!destination) return false;
const isSameList = destination.droppableId === source.droppableId;
const isSamePosition = destination.index === source.index;
console.log(`isSameList: ${isSameList} : isSamePosition: ${isSamePosition}`);
return !isSameList || !isSamePosition;
};