feature: update task details design
This commit is contained in:
parent
c250ce574b
commit
16eb9e165f
@ -24,6 +24,7 @@
|
||||
"@types/node": "^12.0.0",
|
||||
"@types/react": "^16.9.21",
|
||||
"@types/react-beautiful-dnd": "^12.1.1",
|
||||
"@types/react-datepicker": "^2.11.0",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react-router": "^5.1.4",
|
||||
"@types/react-router-dom": "^5.1.3",
|
||||
@ -41,10 +42,12 @@
|
||||
"history": "^4.10.1",
|
||||
"immer": "^6.0.3",
|
||||
"lodash": "^4.17.15",
|
||||
"moment": "^2.24.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react": "^16.12.0",
|
||||
"react-autosize-textarea": "^7.0.0",
|
||||
"react-beautiful-dnd": "^13.0.0",
|
||||
"react-datepicker": "^2.14.1",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-hook-form": "^5.2.0",
|
||||
"react-router": "^5.1.2",
|
||||
|
@ -106,5 +106,9 @@ export default createGlobalStyle`
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
${mixin.placeholderColor(color.textLight)}
|
||||
`;
|
||||
|
0
web/src/Projects/Project/Lists/index.tsx
Normal file
0
web/src/Projects/Project/Lists/index.tsx
Normal file
@ -19,6 +19,10 @@ import Lists from 'shared/components/Lists';
|
||||
import QuickCardEditor from 'shared/components/QuickCardEditor';
|
||||
import PopupMenu from 'shared/components/PopupMenu';
|
||||
import ListActions from 'shared/components/ListActions';
|
||||
import Modal from 'shared/components/Modal';
|
||||
import TaskDetails from 'shared/components/TaskDetails';
|
||||
import MemberManager from 'shared/components/MemberManager';
|
||||
import { LabelsPopup } from 'shared/components/PopupMenu/PopupMenu.stories';
|
||||
|
||||
interface ColumnState {
|
||||
[key: string]: TaskGroup;
|
||||
@ -73,11 +77,16 @@ interface ProjectParams {
|
||||
const initialState: State = { tasks: {}, columns: {} };
|
||||
const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' };
|
||||
const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 };
|
||||
const initialMemberPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
|
||||
const initialLabelsPopupState = { taskID: '', isOpen: false, top: 0, left: 0 };
|
||||
const initialTaskDetailsState = { isOpen: false, taskID: '' };
|
||||
|
||||
const Project = () => {
|
||||
const { projectId } = useParams<ProjectParams>();
|
||||
const [listsData, setListsData] = useState(initialState);
|
||||
const [popupData, setPopupData] = useState(initialPopupState);
|
||||
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
|
||||
const [taskDetails, setTaskDetails] = useState(initialTaskDetailsState);
|
||||
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
|
||||
const [updateTaskLocation] = useUpdateTaskLocationMutation();
|
||||
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation();
|
||||
@ -256,6 +265,9 @@ const Project = () => {
|
||||
</TitleWrapper>
|
||||
<Board>
|
||||
<Lists
|
||||
onCardClick={task => {
|
||||
setTaskDetails({ isOpen: true, taskID: task.taskID });
|
||||
}}
|
||||
onExtraMenuOpen={(taskGroupID, pos, size) => {
|
||||
setPopupData({
|
||||
isOpen: true,
|
||||
@ -315,6 +327,57 @@ const Project = () => {
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
{memberPopupData.isOpen && (
|
||||
<PopupMenu
|
||||
title="Members"
|
||||
onClose={() => setMemberPopupData(initialMemberPopupState)}
|
||||
top={memberPopupData.top}
|
||||
left={memberPopupData.left}
|
||||
>
|
||||
<MemberManager
|
||||
availableMembers={[{ displayName: 'Jordan Knott', userID: '21345076-6423-4a00-a6bd-cd9f830e2764' }]}
|
||||
activeMembers={[]}
|
||||
onMemberChange={(member, isActive) => console.log(member, isActive)}
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
{taskDetails.isOpen && (
|
||||
<Modal
|
||||
width={1040}
|
||||
onClose={() => {
|
||||
setTaskDetails(initialTaskDetailsState);
|
||||
}}
|
||||
renderContent={() => {
|
||||
const task = listsData.tasks[taskDetails.taskID];
|
||||
return (
|
||||
<TaskDetails
|
||||
task={task}
|
||||
onTaskNameChange={(updatedTask, newName) => {
|
||||
updateTaskName({ variables: { taskID: updatedTask.taskID, name: newName } });
|
||||
}}
|
||||
onTaskDescriptionChange={(updatedTask, newDescription) => {
|
||||
console.log(updatedTask, newDescription);
|
||||
}}
|
||||
onDeleteTask={deletedTask => {
|
||||
setTaskDetails(initialTaskDetailsState);
|
||||
deleteTask({ variables: { taskID: deletedTask.taskID } });
|
||||
}}
|
||||
onCloseModal={() => setTaskDetails(initialTaskDetailsState)}
|
||||
onOpenAddMemberPopup={(task, bounds) => {
|
||||
console.log(task, bounds);
|
||||
setMemberPopupData({
|
||||
isOpen: true,
|
||||
taskID: task.taskID,
|
||||
top: bounds.position.top + bounds.size.height + 10,
|
||||
left: bounds.position.left,
|
||||
});
|
||||
}}
|
||||
onOpenAddLabelPopup={(task, bounds) => {}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
11
web/src/citadel.d.ts
vendored
11
web/src/citadel.d.ts
vendored
@ -16,6 +16,11 @@ type InnerTaskGroup = {
|
||||
position?: number;
|
||||
};
|
||||
|
||||
type TaskUser = {
|
||||
userID: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
type Task = {
|
||||
taskID: string;
|
||||
taskGroup: InnerTaskGroup;
|
||||
@ -23,6 +28,7 @@ type Task = {
|
||||
position: number;
|
||||
labels: Label[];
|
||||
description?: string;
|
||||
members?: Array<TaskUser>;
|
||||
};
|
||||
|
||||
type TaskGroup = {
|
||||
@ -85,3 +91,8 @@ type ElementSize = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
type ElementBounds = {
|
||||
size: ElementSize;
|
||||
position: ElementPosition;
|
||||
};
|
||||
|
@ -0,0 +1,32 @@
|
||||
import React, { useRef } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import DueDateManager from '.';
|
||||
|
||||
export default {
|
||||
component: DueDateManager,
|
||||
title: 'DueDateManager',
|
||||
parameters: {
|
||||
backgrounds: [
|
||||
{ name: 'gray', value: '#f8f8f8', default: true },
|
||||
{ name: 'white', value: '#ffffff' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
return (
|
||||
<DueDateManager
|
||||
task={{
|
||||
taskID: '1',
|
||||
taskGroup: { name: 'General', taskGroupID: '1' },
|
||||
name: 'Hello, world',
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
description: 'hello!',
|
||||
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||
}}
|
||||
onCancel={action('cancel')}
|
||||
onDueDateChange={action('due date change')}
|
||||
/>
|
||||
);
|
||||
};
|
45
web/src/shared/components/DueDateManager/Styles.ts
Normal file
45
web/src/shared/components/DueDateManager/Styles.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
display: flex
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const DueDatePickerWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const ConfirmAddDueDate = styled.div`
|
||||
background-color: #5aac44;
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
color: #fff;
|
||||
float: left;
|
||||
margin: 0 4px 0 0;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
padding: 6px 12px;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
export const CancelDueDate = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const ActionWrapper = styled.div`
|
||||
padding-top: 8px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
32
web/src/shared/components/DueDateManager/index.tsx
Normal file
32
web/src/shared/components/DueDateManager/index.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import { Cross } from 'shared/icons';
|
||||
|
||||
import { Wrapper, ActionWrapper, DueDatePickerWrapper, ConfirmAddDueDate, CancelDueDate } from './Styles';
|
||||
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
type DueDateManagerProps = {
|
||||
task: Task;
|
||||
onDueDateChange: (task: Task, newDueDate: Date) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
const DueDateManager: React.FC<DueDateManagerProps> = ({ task, onDueDateChange, onCancel }) => {
|
||||
const [startDate, setStartDate] = useState(new Date());
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<DueDatePickerWrapper>
|
||||
<DatePicker inline selected={startDate} onChange={date => setStartDate(date ?? new Date())} />
|
||||
</DueDatePickerWrapper>
|
||||
<ActionWrapper>
|
||||
<ConfirmAddDueDate onClick={() => onDueDateChange(task, startDate)}>Save</ConfirmAddDueDate>
|
||||
<CancelDueDate onClick={onCancel}>
|
||||
<Cross size={16} color="#c2c6dc" />
|
||||
</CancelDueDate>
|
||||
</ActionWrapper>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default DueDateManager;
|
@ -113,7 +113,7 @@ export const ListCards = styled.div`
|
||||
margin: 0 4px;
|
||||
padding: 0 4px;
|
||||
flex: 1 1 auto;
|
||||
min-height: 30px;
|
||||
min-height: 45px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
`;
|
||||
|
@ -64,13 +64,13 @@ const initialListsData = {
|
||||
|
||||
export const Default = () => {
|
||||
const [listsData, setListsData] = useState(initialListsData);
|
||||
const onCardDrop = (droppedTask: any) => {
|
||||
const onCardDrop = (droppedTask: Task) => {
|
||||
console.log(droppedTask);
|
||||
const newState = {
|
||||
...listsData,
|
||||
tasks: {
|
||||
...listsData.tasks,
|
||||
[droppedTask.taskGroupID]: droppedTask,
|
||||
[droppedTask.taskID]: droppedTask,
|
||||
},
|
||||
};
|
||||
console.log(newState);
|
||||
@ -91,6 +91,7 @@ export const Default = () => {
|
||||
return (
|
||||
<Lists
|
||||
{...listsData}
|
||||
onCardClick={action('card click')}
|
||||
onExtraMenuOpen={action('extra menu open')}
|
||||
onQuickEditorOpen={action('card composer open')}
|
||||
onCardDrop={onCardDrop}
|
||||
@ -201,6 +202,7 @@ export const ListsWithManyList = () => {
|
||||
return (
|
||||
<Lists
|
||||
{...listsData}
|
||||
onCardClick={action('card click')}
|
||||
onQuickEditorOpen={action('card composer open')}
|
||||
onCardCreate={action('card create')}
|
||||
onCardDrop={onCardDrop}
|
||||
|
@ -23,6 +23,7 @@ interface Tasks {
|
||||
type Props = {
|
||||
columns: Columns;
|
||||
tasks: Tasks;
|
||||
onCardClick: (task: Task) => void;
|
||||
onCardDrop: (task: Task) => void;
|
||||
onListDrop: (taskGroup: TaskGroup) => void;
|
||||
onCardCreate: (taskGroupID: string, name: string) => void;
|
||||
@ -34,6 +35,7 @@ type Props = {
|
||||
const Lists: React.FC<Props> = ({
|
||||
columns,
|
||||
tasks,
|
||||
onCardClick,
|
||||
onCardDrop,
|
||||
onListDrop,
|
||||
onCardCreate,
|
||||
@ -72,7 +74,6 @@ const Lists: React.FC<Props> = ({
|
||||
|
||||
console.log(beforeDropDraggables);
|
||||
console.log(destination);
|
||||
console.log(droppedDraggable);
|
||||
const afterDropDraggables = getAfterDropDraggableList(
|
||||
beforeDropDraggables,
|
||||
droppedDraggable,
|
||||
@ -85,16 +86,20 @@ const Lists: React.FC<Props> = ({
|
||||
|
||||
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,
|
||||
taskGroup: {
|
||||
taskGroupID: destination.droppableId,
|
||||
},
|
||||
};
|
||||
onCardDrop(newCard);
|
||||
}
|
||||
@ -121,22 +126,22 @@ const Lists: React.FC<Props> = ({
|
||||
return (
|
||||
<Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}>
|
||||
{columnDragProvided => (
|
||||
<Droppable type="tasks" droppableId={column.taskGroupID}>
|
||||
{(columnDropProvided, snapshot) => (
|
||||
<List
|
||||
id={column.taskGroupID}
|
||||
name={column.name}
|
||||
key={column.taskGroupID}
|
||||
onOpenComposer={id => setCurrentComposer(id)}
|
||||
isComposerOpen={currentComposer === column.taskGroupID}
|
||||
onSaveName={name => console.log(name)}
|
||||
index={index}
|
||||
tasks={columnCards}
|
||||
ref={columnDragProvided.innerRef}
|
||||
wrapperProps={columnDragProvided.draggableProps}
|
||||
headerProps={columnDragProvided.dragHandleProps}
|
||||
onExtraMenuOpen={onExtraMenuOpen}
|
||||
id={column.taskGroupID}
|
||||
key={column.taskGroupID}
|
||||
index={index}
|
||||
>
|
||||
<Droppable type="tasks" droppableId={column.taskGroupID}>
|
||||
{columnDropProvided => (
|
||||
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
||||
{columnCards.map((task: Task, taskIndex: any) => {
|
||||
return (
|
||||
@ -154,7 +159,7 @@ const Lists: React.FC<Props> = ({
|
||||
description=""
|
||||
title={task.name}
|
||||
labels={task.labels}
|
||||
onClick={e => console.log(e)}
|
||||
onClick={() => onCardClick(task)}
|
||||
onContextMenu={onQuickEditorOpen}
|
||||
/>
|
||||
);
|
||||
@ -163,7 +168,6 @@ const Lists: React.FC<Props> = ({
|
||||
);
|
||||
})}
|
||||
{columnDropProvided.placeholder}
|
||||
|
||||
{currentComposer === column.taskGroupID && (
|
||||
<CardComposer
|
||||
onClose={() => {
|
||||
@ -176,9 +180,9 @@ const Lists: React.FC<Props> = ({
|
||||
/>
|
||||
)}
|
||||
</ListCards>
|
||||
</List>
|
||||
)}
|
||||
</Droppable>
|
||||
</List>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
|
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import MemberManager from '.';
|
||||
|
||||
export default {
|
||||
component: MemberManager,
|
||||
title: 'MemberManager',
|
||||
parameters: {
|
||||
backgrounds: [
|
||||
{ name: 'white', value: '#ffffff', default: true },
|
||||
{ name: 'gray', value: '#f8f8f8' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
return <MemberManager availableMembers={[]} activeMembers={[]} onMemberChange={action('member change')} />;
|
||||
};
|
81
web/src/shared/components/MemberManager/Styles.ts
Normal file
81
web/src/shared/components/MemberManager/Styles.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import styled from 'styled-components';
|
||||
import TextareaAutosize from 'react-autosize-textarea/lib';
|
||||
|
||||
export const MemberManagerWrapper = styled.div``;
|
||||
|
||||
export const MemberManagerSearchWrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const MemberManagerSearch = styled(TextareaAutosize)`
|
||||
margin: 4px 0 12px;
|
||||
width: 100%;
|
||||
background-color: #ebecf0;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 2px #dfe1e6;
|
||||
line-height: 20px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
color: #172b4d;
|
||||
`;
|
||||
|
||||
export const BoardMembersLabel = styled.h4`
|
||||
color: #5e6c84;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
line-height: 16px;
|
||||
text-transform: uppercase;
|
||||
`;
|
||||
|
||||
export const BoardMembersList = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
`;
|
||||
|
||||
export const BoardMembersListItem = styled.li``;
|
||||
|
||||
export const BoardMemberListItemContent = styled.div`
|
||||
background-color: rgba(9, 30, 66, 0.04);
|
||||
padding-right: 28px;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
padding: 4px;
|
||||
margin-bottom: 2px;
|
||||
color: #172b4d;
|
||||
`;
|
||||
|
||||
export const ProfileIcon = styled.div`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
background: rgb(115, 103, 240);
|
||||
cursor: pointer;
|
||||
margin-right: 6px;
|
||||
`;
|
||||
|
||||
export const MemberName = styled.span`
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
export const ActiveIconWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 11px;
|
||||
`;
|
73
web/src/shared/components/MemberManager/index.tsx
Normal file
73
web/src/shared/components/MemberManager/index.tsx
Normal file
@ -0,0 +1,73 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
MemberName,
|
||||
ProfileIcon,
|
||||
MemberManagerWrapper,
|
||||
MemberManagerSearchWrapper,
|
||||
MemberManagerSearch,
|
||||
BoardMembersLabel,
|
||||
BoardMembersList,
|
||||
BoardMembersListItem,
|
||||
BoardMemberListItemContent,
|
||||
ActiveIconWrapper,
|
||||
} from './Styles';
|
||||
import { Checkmark } from 'shared/icons';
|
||||
|
||||
type MemberManagerProps = {
|
||||
availableMembers: Array<TaskUser>;
|
||||
activeMembers: Array<TaskUser>;
|
||||
onMemberChange: (member: TaskUser, isActive: boolean) => void;
|
||||
};
|
||||
const MemberManager: React.FC<MemberManagerProps> = ({
|
||||
availableMembers,
|
||||
activeMembers: initialActiveMembers,
|
||||
onMemberChange,
|
||||
}) => {
|
||||
const [activeMembers, setActiveMembers] = useState(initialActiveMembers);
|
||||
const [currentSearch, setCurrentSearch] = useState('');
|
||||
return (
|
||||
<MemberManagerWrapper>
|
||||
<MemberManagerSearchWrapper>
|
||||
<MemberManagerSearch
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setCurrentSearch(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</MemberManagerSearchWrapper>
|
||||
<BoardMembersLabel>Board Members</BoardMembersLabel>
|
||||
<BoardMembersList>
|
||||
{availableMembers
|
||||
.filter(
|
||||
member => currentSearch === '' || member.displayName.toLowerCase().startsWith(currentSearch.toLowerCase()),
|
||||
)
|
||||
.map(member => {
|
||||
return (
|
||||
<BoardMembersListItem>
|
||||
<BoardMemberListItemContent
|
||||
onClick={() => {
|
||||
const isActive = activeMembers.findIndex(m => m.userID === member.userID) !== -1;
|
||||
if (isActive) {
|
||||
setActiveMembers(activeMembers.filter(m => m.userID !== member.userID));
|
||||
} else {
|
||||
setActiveMembers([...activeMembers, member]);
|
||||
}
|
||||
onMemberChange(member, !isActive);
|
||||
}}
|
||||
>
|
||||
<ProfileIcon>JK</ProfileIcon>
|
||||
<MemberName>{member.displayName}</MemberName>
|
||||
{activeMembers.findIndex(m => m.userID === member.userID) !== -1 && (
|
||||
<ActiveIconWrapper>
|
||||
<Checkmark size={16} color="#42526e" />
|
||||
</ActiveIconWrapper>
|
||||
)}
|
||||
</BoardMemberListItemContent>
|
||||
</BoardMembersListItem>
|
||||
);
|
||||
})}
|
||||
</BoardMembersList>
|
||||
</MemberManagerWrapper>
|
||||
);
|
||||
};
|
||||
export default MemberManager;
|
@ -14,7 +14,7 @@ export const ScrollOverlay = styled.div`
|
||||
|
||||
export const ClickableOverlay = styled.div`
|
||||
min-height: 100%;
|
||||
background: rgba(9, 30, 66, 0.54);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -25,7 +25,7 @@ export const StyledModal = styled.div<{ width: number }>`
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
background: #262c49;
|
||||
max-width: ${props => props.width}px;
|
||||
vertical-align: middle;
|
||||
border-radius: 3px;
|
||||
|
@ -4,8 +4,12 @@ import LabelColors from 'shared/constants/labelColors';
|
||||
import LabelManager from 'shared/components/PopupMenu/LabelManager';
|
||||
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 PopupMenu from '.';
|
||||
import NormalizeStyles from 'App/NormalizeStyles';
|
||||
import BaseStyles from 'App/BaseStyles';
|
||||
|
||||
export default {
|
||||
component: PopupMenu,
|
||||
@ -99,3 +103,93 @@ export const ListActionsPopup = () => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const MemberManagerPopup = () => {
|
||||
const $buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const [popupData, setPopupData] = useState(initalState);
|
||||
return (
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
{popupData.isOpen && (
|
||||
<PopupMenu title="Members" top={popupData.top} onClose={() => setPopupData(initalState)} left={popupData.left}>
|
||||
<MemberManager
|
||||
availableMembers={[{ userID: '1', displayName: 'Jordan Knott' }]}
|
||||
activeMembers={[]}
|
||||
onMemberChange={action('member change')}
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
width: '60px',
|
||||
textAlign: 'center',
|
||||
margin: '25px auto',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const DueDateManagerPopup = () => {
|
||||
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}>
|
||||
<DueDateManager
|
||||
task={{
|
||||
taskID: '1',
|
||||
taskGroup: { name: 'General', taskGroupID: '1' },
|
||||
name: 'Hello, world',
|
||||
position: 1,
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
description: 'hello!',
|
||||
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||
}}
|
||||
onCancel={action('cancel')}
|
||||
onDueDateChange={action('due date change')}
|
||||
/>
|
||||
</PopupMenu>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
width: '60px',
|
||||
textAlign: 'center',
|
||||
margin: '25px auto',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -8,10 +8,9 @@ export const Container = styled.div<{ top: number; left: number; ref: any }>`
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 8px 16px -4px rgba(9, 30, 66, 0.25), 0 0 0 1px rgba(9, 30, 66, 0.08);
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 304px;
|
||||
z-index: 70;
|
||||
z-index: 100000000000;
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
|
@ -1,22 +1,38 @@
|
||||
import styled from 'styled-components';
|
||||
import TextareaAutosize from 'react-autosize-textarea/lib';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
|
||||
export const TaskHeader = styled.div`
|
||||
padding: 21px 30px 0px;
|
||||
margin-right: 70px;
|
||||
display: flex;
|
||||
-webkit-box-pack: justify;
|
||||
justify-content: space-between;
|
||||
padding: 21px 18px 0px;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const TaskMeta = styled.div`
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
`;
|
||||
|
||||
export const TaskGroupLabel = styled.span`
|
||||
color: #c2c6dc;
|
||||
font-size: 14px;
|
||||
`;
|
||||
export const TaskGroupLabelName = styled.span`
|
||||
color: #c2c6dc;
|
||||
text-decoration: underline;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
export const TaskActions = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 21px 18px 0px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
@ -26,19 +42,8 @@ export const TaskAction = styled.button`
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 14.5px;
|
||||
color: rgb(66, 82, 110);
|
||||
font-family: CircularStdBook;
|
||||
font-weight: normal;
|
||||
padding: 0px 9px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.1s ease 0s;
|
||||
background: rgb(255, 255, 255);
|
||||
`;
|
||||
|
||||
export const TaskDetailsWrapper = styled.div`
|
||||
@ -53,13 +58,12 @@ export const TaskDetailsContent = styled.div`
|
||||
|
||||
export const TaskDetailsSidebar = styled.div`
|
||||
width: 35%;
|
||||
padding-top: 5px;
|
||||
`;
|
||||
|
||||
export const TaskDetailsTitleWrapper = styled.div`
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
margin: 18px 0px 0px -8px;
|
||||
margin: 0 0 0 -8px;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
@ -70,8 +74,8 @@ export const TaskDetailsTitle = styled(TextareaAutosize)`
|
||||
font-size: 24px;
|
||||
font-family: 'Droid Sans';
|
||||
font-weight: 700;
|
||||
padding: 7px 7px 8px;
|
||||
background: rgb(255, 255, 255);
|
||||
padding: 4px;
|
||||
background: #262c49;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
@ -79,28 +83,22 @@ export const TaskDetailsTitle = styled(TextareaAutosize)`
|
||||
transition: background 0.1s ease 0s;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
color: rgb(23, 43, 77);
|
||||
&:hover {
|
||||
background: rgb(235, 236, 240);
|
||||
}
|
||||
color: #c2c6dc;
|
||||
&:focus {
|
||||
box-shadow: rgb(76, 154, 255) 0px 0px 0px 1px;
|
||||
background: rgb(255, 255, 255);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: rgb(76, 154, 255);
|
||||
border-image: initial;
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsLabel = styled.div`
|
||||
padding: 20px 0px 12px;
|
||||
padding: 24px 0px 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #c2c6dc;
|
||||
`;
|
||||
|
||||
export const TaskDetailsAddDetailsButton = styled.div`
|
||||
background-color: rgba(9, 30, 66, 0.04);
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
@ -110,8 +108,9 @@ export const TaskDetailsAddDetailsButton = styled.div`
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: #c2c6dc;
|
||||
&:hover {
|
||||
background-color: rgba(9, 30, 66, 0.08);
|
||||
background: ${mixin.darken('#262c49', 0.25)};
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
@ -128,9 +127,9 @@ export const TaskDetailsEditorWrapper = styled.div`
|
||||
export const TaskDetailsEditor = styled(TextareaAutosize)`
|
||||
width: 100%;
|
||||
min-height: 108px;
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
border-color: rgba(9, 30, 66, 0.13);
|
||||
color: #c2c6dc;
|
||||
background: #262c49;
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
border-radius: 3px;
|
||||
line-height: 20px;
|
||||
padding: 8px 12px;
|
||||
@ -138,15 +137,15 @@ export const TaskDetailsEditor = styled(TextareaAutosize)`
|
||||
border: none;
|
||||
|
||||
&:focus {
|
||||
background: #fff;
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 2px #0079bf;
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
background: ${mixin.darken('#262c49', 0.05)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsMarkdown = styled.div`
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
color: #c2c6dc;
|
||||
`;
|
||||
|
||||
export const TaskDetailsControls = styled.div`
|
||||
@ -179,3 +178,106 @@ export const CancelEdit = styled.div`
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const TaskDetailSectionTitle = styled.div`
|
||||
text-transform: uppercase;
|
||||
color: #c2c6dc;
|
||||
font-size: 12.5px;
|
||||
font-weight: 600;
|
||||
margin: 24px 0px 5px;
|
||||
`;
|
||||
|
||||
export const TaskDetailAssignees = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const TaskDetailAssignee = styled.div`
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
margin-right: 4px;
|
||||
`;
|
||||
export const ProfileIcon = styled.div`
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
background: rgb(115, 103, 240);
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const TaskDetailsAddMember = styled.div`
|
||||
border-radius: 100%;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsAddMemberIcon = styled.div`
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const TaskDetailLabels = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
export const TaskDetailLabel = styled.div`
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
background-color: #00c2e0;
|
||||
color: #fff;
|
||||
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
float: left;
|
||||
font-weight: 600;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 4px 4px 0;
|
||||
min-width: 40px;
|
||||
padding: 0 12px;
|
||||
width: auto;
|
||||
`;
|
||||
|
||||
export const TaskDetailsAddLabel = styled.div`
|
||||
border-radius: 3px;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsAddLabelIcon = styled.div`
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const NoDueDateLabel = styled.span`
|
||||
color: rgb(137, 147, 164);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
@ -30,15 +30,19 @@ export const Default = () => {
|
||||
<TaskDetails
|
||||
task={{
|
||||
taskID: '1',
|
||||
taskGroup: { taskGroupID: '1' },
|
||||
taskGroup: { name: 'General', taskGroupID: '1' },
|
||||
name: 'Hello, world',
|
||||
position: 1,
|
||||
labels: [],
|
||||
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||
description,
|
||||
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||
}}
|
||||
onTaskNameChange={action('task name change')}
|
||||
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
|
||||
onDeleteTask={action('delete task')}
|
||||
onCloseModal={action('close modal')}
|
||||
onOpenAddMemberPopup={action('open add member popup')}
|
||||
onOpenAddLabelPopup={action('open add label popup')}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
@ -1,12 +1,19 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Bin, Cross } from 'shared/icons';
|
||||
import { Bin, Cross, Plus } from 'shared/icons';
|
||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||
|
||||
import {
|
||||
NoDueDateLabel,
|
||||
TaskDetailsAddMember,
|
||||
TaskGroupLabel,
|
||||
TaskGroupLabelName,
|
||||
TaskActions,
|
||||
TaskDetailsAddLabel,
|
||||
TaskDetailsAddLabelIcon,
|
||||
TaskAction,
|
||||
TaskMeta,
|
||||
TaskHeader,
|
||||
ProfileIcon,
|
||||
TaskDetailsContent,
|
||||
TaskDetailsWrapper,
|
||||
TaskDetailsSidebar,
|
||||
@ -20,8 +27,16 @@ import {
|
||||
TaskDetailsControls,
|
||||
ConfirmSave,
|
||||
CancelEdit,
|
||||
TaskDetailSectionTitle,
|
||||
TaskDetailLabel,
|
||||
TaskDetailLabels,
|
||||
TaskDetailAssignee,
|
||||
TaskDetailAssignees,
|
||||
TaskDetailsAddMemberIcon,
|
||||
} from './Styles';
|
||||
|
||||
import convertDivElementRefToBounds from 'shared/utils/boundingRect';
|
||||
|
||||
type TaskContentProps = {
|
||||
onEditContent: () => void;
|
||||
description: string;
|
||||
@ -70,7 +85,7 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
|
||||
<TaskDetailsControls>
|
||||
<ConfirmSave onClick={handleOutsideClick}>Save</ConfirmSave>
|
||||
<CancelEdit onClick={onCancel}>
|
||||
<Cross size={16} />
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</CancelEdit>
|
||||
</TaskDetailsControls>
|
||||
</TaskDetailsEditorWrapper>
|
||||
@ -79,37 +94,81 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
|
||||
|
||||
type TaskDetailsProps = {
|
||||
task: Task;
|
||||
onTaskNameChange: (task: Task, newName: string) => void;
|
||||
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
||||
onDeleteTask: (task: Task) => void;
|
||||
onCloseModal: () => void;
|
||||
onOpenAddMemberPopup: (task: Task, bounds: ElementBounds) => void;
|
||||
onOpenAddLabelPopup: (task: Task, bounds: ElementBounds) => void;
|
||||
};
|
||||
|
||||
const TaskDetails: React.FC<TaskDetailsProps> = ({ task, onTaskDescriptionChange, onDeleteTask, onCloseModal }) => {
|
||||
const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
task,
|
||||
onTaskNameChange,
|
||||
onTaskDescriptionChange,
|
||||
onDeleteTask,
|
||||
onCloseModal,
|
||||
onOpenAddMemberPopup,
|
||||
onOpenAddLabelPopup,
|
||||
}) => {
|
||||
const [editorOpen, setEditorOpen] = useState(false);
|
||||
const [taskName, setTaskName] = useState(task.name);
|
||||
const handleClick = () => {
|
||||
setEditorOpen(!editorOpen);
|
||||
};
|
||||
const handleDeleteTask = () => {
|
||||
onDeleteTask(task);
|
||||
};
|
||||
const onKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onTaskNameChange(task, taskName);
|
||||
}
|
||||
};
|
||||
const $addMemberRef = useRef<HTMLDivElement>(null);
|
||||
const onAddMember = () => {
|
||||
console.log('beep!');
|
||||
const bounds = convertDivElementRefToBounds($addMemberRef);
|
||||
console.log(bounds);
|
||||
if (bounds) {
|
||||
onOpenAddMemberPopup(task, bounds);
|
||||
}
|
||||
};
|
||||
const $addLabelRef = useRef<HTMLDivElement>(null);
|
||||
const onAddLabel = () => {
|
||||
const bounds = convertDivElementRefToBounds($addLabelRef);
|
||||
if (bounds) {
|
||||
onOpenAddLabelPopup(task, bounds);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TaskHeader>
|
||||
<TaskMeta />
|
||||
<TaskActions>
|
||||
<TaskAction onClick={handleDeleteTask}>
|
||||
<Bin size={20} />
|
||||
<Bin size={20} color="#c2c6dc" />
|
||||
</TaskAction>
|
||||
<TaskAction onClick={onCloseModal}>
|
||||
<Cross size={20} />
|
||||
<Cross size={20} color="#c2c6dc" />
|
||||
</TaskAction>
|
||||
</TaskActions>
|
||||
<TaskHeader>
|
||||
<TaskDetailsTitleWrapper>
|
||||
<TaskDetailsTitle
|
||||
value={taskName}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setTaskName(e.currentTarget.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</TaskDetailsTitleWrapper>
|
||||
<TaskMeta>
|
||||
{task.taskGroup.name && (
|
||||
<TaskGroupLabel>
|
||||
in list <TaskGroupLabelName>{task.taskGroup.name}</TaskGroupLabelName>
|
||||
</TaskGroupLabel>
|
||||
)}
|
||||
</TaskMeta>
|
||||
</TaskHeader>
|
||||
<TaskDetailsWrapper>
|
||||
<TaskDetailsContent>
|
||||
<TaskDetailsTitleWrapper>
|
||||
<TaskDetailsTitle value="Hello darkness my old friend" />
|
||||
</TaskDetailsTitleWrapper>
|
||||
<TaskDetailsLabel>Description</TaskDetailsLabel>
|
||||
{editorOpen ? (
|
||||
<DetailsEditor
|
||||
@ -126,7 +185,38 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({ task, onTaskDescriptionChange
|
||||
<TaskContent description={task.description ?? ''} onEditContent={handleClick} />
|
||||
)}
|
||||
</TaskDetailsContent>
|
||||
<TaskDetailsSidebar />
|
||||
<TaskDetailsSidebar>
|
||||
<TaskDetailSectionTitle>Assignees</TaskDetailSectionTitle>
|
||||
<TaskDetailAssignees>
|
||||
{task.members &&
|
||||
task.members.map(member => {
|
||||
const initials = 'JK';
|
||||
return (
|
||||
<TaskDetailAssignee key={member.userID}>
|
||||
<ProfileIcon>{initials}</ProfileIcon>
|
||||
</TaskDetailAssignee>
|
||||
);
|
||||
})}
|
||||
<TaskDetailsAddMember ref={$addMemberRef} onClick={onAddMember}>
|
||||
<TaskDetailsAddMemberIcon>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</TaskDetailsAddMemberIcon>
|
||||
</TaskDetailsAddMember>
|
||||
</TaskDetailAssignees>
|
||||
<TaskDetailSectionTitle>Labels</TaskDetailSectionTitle>
|
||||
<TaskDetailLabels>
|
||||
{task.labels.map(label => {
|
||||
return <TaskDetailLabel>{label.name}</TaskDetailLabel>;
|
||||
})}
|
||||
<TaskDetailsAddLabel ref={$addLabelRef} onClick={onAddLabel}>
|
||||
<TaskDetailsAddLabelIcon>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</TaskDetailsAddLabelIcon>
|
||||
</TaskDetailsAddLabel>
|
||||
</TaskDetailLabels>
|
||||
<TaskDetailSectionTitle>Due Date</TaskDetailSectionTitle>
|
||||
<NoDueDateLabel>No due date</NoDueDateLabel>
|
||||
</TaskDetailsSidebar>
|
||||
</TaskDetailsWrapper>
|
||||
</>
|
||||
);
|
||||
|
104
web/src/shared/undraw/NoData.tsx
Normal file
104
web/src/shared/undraw/NoData.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
const AccessAccount = ({ width, height }: Props) => {
|
||||
return (
|
||||
<svg data-name="Layer 1" width={width} height={height} viewBox="0 0 820.16 780.81">
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1="539.63"
|
||||
y1="734.6"
|
||||
x2="539.63"
|
||||
y2="151.19"
|
||||
gradientTransform="translate(-3.62 1.57)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset="0" stop-color="gray" stop-opacity=".25" />
|
||||
<stop offset=".54" stop-color="gray" stop-opacity=".12" />
|
||||
<stop offset="1" stop-color="gray" stop-opacity=".1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="540.17"
|
||||
y1="180.2"
|
||||
x2="540.17"
|
||||
y2="130.75"
|
||||
gradientTransform="translate(-63.92 7.85)"
|
||||
/>
|
||||
<linearGradient
|
||||
id="c"
|
||||
x1="540.17"
|
||||
y1="140.86"
|
||||
x2="540.17"
|
||||
y2="82.43"
|
||||
gradientTransform="rotate(-12.11 545.066 460.65)"
|
||||
/>
|
||||
<linearGradient id="d" x1="476.4" y1="710.53" x2="476.4" y2="127.12" />
|
||||
<linearGradient id="e" x1="476.94" y1="156.13" x2="476.94" y2="106.68" />
|
||||
<linearGradient id="f" x1="666.86" y1="176.39" x2="666.86" y2="117.95" />
|
||||
</defs>
|
||||
<path fill="#e0e0e0" d="M69.12 135.49l427.295-91.682L623.09 634.19l-427.295 91.682z" />
|
||||
<path
|
||||
transform="rotate(-12.11 160.03 1309.797)"
|
||||
fill="url(#a)"
|
||||
d="M324.89 152.76h422.25v583.41H324.89z"
|
||||
opacity=".5"
|
||||
/>
|
||||
<path fill="#fafafa" d="M84.639 146.993L486.98 60.665l119.69 557.824-402.344 86.328z" />
|
||||
<path transform="rotate(-12.11 100.28 1028.707)" fill="url(#b)" d="M374.18 138.6h204.14v49.45H374.18z" />
|
||||
<path
|
||||
d="M460.93 91.9c-15.41 3.31-25.16 18.78-21.77 34.55s18.62 25.89 34 22.58 25.16-18.78 21.77-34.55-18.59-25.89-34-22.58zm9.67 45.1a16.86 16.86 0 1112.56-20 16.66 16.66 0 01-12.56 20z"
|
||||
transform="translate(-189.92 -59.59)"
|
||||
fill="url(#c)"
|
||||
/>
|
||||
<path fill="#6c63ff" d="M183.007 98.422L378.4 56.498l9.917 46.218-195.393 41.924z" />
|
||||
<path
|
||||
d="M271.01 32.31a27.93 27.93 0 1033.17 21.45 27.93 27.93 0 00-33.17-21.45zm9.24 43.1a16.12 16.12 0 1112.38-19.14 16.12 16.12 0 01-12.38 19.14z"
|
||||
fill="#6c63ff"
|
||||
/>
|
||||
<path fill="#e0e0e0" d="M257.89 116.91h437.02v603.82H257.89z" />
|
||||
<path fill="url(#d)" d="M265.28 127.12h422.25v583.41H265.28z" opacity=".5" />
|
||||
<path fill="#fff" d="M270.65 131.42h411.5v570.52h-411.5z" />
|
||||
<path fill="url(#e)" d="M374.87 106.68h204.14v49.45H374.87z" />
|
||||
<path
|
||||
d="M666.86 118c-15.76 0-28.54 13.08-28.54 29.22s12.78 29.22 28.54 29.22 28.54-13.08 28.54-29.22S682.62 118 666.86 118zm0 46.08a16.86 16.86 0 1116.46-16.86A16.66 16.66 0 01666.86 164z"
|
||||
transform="translate(-189.92 -59.59)"
|
||||
fill="url(#f)"
|
||||
/>
|
||||
<path fill="#6c63ff" d="M377.02 104.56h199.84v47.27H377.02z" />
|
||||
<path
|
||||
d="M476.94 58.41a27.93 27.93 0 1027.93 27.93 27.93 27.93 0 00-27.93-27.93zm0 44.05a16.12 16.12 0 1116.14-16.16 16.12 16.12 0 01-16.14 16.11z"
|
||||
fill="#6c63ff"
|
||||
/>
|
||||
<g opacity=".5" fill="#47e6b1">
|
||||
<path d="M15.27 737.05h3.76v21.33h-3.76z" />
|
||||
<path d="M27.82 745.84v3.76H6.49v-3.76z" />
|
||||
</g>
|
||||
<g opacity=".5" fill="#47e6b1">
|
||||
<path d="M451.49 0h3.76v21.33h-3.76z" />
|
||||
<path d="M464.04 8.78v3.76h-21.33V8.78z" />
|
||||
</g>
|
||||
<path
|
||||
d="M771.08 772.56a4.61 4.61 0 01-2.57-5.57 2.22 2.22 0 00.1-.51 2.31 2.31 0 00-4.15-1.53 2.22 2.22 0 00-.26.45 4.61 4.61 0 01-5.57 2.57 2.22 2.22 0 00-.51-.1 2.31 2.31 0 00-1.53 4.15 2.22 2.22 0 00.45.26 4.61 4.61 0 012.57 5.57 2.22 2.22 0 00-.1.51 2.31 2.31 0 004.15 1.53 2.22 2.22 0 00.26-.45 4.61 4.61 0 015.57-2.57 2.22 2.22 0 00.51.1 2.31 2.31 0 001.53-4.15 2.22 2.22 0 00-.45-.26z"
|
||||
fill="#4d8af0"
|
||||
opacity=".5"
|
||||
/>
|
||||
<path
|
||||
d="M136.67 567.5a4.61 4.61 0 01-2.57-5.57 2.22 2.22 0 00.1-.51 2.31 2.31 0 00-4.15-1.53 2.22 2.22 0 00-.26.45 4.61 4.61 0 01-5.57 2.57 2.22 2.22 0 00-.51-.1 2.31 2.31 0 00-1.53 4.15 2.22 2.22 0 00.45.26 4.61 4.61 0 012.57 5.57 2.22 2.22 0 00-.1.51 2.31 2.31 0 004.15 1.53 2.22 2.22 0 00.26-.45 4.61 4.61 0 015.57-2.57 2.22 2.22 0 00.51.1 2.31 2.31 0 001.53-4.15 2.22 2.22 0 00-.45-.26zM665.08 68.18a4.61 4.61 0 01-2.57-5.57 2.22 2.22 0 00.1-.51 2.31 2.31 0 00-4.15-1.53 2.22 2.22 0 00-.26.45 4.61 4.61 0 01-5.57 2.57 2.22 2.22 0 00-.51-.1 2.31 2.31 0 00-1.53 4.15 2.22 2.22 0 00.45.26 4.61 4.61 0 012.57 5.57 2.22 2.22 0 00-.1.51 2.31 2.31 0 004.15 1.53 2.22 2.22 0 00.26-.45 4.61 4.61 0 015.57-2.57 2.22 2.22 0 00.51.1 2.31 2.31 0 001.53-4.15 2.22 2.22 0 00-.45-.26z"
|
||||
fill="#fdd835"
|
||||
opacity=".5"
|
||||
/>
|
||||
<circle cx="812.64" cy="314.47" r="7.53" fill="#f55f44" opacity=".5" />
|
||||
<circle cx="230.73" cy="746.65" r="7.53" fill="#f55f44" opacity=".5" />
|
||||
<circle cx="735.31" cy="477.23" r="7.53" fill="#f55f44" opacity=".5" />
|
||||
<circle cx="87.14" cy="96.35" r="7.53" fill="#4d8af0" opacity=".5" />
|
||||
<circle cx="7.53" cy="301.76" r="7.53" fill="#47e6b1" opacity=".5" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export default AccessAccount;
|
20
web/src/shared/utils/boundingRect.ts
Normal file
20
web/src/shared/utils/boundingRect.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export const convertDivElementRefToBounds = ($ref: React.RefObject<HTMLDivElement>) => {
|
||||
if ($ref && $ref.current) {
|
||||
const bounds = $ref.current.getBoundingClientRect();
|
||||
return {
|
||||
size: {
|
||||
width: bounds.width,
|
||||
height: bounds.height,
|
||||
},
|
||||
position: {
|
||||
left: bounds.left,
|
||||
right: bounds.right,
|
||||
top: bounds.top,
|
||||
bottom: bounds.bottom,
|
||||
},
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default convertDivElementRefToBounds;
|
@ -9,7 +9,9 @@ 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;
|
||||
};
|
||||
|
||||
@ -56,6 +58,7 @@ 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;
|
||||
};
|
||||
|
||||
|
@ -3179,6 +3179,15 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-datepicker@^2.11.0":
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-2.11.0.tgz#aa6faa66de17b0ff96bc0af9d5d2506d5dd99703"
|
||||
integrity sha512-eagG8BE3TFgPYyZb2/hG4+2delLH9z/4OWzT7wuTCKLHDDXIXgvkb2O2cW8q4/wuqnTnMxo+vl3vgPTENkofzw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
date-fns "^2.0.1"
|
||||
popper.js "^1.14.1"
|
||||
|
||||
"@types/react-dom@*", "@types/react-dom@^16.9.5":
|
||||
version "16.9.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7"
|
||||
@ -5480,7 +5489,7 @@ class-utils@^0.3.5:
|
||||
isobject "^3.0.0"
|
||||
static-extend "^0.1.1"
|
||||
|
||||
classnames@^2.2.5:
|
||||
classnames@^2.2.5, classnames@^2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||
@ -6319,6 +6328,11 @@ date-fns@^1.27.2:
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
||||
|
||||
date-fns@^2.0.1:
|
||||
version "2.12.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.12.0.tgz#01754c8a2f3368fc1119cf4625c3dad8c1845ee6"
|
||||
integrity sha512-qJgn99xxKnFgB1qL4jpxU7Q2t0LOn1p8KMIveef3UZD7kqjT3tpFNNdXJelEHhE+rUgffriXriw/sOSU+cS1Hw==
|
||||
|
||||
de-indent@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||
@ -12032,7 +12046,7 @@ polished@^3.3.1:
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.6.3"
|
||||
|
||||
popper.js@^1.14.4, popper.js@^1.14.7:
|
||||
popper.js@^1.14.1, popper.js@^1.14.4, popper.js@^1.14.7:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
|
||||
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
||||
@ -13250,6 +13264,17 @@ react-color@^2.17.0:
|
||||
reactcss "^1.2.0"
|
||||
tinycolor2 "^1.4.1"
|
||||
|
||||
react-datepicker@^2.14.1:
|
||||
version "2.14.1"
|
||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-2.14.1.tgz#83463beb85235a575475955f554290a95f89c65b"
|
||||
integrity sha512-8eWHvrjXfKVkt5rERXC6/c/eEdcE2stIsl+QmTO5Efgpacf8MOCyVpBisJLVLDYjVlENczhOcRlIzvraODHKxA==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
date-fns "^2.0.1"
|
||||
prop-types "^15.7.2"
|
||||
react-onclickoutside "^6.9.0"
|
||||
react-popper "^1.3.4"
|
||||
|
||||
react-dev-utils@^10.2.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.0.tgz#b11cc48aa2be2502fb3c27a50d1dfa95cfa9dfe0"
|
||||
@ -13442,6 +13467,11 @@ react-lifecycles-compat@^3.0.4:
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-onclickoutside@^6.9.0:
|
||||
version "6.9.0"
|
||||
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f"
|
||||
integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A==
|
||||
|
||||
react-popper-tooltip@^2.8.3:
|
||||
version "2.10.1"
|
||||
resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-2.10.1.tgz#e10875f31916297c694d64a677d6f8fa0a48b4d1"
|
||||
@ -13450,7 +13480,7 @@ react-popper-tooltip@^2.8.3:
|
||||
"@babel/runtime" "^7.7.4"
|
||||
react-popper "^1.3.6"
|
||||
|
||||
react-popper@^1.3.6:
|
||||
react-popper@^1.3.4, react-popper@^1.3.6:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"
|
||||
integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==
|
||||
|
Loading…
Reference in New Issue
Block a user