feature: UI changes

This commit is contained in:
Jordan Knott
2020-04-20 18:04:27 -05:00
parent c38024e692
commit 7e78ee36b4
45 changed files with 1569 additions and 137 deletions

View File

@ -41,13 +41,27 @@ const GlobalTopNavbar: React.FC = () => {
return (
<>
<TopNavbar
bgColor={data ? data.me.profileIcon.bgColor ?? '#7367F0' : '#7367F0'}
firstName={data ? data.me.firstName : ''}
lastName={data ? data.me.lastName : ''}
initials={!data ? '' : data.me.profileIcon.initials ?? ''}
onNotificationClick={() => console.log('beep')}
onProfileClick={onProfileClick}
/>
{menu.isOpen && <DropdownMenu onLogout={onLogout} left={menu.left} top={menu.top} />}
{menu.isOpen && (
<DropdownMenu
onCloseDropdown={() => {
setMenu({
top: 0,
left: 0,
isOpen: false,
});
}}
onLogout={onLogout}
left={menu.left}
top={menu.top}
/>
)}
</>
);
};

View File

@ -4,7 +4,7 @@ import TaskDetails from 'shared/components/TaskDetails';
import PopupMenu from 'shared/components/PopupMenu';
import MemberManager from 'shared/components/MemberManager';
import { useRouteMatch, useHistory } from 'react-router';
import { useFindTaskQuery, useAssignTaskMutation } from 'shared/generated/graphql';
import { useFindTaskQuery, useAssignTaskMutation, useUnassignTaskMutation } from 'shared/generated/graphql';
import UserIDContext from 'App/context';
type DetailsProps = {
@ -15,6 +15,7 @@ type DetailsProps = {
onDeleteTask: (task: Task) => void;
onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void;
availableMembers: Array<TaskUser>;
refreshCache: () => void;
};
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
@ -27,13 +28,25 @@ const Details: React.FC<DetailsProps> = ({
onDeleteTask,
onOpenAddLabelPopup,
availableMembers,
refreshCache,
}) => {
const { userID } = useContext(UserIDContext);
const history = useHistory();
const match = useRouteMatch();
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
const { loading, data } = useFindTaskQuery({ variables: { taskID } });
const [assignTask] = useAssignTaskMutation();
const { loading, data, refetch } = useFindTaskQuery({ variables: { taskID } });
const [assignTask] = useAssignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
const [unassignTask] = useUnassignTaskMutation({
onCompleted: () => {
refetch();
refreshCache();
},
});
if (loading) {
return <div>loading</div>;
}
@ -47,6 +60,7 @@ const Details: React.FC<DetailsProps> = ({
profileIcon: {
url: null,
initials: assigned.profileIcon.initials ?? null,
bgColor: assigned.profileIcon.bgColor ?? null,
},
};
});
@ -93,10 +107,13 @@ const Details: React.FC<DetailsProps> = ({
>
<MemberManager
availableMembers={availableMembers}
activeMembers={[]}
activeMembers={taskMembers}
onMemberChange={(member, isActive) => {
console.log(`is active ${member.userID} - ${isActive}`);
if (isActive) {
assignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
} else {
unassignTask({ variables: { taskID: data.findTask.taskID, userID: userID ?? '' } });
}
console.log(member, isActive);
}}

View File

@ -1,5 +1,6 @@
import styled from 'styled-components';
export const Board = styled.div`
margin-left: 36px;
margin-top: 12px;
margin-left: 8px;
`;

View File

@ -109,9 +109,10 @@ const Project = () => {
);
},
});
const { loading, data } = useFindProjectQuery({
const { loading, data, refetch } = useFindProjectQuery({
variables: { projectId },
onCompleted: newData => {
console.log('beep!');
const newListsData: BoardState = { tasks: {}, columns: {} };
newData.findProject.taskGroups.forEach(taskGroup => {
newListsData.columns[taskGroup.taskGroupID] = {
@ -121,15 +122,27 @@ const Project = () => {
tasks: [],
};
taskGroup.tasks.forEach(task => {
const taskMembers = task.assigned.map(assigned => {
return {
userID: assigned.userID,
displayName: `${assigned.firstName} ${assigned.lastName}`,
profileIcon: {
url: null,
initials: assigned.profileIcon.initials ?? '',
bgColor: assigned.profileIcon.bgColor ?? '#7367F0',
},
};
});
newListsData.tasks[task.taskID] = {
taskID: task.taskID,
taskGroup: {
taskGroupID: taskGroup.taskGroupID,
},
name: task.name,
position: task.position,
labels: [],
position: task.position,
description: task.description ?? undefined,
members: taskMembers,
};
});
});
@ -196,15 +209,16 @@ const Project = () => {
const availableMembers = data.findProject.members.map(member => {
return {
displayName: `${member.firstName} ${member.lastName}`,
profileIcon: { url: null, initials: member.profileIcon.initials ?? null },
profileIcon: {
url: null,
initials: member.profileIcon.initials ?? null,
bgColor: member.profileIcon.bgColor ?? null,
},
userID: member.userID,
};
});
return (
<>
<TitleWrapper>
<Title>{data.findProject.name}</Title>
</TitleWrapper>
<KanbanBoard
listsData={listsData}
onCardDrop={onCardDrop}
@ -253,6 +267,10 @@ const Project = () => {
path={`${match.path}/c/:taskID`}
render={(routeProps: RouteComponentProps<TaskRouteProps>) => (
<Details
refreshCache={() => {
console.log('beep 2!');
refetch();
}}
availableMembers={availableMembers}
projectURL={match.url}
taskID={routeProps.match.params.taskID}

View File

@ -37,6 +37,7 @@ type InnerTaskGroup = {
type ProfileIcon = {
url: string | null;
initials: string | null;
bgColor: string | null;
};
type TaskUser = {

View File

@ -98,9 +98,6 @@ export const ListCardOperation = styled.span`
display: flex;
align-content: center;
justify-content: center;
background-color: ${props => mixin.darken('#262c49', 0.15)};
background-clip: padding-box;
background-origin: padding-box;
border-radius: 3px;
opacity: 0.8;
padding: 6px;
@ -108,6 +105,10 @@ export const ListCardOperation = styled.span`
right: 2px;
top: 2px;
z-index: 10;
&:hover {
background-color: ${props => mixin.darken('#262c49', 0.45)};
}
`;
export const CardTitle = styled.span`
@ -120,3 +121,34 @@ export const CardTitle = styled.span`
word-wrap: break-word;
color: #c2c6dc;
`;
export const CardMembers = styled.div`
float: right;
margin: 0 -2px 0 0;
`;
export const CardMember = styled.div<{ bgColor: string }>`
height: 28px;
width: 28px;
float: right;
margin: 0 0 4px 4px;
background-color: ${props => props.bgColor};
color: #fff;
border-radius: 25em;
cursor: pointer;
display: block;
overflow: visible;
position: relative;
text-decoration: none;
z-index: 0;
`;
export const CardMemberInitials = styled.div`
height: 28px;
width: 28px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
`;

View File

@ -18,6 +18,9 @@ import {
ListCardLabel,
ListCardOperation,
CardTitle,
CardMembers,
CardMember,
CardMemberInitials,
} from './Styles';
type DueDate = {
@ -42,6 +45,7 @@ type Props = {
watched?: boolean;
labels?: Label[];
wrapperProps?: any;
members?: Array<TaskUser> | null;
};
const Card = React.forwardRef(
@ -58,6 +62,7 @@ const Card = React.forwardRef(
description,
checklists,
watched,
members,
}: Props,
$cardRef: any,
) => {
@ -95,9 +100,11 @@ const Card = React.forwardRef(
{...wrapperProps}
>
<ListCardInnerContainer ref={$innerCardRef}>
<ListCardOperation>
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
</ListCardOperation>
{isActive && (
<ListCardOperation>
<FontAwesomeIcon onClick={onOperationClick} color="#c2c6dc" size="xs" icon={faPencilAlt} />
</ListCardOperation>
)}
<ListCardDetails>
<ListCardLabels>
{labels &&
@ -132,6 +139,14 @@ const Card = React.forwardRef(
</ListCardBadge>
)}
</ListCardBadges>
<CardMembers>
{members &&
members.map(member => (
<CardMember key={member.userID} bgColor={member.profileIcon.bgColor ?? '#7367F0'}>
<CardMemberInitials>{member.profileIcon.initials}</CardMemberInitials>
</CardMember>
))}
</CardMembers>
</ListCardDetails>
</ListCardInnerContainer>
</ListCardContainer>

View File

@ -1,8 +1,7 @@
import React, { createRef, useState } from 'react';
import styled from 'styled-components';
import DropdownMenu from '.';
import { action } from '@storybook/addon-actions';
import DropdownMenu from '.';
export default {
component: DropdownMenu,
@ -50,7 +49,16 @@ export const Default = () => {
Click me
</Button>
</Container>
{menu.isOpen && <DropdownMenu onLogout={action('on logout')} left={menu.left} top={menu.top} />}
{menu.isOpen && (
<DropdownMenu
onCloseDropdown={() => {
setMenu({ top: 0, left: 0, isOpen: false });
}}
onLogout={action('on logout')}
left={menu.left}
top={menu.top}
/>
)}
</>
);
};

View File

@ -1,5 +1,5 @@
import React from 'react';
import React, { useRef } from 'react';
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import { Exit, User } from 'shared/icons';
import { Separator, Container, WrapperDiamond, Wrapper, ActionsList, ActionItem, ActionTitle } from './Styles';
@ -7,11 +7,14 @@ type DropdownMenuProps = {
left: number;
top: number;
onLogout: () => void;
onCloseDropdown: () => void;
};
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout }) => {
const DropdownMenu: React.FC<DropdownMenuProps> = ({ left, top, onLogout, onCloseDropdown }) => {
const $containerRef = useRef<HTMLDivElement>(null);
useOnOutsideClick($containerRef, true, onCloseDropdown, null);
return (
<Container left={left} top={top}>
<Container ref={$containerRef} left={left} top={top}>
<Wrapper>
<ActionItem>
<User size={16} color="#c2c6dc" />

View File

@ -23,7 +23,9 @@ export const Default = () => {
position: 1,
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
description: 'hello!',
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }],
members: [
{ userID: '1', profileIcon: { url: null, initials: null, bgColor: null }, displayName: 'Jordan Knott' },
],
}}
onCancel={action('cancel')}
onDueDateChange={action('due date change')}

View File

@ -159,6 +159,7 @@ const Lists: React.FC<Props> = ({
description=""
title={task.name}
labels={task.labels}
members={task.members}
onClick={() => onCardClick(task)}
onContextMenu={onQuickEditorOpen}
/>

View File

@ -0,0 +1,50 @@
import styled from 'styled-components';
export const Profile = styled.div`
margin: 8px 0;
min-height: 56px;
position: relative;
`;
export const ProfileIcon = styled.div<{ bgColor: string }>`
float: left;
margin: 2px;
background-color: ${props => props.bgColor};
border-radius: 25em;
display: block;
height: 50px;
overflow: hidden;
position: relative;
width: 50px;
z-index: 1;
`;
export const ProfileInfo = styled.div`
margin: 0 0 0 64px;
word-wrap: break-word;
`;
export const InfoTitle = styled.h3`
margin: 0 40px 0 0;
font-size: 16px;
font-weight: 600;
line-height: 20px;
color: #172b4d;
`;
export const InfoUsername = styled.p`
color: #5e6c84;
font-size: 14px;
line-height: 20px;
`;
export const InfoBio = styled.p`
font-size: 14px;
line-height: 20px;
color: #5e6c84;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin: 0;
padding: 0;
`;

View File

@ -0,0 +1,26 @@
import React from 'react';
import { Profile, ProfileIcon, ProfileInfo, InfoTitle, InfoUsername, InfoBio } from './Styles';
type MiniProfileProps = {
displayName: string;
username: string;
bio: string;
profileIcon: ProfileIcon;
};
const MiniProfile: React.FC<MiniProfileProps> = ({ displayName, username, bio, profileIcon }) => {
return (
<>
<Profile>
<ProfileIcon bgColor={profileIcon.bgColor ?? ''}>{profileIcon.initials}</ProfileIcon>
<ProfileInfo>
<InfoTitle>{displayName}</InfoTitle>
<InfoUsername>{username}</InfoUsername>
<InfoBio>{bio}</InfoBio>
</ProfileInfo>
</Profile>
</>
);
};
export default MiniProfile;

View File

@ -17,13 +17,12 @@ export const ClickableOverlay = styled.div`
background: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
padding: 50px;
`;
export const StyledModal = styled.div<{ width: number }>`
display: inline-block;
position: relative;
margin: 48px 0 80px;
width: 100%;
background: #262c49;
max-width: ${props => props.width}px;

View File

@ -1,32 +1,14 @@
import styled, { css } from 'styled-components';
export const LogoWrapper = styled.div`
margin: 20px 0px 20px;
position: relative;
width: 100%;
height: 42px;
line-height: 42px;
padding-left: 64px;
color: rgb(222, 235, 255);
cursor: pointer;
user-select: none;
transition: color 0.1s ease 0s;
`;
export const Logo = styled.div`
position: absolute;
left: 19px;
`;
export const Logo = styled.div``;
export const LogoTitle = styled.div`
position: relative;
right: 12px;
position: absolute;
visibility: hidden;
opacity: 0;
font-size: 24px;
font-weight: 600;
transition: right 0.1s ease 0s, visibility, opacity, transform 0.25s ease;
transition: visibility, opacity, transform 0.25s ease;
color: #7367f0;
`;
export const ActionContainer = styled.div`
@ -54,6 +36,10 @@ export const IconWrapper = styled.div`
export const ActionButtonContainer = styled.div`
padding: 0 12px;
position: relative;
& > a:first-child > div {
padding-top: 48px;
}
`;
export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
@ -65,7 +51,7 @@ export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
`}
border-radius: 6px;
cursor: pointer;
padding: 10px 15px;
padding: 24px 15px;
display: flex;
align-items: center;
&:hover ${ActionButtonTitle} {
@ -76,6 +62,20 @@ export const ActionButtonWrapper = styled.div<{ active?: boolean }>`
}
`;
export const LogoWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
width: 100%;
height: 80px;
color: rgb(222, 235, 255);
cursor: pointer;
transition: color 0.1s ease 0s, border 0.1s ease 0s;
border-bottom: 1px solid rgba(65, 69, 97, 0.65);
`;
export const Container = styled.aside`
z-index: 100;
position: fixed;
@ -87,13 +87,15 @@ export const Container = styled.aside`
transform: translateZ(0px);
background: #10163a;
transition: all 0.1s ease 0s;
border-right: 1px solid rgba(65, 69, 97, 0.65);
&:hover {
width: 260px;
box-shadow: rgba(0, 0, 0, 0.6) 0px 0px 50px 0px;
border-right: 1px solid rgba(65, 69, 97, 0);
}
&:hover ${LogoTitle} {
right: 0px;
bottom: -12px;
visibility: visible;
opacity: 1;
}
@ -102,4 +104,8 @@ export const Container = styled.aside`
visibility: visible;
opacity: 1;
}
&:hover ${LogoWrapper} {
border-bottom: 1px solid rgba(65, 69, 97, 0);
}
`;

View File

@ -35,9 +35,7 @@ export const ButtonContainer: React.FC = ({ children }) => (
export const PrimaryLogo = () => {
return (
<LogoWrapper>
<Logo>
<Citadel size={42} />
</Logo>
<Citadel size={42} />
<LogoTitle>Citadel</LogoTitle>
</LogoWrapper>
);

View File

@ -6,6 +6,7 @@ import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
import ListActions from 'shared/components/ListActions';
import MemberManager from 'shared/components/MemberManager';
import DueDateManager from 'shared/components/DueDateManager';
import MiniProfile from 'shared/components/MiniProfile';
import PopupMenu from '.';
import NormalizeStyles from 'App/NormalizeStyles';
@ -115,7 +116,7 @@ export const MemberManagerPopup = () => {
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MemberManager
availableMembers={[
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { url: null, initials: null } },
{ userID: '1', displayName: 'Jordan Knott', profileIcon: { bgColor: null, url: null, initials: null } },
]}
activeMembers={[]}
onMemberChange={action('member change')}
@ -158,7 +159,9 @@ export const DueDateManagerPopup = () => {
position: 1,
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
description: 'hello!',
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }],
members: [
{ userID: '1', profileIcon: { bgColor: null, url: null, initials: null }, displayName: 'Jordan Knott' },
],
}}
onCancel={action('cancel')}
onDueDateChange={action('due date change')}
@ -189,3 +192,47 @@ export const DueDateManagerPopup = () => {
</>
);
};
export const MiniProfilePopup = () => {
const $buttonRef = useRef<HTMLButtonElement>(null);
const [popupData, setPopupData] = useState(initalState);
return (
<>
<NormalizeStyles />
<BaseStyles />
{popupData.isOpen && (
<PopupMenu title="Due Date" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
<MiniProfile
displayName="Jordan Knott"
profileIcon={{ url: null, bgColor: '#000', initials: 'JK' }}
username="@jordanthedev"
bio="Stuff and things"
/>
</PopupMenu>
)}
<span
style={{
width: '60px',
textAlign: 'center',
margin: '25px auto',
cursor: 'pointer',
color: '#fff',
background: '#f00',
}}
ref={$buttonRef}
onClick={() => {
if ($buttonRef && $buttonRef.current) {
const pos = $buttonRef.current.getBoundingClientRect();
setPopupData({
isOpen: true,
left: pos.left,
top: pos.top + pos.height + 10,
});
}
}}
>
Open
</span>
</>
);
};

View File

@ -281,3 +281,9 @@ export const NoDueDateLabel = styled.span`
font-size: 14px;
cursor: pointer;
`;
export const UnassignedLabel = styled.div`
color: rgb(137, 147, 164);
font-size: 14px;
cursor: pointer;
`;

View File

@ -35,7 +35,13 @@ export const Default = () => {
position: 1,
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
description,
members: [{ userID: '1', profileIcon: { url: null, initials: null }, displayName: 'Jordan Knott' }],
members: [
{
userID: '1',
profileIcon: { bgColor: null, url: null, initials: null },
displayName: 'Jordan Knott',
},
],
}}
onTaskNameChange={action('task name change')}
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}

View File

@ -4,6 +4,7 @@ import useOnOutsideClick from 'shared/hooks/onOutsideClick';
import {
NoDueDateLabel,
UnassignedLabel,
TaskDetailsAddMember,
TaskGroupLabel,
TaskGroupLabelName,
@ -126,11 +127,16 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
onTaskNameChange(task, taskName);
}
};
const $unassignedRef = useRef<HTMLDivElement>(null);
const $addMemberRef = useRef<HTMLDivElement>(null);
const onUnassignedClick = () => {
const bounds = convertDivElementRefToBounds($unassignedRef);
if (bounds) {
onOpenAddMemberPopup(task, bounds);
}
};
const onAddMember = () => {
console.log('beep!');
const bounds = convertDivElementRefToBounds($addMemberRef);
console.log(bounds);
if (bounds) {
onOpenAddMemberPopup(task, bounds);
}
@ -191,20 +197,28 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({
<TaskDetailsSidebar>
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
<TaskDetailAssignees>
{task.members &&
task.members.map(member => {
console.log(member);
return (
<TaskDetailAssignee key={member.userID}>
<ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon>
</TaskDetailAssignee>
);
})}
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
<TaskDetailsAddMemberIcon>
<Plus size={16} color="#c2c6dc" />
</TaskDetailsAddMemberIcon>
</TaskDetailsAddMember>
{task.members && task.members.length === 0 ? (
<UnassignedLabel ref={$unassignedRef} onClick={onUnassignedClick}>
Unassigned
</UnassignedLabel>
) : (
<>
{task.members &&
task.members.map(member => {
console.log(member);
return (
<TaskDetailAssignee key={member.userID}>
<ProfileIcon>{member.profileIcon.initials ?? ''}</ProfileIcon>
</TaskDetailAssignee>
);
})}
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
<TaskDetailsAddMemberIcon>
<Plus size={16} color="#c2c6dc" />
</TaskDetailsAddMemberIcon>
</TaskDetailsAddMember>
</>
)}
</TaskDetailAssignees>
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
<TaskDetailLabels>

View File

@ -1,19 +1,18 @@
import styled from 'styled-components';
export const NavbarWrapper = styled.div`
height: 103px;
padding: 1.3rem 2.2rem 2.2rem;
width: 100%;
`;
export const NavbarHeader = styled.header`
border-radius: 0.5rem;
padding: 0.8rem 1rem;
height: 80px;
padding: 0 1.75rem;
display: flex;
align-items: center;
justify-content: space-between;
background: rgb(16, 22, 58);
box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.05);
border-bottom: 1px solid rgba(65, 69, 97, 0.65);
`;
export const Breadcrumbs = styled.div`
color: rgb(94, 108, 132);
@ -26,7 +25,14 @@ export const BreadcrumpSeparator = styled.span`
margin: 0px 10px;
`;
export const ProjectActions = styled.div``;
export const ProjectActions = styled.div`
align-items: flex-start;
display: flex;
flex: 1;
flex-direction: column;
min-width: 1px;
`;
export const GlobalActions = styled.div`
display: flex;
align-items: center;
@ -55,7 +61,7 @@ export const ProfileNameSecondary = styled.small`
color: #c2c6dc;
`;
export const ProfileIcon = styled.div`
export const ProfileIcon = styled.div<{ bgColor: string }>`
margin-left: 10px;
width: 40px;
height: 40px;
@ -65,6 +71,48 @@ export const ProfileIcon = styled.div`
justify-content: center;
color: #fff;
font-weight: 700;
background: rgb(115, 103, 240);
background: ${props => props.bgColor};
cursor: pointer;
`;
export const ProjectMeta = styled.div`
align-items: center;
display: flex;
max-width: 100%;
min-height: 51px;
`;
export const ProjectTabs = styled.div`
align-items: flex-end;
align-self: stretch;
display: flex;
flex: 1 0 auto;
justify-content: flex-start;
max-width: 100%;
`;
export const ProjectTab = styled.span`
font-size: 80%;
color: #c2c6dc;
font-size: 15px;
cursor: default;
display: flex;
line-height: normal;
min-width: 1px;
transition-duration: 0.2s;
transition-property: box-shadow, color;
white-space: nowrap;
flex: 0 1 auto;
padding-bottom: 12px;
box-shadow: inset 0 -2px #d85dd8;
color: #d85dd8;
`;
export const ProjectName = styled.h1`
color: #c2c6dc;
margin-top: 9px;
font-weight: 600;
font-size: 20px;
`;

View File

@ -38,13 +38,23 @@ export const Default = () => {
<NormalizeStyles />
<BaseStyles />
<TopNavbar
bgColor="#7367F0"
firstName="Jordan"
lastName="Knott"
initials="JK"
onNotificationClick={action('notifications click')}
onProfileClick={onClick}
/>
{menu.isOpen && <DropdownMenu onLogout={action('on logout')} left={menu.left} top={menu.top} />}
{menu.isOpen && (
<DropdownMenu
onCloseDropdown={() => {
setMenu({ left: 0, top: 0, isOpen: false });
}}
onLogout={action('on logout')}
left={menu.left}
top={menu.top}
/>
)}
</>
);
};

View File

@ -5,6 +5,10 @@ import {
NotificationContainer,
GlobalActions,
ProjectActions,
ProjectMeta,
ProjectName,
ProjectTabs,
ProjectTab,
NavbarWrapper,
NavbarHeader,
Breadcrumbs,
@ -19,11 +23,19 @@ import {
type NavBarProps = {
onProfileClick: (bottom: number, right: number) => void;
onNotificationClick: () => void;
bgColor: string;
firstName: string;
lastName: string;
initials: string;
};
const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, firstName, lastName, initials }) => {
const NavBar: React.FC<NavBarProps> = ({
onProfileClick,
onNotificationClick,
firstName,
lastName,
initials,
bgColor,
}) => {
const $profileRef: any = useRef(null);
const handleProfileClick = () => {
console.log('click');
@ -34,13 +46,12 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, fi
<NavbarWrapper>
<NavbarHeader>
<ProjectActions>
<Breadcrumbs>
Projects
<BreadcrumpSeparator>/</BreadcrumpSeparator>
project name
<BreadcrumpSeparator>/</BreadcrumpSeparator>
Board
</Breadcrumbs>
<ProjectMeta>
<ProjectName>Production Team</ProjectName>
</ProjectMeta>
<ProjectTabs>
<ProjectTab>Board</ProjectTab>
</ProjectTabs>
</ProjectActions>
<GlobalActions>
<NotificationContainer onClick={onNotificationClick}>
@ -53,7 +64,7 @@ const NavBar: React.FC<NavBarProps> = ({ onProfileClick, onNotificationClick, fi
</ProfileNamePrimary>
<ProfileNameSecondary>Manager</ProfileNameSecondary>
</ProfileNameWrapper>
<ProfileIcon ref={$profileRef} onClick={handleProfileClick}>
<ProfileIcon ref={$profileRef} onClick={handleProfileClick} bgColor={bgColor}>
{initials}
</ProfileIcon>
</ProfileContainer>

View File

@ -15,17 +15,28 @@ export type Scalars = {
export type ProjectLabel = {
__typename?: 'ProjectLabel';
projectLabelID: Scalars['ID'];
createdDate: Scalars['Time'];
colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
};
export type TaskLabel = {
__typename?: 'TaskLabel';
taskLabelID: Scalars['ID'];
labelColorID: Scalars['UUID'];
projectLabelID: Scalars['UUID'];
assignedDate: Scalars['Time'];
colorHex: Scalars['String'];
name?: Maybe<Scalars['String']>;
};
export type ProfileIcon = {
__typename?: 'ProfileIcon';
url?: Maybe<Scalars['String']>;
initials?: Maybe<Scalars['String']>;
bgColor?: Maybe<Scalars['String']>;
};
export type ProjectMember = {
@ -71,6 +82,7 @@ export type Project = {
owner: ProjectMember;
taskGroups: Array<TaskGroup>;
members: Array<ProjectMember>;
labels: Array<ProjectLabel>;
};
export type TaskGroup = {
@ -222,6 +234,11 @@ export type AssignTaskInput = {
userID: Scalars['UUID'];
};
export type UnassignTaskInput = {
taskID: Scalars['UUID'];
userID: Scalars['UUID'];
};
export type UpdateTaskDescriptionInput = {
taskID: Scalars['UUID'];
description: Scalars['String'];
@ -237,12 +254,19 @@ export type RemoveTaskLabelInput = {
taskLabelID: Scalars['UUID'];
};
export type NewProjectLabel = {
projectID: Scalars['UUID'];
labelColorID: Scalars['UUID'];
name?: Maybe<Scalars['String']>;
};
export type Mutation = {
__typename?: 'Mutation';
createRefreshToken: RefreshToken;
createUserAccount: UserAccount;
createTeam: Team;
createProject: Project;
createProjectLabel: ProjectLabel;
createTaskGroup: TaskGroup;
updateTaskGroupLocation: TaskGroup;
deleteTaskGroup: DeleteTaskGroupPayload;
@ -254,6 +278,7 @@ export type Mutation = {
updateTaskName: Task;
deleteTask: DeleteTaskPayload;
assignTask: Task;
unassignTask: Task;
logoutUser: Scalars['Boolean'];
};
@ -278,6 +303,11 @@ export type MutationCreateProjectArgs = {
};
export type MutationCreateProjectLabelArgs = {
input: NewProjectLabel;
};
export type MutationCreateTaskGroupArgs = {
input: NewTaskGroup;
};
@ -333,6 +363,11 @@ export type MutationAssignTaskArgs = {
};
export type MutationUnassignTaskArgs = {
input?: Maybe<UnassignTaskInput>;
};
export type MutationLogoutUserArgs = {
input: LogoutUser;
};
@ -438,7 +473,7 @@ export type FindProjectQuery = (
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials'>
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)>, taskGroups: Array<(
{ __typename?: 'TaskGroup' }
@ -446,6 +481,14 @@ export type FindProjectQuery = (
& { tasks: Array<(
{ __typename?: 'Task' }
& Pick<Task, 'taskID' | 'name' | 'position' | 'description'>
& { assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
)> }
)> }
) }
@ -469,7 +512,7 @@ export type FindTaskQuery = (
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'url' | 'initials'>
& Pick<ProfileIcon, 'url' | 'initials' | 'bgColor'>
) }
)> }
) }
@ -500,11 +543,29 @@ export type MeQuery = (
& Pick<UserAccount, 'firstName' | 'lastName'>
& { profileIcon: (
{ __typename?: 'ProfileIcon' }
& Pick<ProfileIcon, 'initials'>
& Pick<ProfileIcon, 'initials' | 'bgColor'>
) }
) }
);
export type UnassignTaskMutationVariables = {
taskID: Scalars['UUID'];
userID: Scalars['UUID'];
};
export type UnassignTaskMutation = (
{ __typename?: 'Mutation' }
& { unassignTask: (
{ __typename?: 'Task' }
& Pick<Task, 'taskID'>
& { assigned: Array<(
{ __typename?: 'ProjectMember' }
& Pick<ProjectMember, 'userID' | 'firstName' | 'lastName'>
)> }
) }
);
export type UpdateTaskDescriptionMutationVariables = {
taskID: Scalars['UUID'];
description: Scalars['String'];
@ -759,6 +820,7 @@ export const FindProjectDocument = gql`
profileIcon {
url
initials
bgColor
}
}
taskGroups {
@ -770,6 +832,16 @@ export const FindProjectDocument = gql`
name
position
description
assigned {
userID
firstName
lastName
profileIcon {
url
initials
bgColor
}
}
}
}
}
@ -818,6 +890,7 @@ export const FindTaskDocument = gql`
profileIcon {
url
initials
bgColor
}
}
}
@ -893,6 +966,7 @@ export const MeDocument = gql`
lastName
profileIcon {
initials
bgColor
}
}
}
@ -922,6 +996,44 @@ export function useMeLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptio
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
export type MeQueryResult = ApolloReactCommon.QueryResult<MeQuery, MeQueryVariables>;
export const UnassignTaskDocument = gql`
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) {
assigned {
userID
firstName
lastName
}
taskID
}
}
`;
export type UnassignTaskMutationFn = ApolloReactCommon.MutationFunction<UnassignTaskMutation, UnassignTaskMutationVariables>;
/**
* __useUnassignTaskMutation__
*
* To run a mutation, you first call `useUnassignTaskMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUnassignTaskMutation` 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 [unassignTaskMutation, { data, loading, error }] = useUnassignTaskMutation({
* variables: {
* taskID: // value for 'taskID'
* userID: // value for 'userID'
* },
* });
*/
export function useUnassignTaskMutation(baseOptions?: ApolloReactHooks.MutationHookOptions<UnassignTaskMutation, UnassignTaskMutationVariables>) {
return ApolloReactHooks.useMutation<UnassignTaskMutation, UnassignTaskMutationVariables>(UnassignTaskDocument, baseOptions);
}
export type UnassignTaskMutationHookResult = ReturnType<typeof useUnassignTaskMutation>;
export type UnassignTaskMutationResult = ApolloReactCommon.MutationResult<UnassignTaskMutation>;
export type UnassignTaskMutationOptions = ApolloReactCommon.BaseMutationOptions<UnassignTaskMutation, UnassignTaskMutationVariables>;
export const UpdateTaskDescriptionDocument = gql`
mutation updateTaskDescription($taskID: UUID!, $description: String!) {
updateTaskDescription(input: {taskID: $taskID, description: $description}) {

View File

@ -8,6 +8,7 @@ query findProject($projectId: String!) {
profileIcon {
url
initials
bgColor
}
}
taskGroups {
@ -19,6 +20,16 @@ query findProject($projectId: String!) {
name
position
description
assigned {
userID
firstName
lastName
profileIcon {
url
initials
bgColor
}
}
}
}
}

View File

@ -14,6 +14,7 @@ query findTask($taskID: UUID!) {
profileIcon {
url
initials
bgColor
}
}
}

View File

@ -4,6 +4,7 @@ query me {
lastName
profileIcon {
initials
bgColor
}
}
}

View File

@ -0,0 +1,10 @@
mutation unassignTask($taskID: UUID!, $userID: UUID!) {
unassignTask(input: {taskID: $taskID, userID: $userID}) {
assigned {
userID
firstName
lastName
}
taskID
}
}