feature: update task details design
This commit is contained in:
parent
c250ce574b
commit
16eb9e165f
@ -24,6 +24,7 @@
|
|||||||
"@types/node": "^12.0.0",
|
"@types/node": "^12.0.0",
|
||||||
"@types/react": "^16.9.21",
|
"@types/react": "^16.9.21",
|
||||||
"@types/react-beautiful-dnd": "^12.1.1",
|
"@types/react-beautiful-dnd": "^12.1.1",
|
||||||
|
"@types/react-datepicker": "^2.11.0",
|
||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/react-router": "^5.1.4",
|
"@types/react-router": "^5.1.4",
|
||||||
"@types/react-router-dom": "^5.1.3",
|
"@types/react-router-dom": "^5.1.3",
|
||||||
@ -41,10 +42,12 @@
|
|||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
"immer": "^6.0.3",
|
"immer": "^6.0.3",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
"moment": "^2.24.0",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-autosize-textarea": "^7.0.0",
|
"react-autosize-textarea": "^7.0.0",
|
||||||
"react-beautiful-dnd": "^13.0.0",
|
"react-beautiful-dnd": "^13.0.0",
|
||||||
|
"react-datepicker": "^2.14.1",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-hook-form": "^5.2.0",
|
"react-hook-form": "^5.2.0",
|
||||||
"react-router": "^5.1.2",
|
"react-router": "^5.1.2",
|
||||||
|
@ -106,5 +106,9 @@ export default createGlobalStyle`
|
|||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
${mixin.placeholderColor(color.textLight)}
|
${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 QuickCardEditor from 'shared/components/QuickCardEditor';
|
||||||
import PopupMenu from 'shared/components/PopupMenu';
|
import PopupMenu from 'shared/components/PopupMenu';
|
||||||
import ListActions from 'shared/components/ListActions';
|
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 {
|
interface ColumnState {
|
||||||
[key: string]: TaskGroup;
|
[key: string]: TaskGroup;
|
||||||
@ -73,11 +77,16 @@ interface ProjectParams {
|
|||||||
const initialState: State = { tasks: {}, columns: {} };
|
const initialState: State = { tasks: {}, columns: {} };
|
||||||
const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' };
|
const initialPopupState = { left: 0, top: 0, isOpen: false, taskGroupID: '' };
|
||||||
const initialQuickCardEditorState: QuickCardEditorState = { isOpen: false, top: 0, left: 0 };
|
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 Project = () => {
|
||||||
const { projectId } = useParams<ProjectParams>();
|
const { projectId } = useParams<ProjectParams>();
|
||||||
const [listsData, setListsData] = useState(initialState);
|
const [listsData, setListsData] = useState(initialState);
|
||||||
const [popupData, setPopupData] = useState(initialPopupState);
|
const [popupData, setPopupData] = useState(initialPopupState);
|
||||||
|
const [memberPopupData, setMemberPopupData] = useState(initialMemberPopupState);
|
||||||
|
const [taskDetails, setTaskDetails] = useState(initialTaskDetailsState);
|
||||||
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
|
const [quickCardEditor, setQuickCardEditor] = useState(initialQuickCardEditorState);
|
||||||
const [updateTaskLocation] = useUpdateTaskLocationMutation();
|
const [updateTaskLocation] = useUpdateTaskLocationMutation();
|
||||||
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation();
|
const [updateTaskGroupLocation] = useUpdateTaskGroupLocationMutation();
|
||||||
@ -256,6 +265,9 @@ const Project = () => {
|
|||||||
</TitleWrapper>
|
</TitleWrapper>
|
||||||
<Board>
|
<Board>
|
||||||
<Lists
|
<Lists
|
||||||
|
onCardClick={task => {
|
||||||
|
setTaskDetails({ isOpen: true, taskID: task.taskID });
|
||||||
|
}}
|
||||||
onExtraMenuOpen={(taskGroupID, pos, size) => {
|
onExtraMenuOpen={(taskGroupID, pos, size) => {
|
||||||
setPopupData({
|
setPopupData({
|
||||||
isOpen: true,
|
isOpen: true,
|
||||||
@ -315,6 +327,57 @@ const Project = () => {
|
|||||||
/>
|
/>
|
||||||
</PopupMenu>
|
</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;
|
position?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TaskUser = {
|
||||||
|
userID: string;
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Task = {
|
type Task = {
|
||||||
taskID: string;
|
taskID: string;
|
||||||
taskGroup: InnerTaskGroup;
|
taskGroup: InnerTaskGroup;
|
||||||
@ -23,6 +28,7 @@ type Task = {
|
|||||||
position: number;
|
position: number;
|
||||||
labels: Label[];
|
labels: Label[];
|
||||||
description?: string;
|
description?: string;
|
||||||
|
members?: Array<TaskUser>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TaskGroup = {
|
type TaskGroup = {
|
||||||
@ -85,3 +91,8 @@ type ElementSize = {
|
|||||||
width: number;
|
width: number;
|
||||||
height: 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;
|
margin: 0 4px;
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 30px;
|
min-height: 45px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
`;
|
`;
|
||||||
|
@ -64,13 +64,13 @@ const initialListsData = {
|
|||||||
|
|
||||||
export const Default = () => {
|
export const Default = () => {
|
||||||
const [listsData, setListsData] = useState(initialListsData);
|
const [listsData, setListsData] = useState(initialListsData);
|
||||||
const onCardDrop = (droppedTask: any) => {
|
const onCardDrop = (droppedTask: Task) => {
|
||||||
console.log(droppedTask);
|
console.log(droppedTask);
|
||||||
const newState = {
|
const newState = {
|
||||||
...listsData,
|
...listsData,
|
||||||
tasks: {
|
tasks: {
|
||||||
...listsData.tasks,
|
...listsData.tasks,
|
||||||
[droppedTask.taskGroupID]: droppedTask,
|
[droppedTask.taskID]: droppedTask,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
console.log(newState);
|
console.log(newState);
|
||||||
@ -91,6 +91,7 @@ export const Default = () => {
|
|||||||
return (
|
return (
|
||||||
<Lists
|
<Lists
|
||||||
{...listsData}
|
{...listsData}
|
||||||
|
onCardClick={action('card click')}
|
||||||
onExtraMenuOpen={action('extra menu open')}
|
onExtraMenuOpen={action('extra menu open')}
|
||||||
onQuickEditorOpen={action('card composer open')}
|
onQuickEditorOpen={action('card composer open')}
|
||||||
onCardDrop={onCardDrop}
|
onCardDrop={onCardDrop}
|
||||||
@ -201,6 +202,7 @@ export const ListsWithManyList = () => {
|
|||||||
return (
|
return (
|
||||||
<Lists
|
<Lists
|
||||||
{...listsData}
|
{...listsData}
|
||||||
|
onCardClick={action('card click')}
|
||||||
onQuickEditorOpen={action('card composer open')}
|
onQuickEditorOpen={action('card composer open')}
|
||||||
onCardCreate={action('card create')}
|
onCardCreate={action('card create')}
|
||||||
onCardDrop={onCardDrop}
|
onCardDrop={onCardDrop}
|
||||||
|
@ -23,6 +23,7 @@ interface Tasks {
|
|||||||
type Props = {
|
type Props = {
|
||||||
columns: Columns;
|
columns: Columns;
|
||||||
tasks: Tasks;
|
tasks: Tasks;
|
||||||
|
onCardClick: (task: Task) => void;
|
||||||
onCardDrop: (task: Task) => void;
|
onCardDrop: (task: Task) => void;
|
||||||
onListDrop: (taskGroup: TaskGroup) => void;
|
onListDrop: (taskGroup: TaskGroup) => void;
|
||||||
onCardCreate: (taskGroupID: string, name: string) => void;
|
onCardCreate: (taskGroupID: string, name: string) => void;
|
||||||
@ -34,6 +35,7 @@ type Props = {
|
|||||||
const Lists: React.FC<Props> = ({
|
const Lists: React.FC<Props> = ({
|
||||||
columns,
|
columns,
|
||||||
tasks,
|
tasks,
|
||||||
|
onCardClick,
|
||||||
onCardDrop,
|
onCardDrop,
|
||||||
onListDrop,
|
onListDrop,
|
||||||
onCardCreate,
|
onCardCreate,
|
||||||
@ -72,7 +74,6 @@ const Lists: React.FC<Props> = ({
|
|||||||
|
|
||||||
console.log(beforeDropDraggables);
|
console.log(beforeDropDraggables);
|
||||||
console.log(destination);
|
console.log(destination);
|
||||||
console.log(droppedDraggable);
|
|
||||||
const afterDropDraggables = getAfterDropDraggableList(
|
const afterDropDraggables = getAfterDropDraggableList(
|
||||||
beforeDropDraggables,
|
beforeDropDraggables,
|
||||||
droppedDraggable,
|
droppedDraggable,
|
||||||
@ -85,16 +86,20 @@ const Lists: React.FC<Props> = ({
|
|||||||
|
|
||||||
if (isList) {
|
if (isList) {
|
||||||
const droppedList = columns[droppedDraggable.id];
|
const droppedList = columns[droppedDraggable.id];
|
||||||
|
console.log(`is list ${droppedList}`);
|
||||||
onListDrop({
|
onListDrop({
|
||||||
...droppedList,
|
...droppedList,
|
||||||
position: newPosition,
|
position: newPosition,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const droppedCard = tasks[droppedDraggable.id];
|
const droppedCard = tasks[droppedDraggable.id];
|
||||||
|
console.log(`is card ${droppedCard}`);
|
||||||
const newCard = {
|
const newCard = {
|
||||||
...droppedCard,
|
...droppedCard,
|
||||||
position: newPosition,
|
position: newPosition,
|
||||||
taskGroupID: destination.droppableId,
|
taskGroup: {
|
||||||
|
taskGroupID: destination.droppableId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
onCardDrop(newCard);
|
onCardDrop(newCard);
|
||||||
}
|
}
|
||||||
@ -121,22 +126,22 @@ const Lists: React.FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}>
|
<Draggable draggableId={column.taskGroupID} key={column.taskGroupID} index={index}>
|
||||||
{columnDragProvided => (
|
{columnDragProvided => (
|
||||||
<List
|
<Droppable type="tasks" droppableId={column.taskGroupID}>
|
||||||
id={column.taskGroupID}
|
{(columnDropProvided, snapshot) => (
|
||||||
name={column.name}
|
<List
|
||||||
key={column.taskGroupID}
|
name={column.name}
|
||||||
onOpenComposer={id => setCurrentComposer(id)}
|
onOpenComposer={id => setCurrentComposer(id)}
|
||||||
isComposerOpen={currentComposer === column.taskGroupID}
|
isComposerOpen={currentComposer === column.taskGroupID}
|
||||||
onSaveName={name => console.log(name)}
|
onSaveName={name => console.log(name)}
|
||||||
index={index}
|
tasks={columnCards}
|
||||||
tasks={columnCards}
|
ref={columnDragProvided.innerRef}
|
||||||
ref={columnDragProvided.innerRef}
|
wrapperProps={columnDragProvided.draggableProps}
|
||||||
wrapperProps={columnDragProvided.draggableProps}
|
headerProps={columnDragProvided.dragHandleProps}
|
||||||
headerProps={columnDragProvided.dragHandleProps}
|
onExtraMenuOpen={onExtraMenuOpen}
|
||||||
onExtraMenuOpen={onExtraMenuOpen}
|
id={column.taskGroupID}
|
||||||
>
|
key={column.taskGroupID}
|
||||||
<Droppable type="tasks" droppableId={column.taskGroupID}>
|
index={index}
|
||||||
{columnDropProvided => (
|
>
|
||||||
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
||||||
{columnCards.map((task: Task, taskIndex: any) => {
|
{columnCards.map((task: Task, taskIndex: any) => {
|
||||||
return (
|
return (
|
||||||
@ -154,7 +159,7 @@ const Lists: React.FC<Props> = ({
|
|||||||
description=""
|
description=""
|
||||||
title={task.name}
|
title={task.name}
|
||||||
labels={task.labels}
|
labels={task.labels}
|
||||||
onClick={e => console.log(e)}
|
onClick={() => onCardClick(task)}
|
||||||
onContextMenu={onQuickEditorOpen}
|
onContextMenu={onQuickEditorOpen}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -163,7 +168,6 @@ const Lists: React.FC<Props> = ({
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{columnDropProvided.placeholder}
|
{columnDropProvided.placeholder}
|
||||||
|
|
||||||
{currentComposer === column.taskGroupID && (
|
{currentComposer === column.taskGroupID && (
|
||||||
<CardComposer
|
<CardComposer
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
@ -176,9 +180,9 @@ const Lists: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ListCards>
|
</ListCards>
|
||||||
)}
|
</List>
|
||||||
</Droppable>
|
)}
|
||||||
</List>
|
</Droppable>
|
||||||
)}
|
)}
|
||||||
</Draggable>
|
</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`
|
export const ClickableOverlay = styled.div`
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
background: rgba(9, 30, 66, 0.54);
|
background: rgba(0, 0, 0, 0.4);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -25,7 +25,7 @@ export const StyledModal = styled.div<{ width: number }>`
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #fff;
|
background: #262c49;
|
||||||
max-width: ${props => props.width}px;
|
max-width: ${props => props.width}px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
@ -4,8 +4,12 @@ import LabelColors from 'shared/constants/labelColors';
|
|||||||
import LabelManager from 'shared/components/PopupMenu/LabelManager';
|
import LabelManager from 'shared/components/PopupMenu/LabelManager';
|
||||||
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
|
import LabelEditor from 'shared/components/PopupMenu/LabelEditor';
|
||||||
import ListActions from 'shared/components/ListActions';
|
import ListActions from 'shared/components/ListActions';
|
||||||
|
import MemberManager from 'shared/components/MemberManager';
|
||||||
|
import DueDateManager from 'shared/components/DueDateManager';
|
||||||
|
|
||||||
import PopupMenu from '.';
|
import PopupMenu from '.';
|
||||||
|
import NormalizeStyles from 'App/NormalizeStyles';
|
||||||
|
import BaseStyles from 'App/BaseStyles';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
component: PopupMenu,
|
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;
|
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);
|
box-shadow: 0 8px 16px -4px rgba(9, 30, 66, 0.25), 0 0 0 1px rgba(9, 30, 66, 0.08);
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 304px;
|
width: 304px;
|
||||||
z-index: 70;
|
z-index: 100000000000;
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -1,22 +1,38 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import TextareaAutosize from 'react-autosize-textarea/lib';
|
import TextareaAutosize from 'react-autosize-textarea/lib';
|
||||||
|
import { mixin } from 'shared/utils/styles';
|
||||||
|
|
||||||
export const TaskHeader = styled.div`
|
export const TaskHeader = styled.div`
|
||||||
|
padding: 21px 30px 0px;
|
||||||
|
margin-right: 70px;
|
||||||
display: flex;
|
display: flex;
|
||||||
-webkit-box-pack: justify;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
|
||||||
padding: 21px 18px 0px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskMeta = styled.div`
|
export const TaskMeta = styled.div`
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: inline-block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
border-radius: 4px;
|
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`
|
export const TaskActions = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 21px 18px 0px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
@ -26,19 +42,8 @@ export const TaskAction = styled.button`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
vertical-align: middle;
|
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
|
||||||
font-size: 14.5px;
|
|
||||||
color: rgb(66, 82, 110);
|
|
||||||
font-family: CircularStdBook;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 0px 9px;
|
padding: 0px 9px;
|
||||||
border-radius: 3px;
|
|
||||||
transition: all 0.1s ease 0s;
|
|
||||||
background: rgb(255, 255, 255);
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsWrapper = styled.div`
|
export const TaskDetailsWrapper = styled.div`
|
||||||
@ -53,13 +58,12 @@ export const TaskDetailsContent = styled.div`
|
|||||||
|
|
||||||
export const TaskDetailsSidebar = styled.div`
|
export const TaskDetailsSidebar = styled.div`
|
||||||
width: 35%;
|
width: 35%;
|
||||||
padding-top: 5px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsTitleWrapper = styled.div`
|
export const TaskDetailsTitleWrapper = styled.div`
|
||||||
height: 44px;
|
height: 44px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 18px 0px 0px -8px;
|
margin: 0 0 0 -8px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -70,8 +74,8 @@ export const TaskDetailsTitle = styled(TextareaAutosize)`
|
|||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-family: 'Droid Sans';
|
font-family: 'Droid Sans';
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
padding: 7px 7px 8px;
|
padding: 4px;
|
||||||
background: rgb(255, 255, 255);
|
background: #262c49;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
@ -79,28 +83,22 @@ export const TaskDetailsTitle = styled(TextareaAutosize)`
|
|||||||
transition: background 0.1s ease 0s;
|
transition: background 0.1s ease 0s;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: rgb(23, 43, 77);
|
color: #c2c6dc;
|
||||||
&:hover {
|
|
||||||
background: rgb(235, 236, 240);
|
|
||||||
}
|
|
||||||
&:focus {
|
&:focus {
|
||||||
box-shadow: rgb(76, 154, 255) 0px 0px 0px 1px;
|
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||||
background: rgb(255, 255, 255);
|
background: ${mixin.darken('#262c49', 0.15)};
|
||||||
border-width: 1px;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: rgb(76, 154, 255);
|
|
||||||
border-image: initial;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsLabel = styled.div`
|
export const TaskDetailsLabel = styled.div`
|
||||||
padding: 20px 0px 12px;
|
padding: 24px 0px 12px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
color: #c2c6dc;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsAddDetailsButton = styled.div`
|
export const TaskDetailsAddDetailsButton = styled.div`
|
||||||
background-color: rgba(9, 30, 66, 0.04);
|
background: ${mixin.darken('#262c49', 0.15)};
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@ -110,8 +108,9 @@ export const TaskDetailsAddDetailsButton = styled.div`
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: #c2c6dc;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(9, 30, 66, 0.08);
|
background: ${mixin.darken('#262c49', 0.25)};
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
@ -128,9 +127,9 @@ export const TaskDetailsEditorWrapper = styled.div`
|
|||||||
export const TaskDetailsEditor = styled(TextareaAutosize)`
|
export const TaskDetailsEditor = styled(TextareaAutosize)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 108px;
|
min-height: 108px;
|
||||||
background: #fff;
|
color: #c2c6dc;
|
||||||
box-shadow: none;
|
background: #262c49;
|
||||||
border-color: rgba(9, 30, 66, 0.13);
|
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@ -138,15 +137,15 @@ export const TaskDetailsEditor = styled(TextareaAutosize)`
|
|||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
background: #fff;
|
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||||
border: none;
|
background: ${mixin.darken('#262c49', 0.05)};
|
||||||
box-shadow: inset 0 0 0 2px #0079bf;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsMarkdown = styled.div`
|
export const TaskDetailsMarkdown = styled.div`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
color: #c2c6dc;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsControls = styled.div`
|
export const TaskDetailsControls = styled.div`
|
||||||
@ -179,3 +178,106 @@ export const CancelEdit = styled.div`
|
|||||||
width: 32px;
|
width: 32px;
|
||||||
cursor: pointer;
|
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
|
<TaskDetails
|
||||||
task={{
|
task={{
|
||||||
taskID: '1',
|
taskID: '1',
|
||||||
taskGroup: { taskGroupID: '1' },
|
taskGroup: { name: 'General', taskGroupID: '1' },
|
||||||
name: 'Hello, world',
|
name: 'Hello, world',
|
||||||
position: 1,
|
position: 1,
|
||||||
labels: [],
|
labels: [{ labelId: 'soft-skills', color: '#fff', active: true, name: 'Soft Skills' }],
|
||||||
description,
|
description,
|
||||||
|
members: [{ userID: '1', displayName: 'Jordan Knott' }],
|
||||||
}}
|
}}
|
||||||
|
onTaskNameChange={action('task name change')}
|
||||||
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
|
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
|
||||||
onDeleteTask={action('delete task')}
|
onDeleteTask={action('delete task')}
|
||||||
onCloseModal={action('close modal')}
|
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 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 useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
NoDueDateLabel,
|
||||||
|
TaskDetailsAddMember,
|
||||||
|
TaskGroupLabel,
|
||||||
|
TaskGroupLabelName,
|
||||||
TaskActions,
|
TaskActions,
|
||||||
|
TaskDetailsAddLabel,
|
||||||
|
TaskDetailsAddLabelIcon,
|
||||||
TaskAction,
|
TaskAction,
|
||||||
TaskMeta,
|
TaskMeta,
|
||||||
TaskHeader,
|
TaskHeader,
|
||||||
|
ProfileIcon,
|
||||||
TaskDetailsContent,
|
TaskDetailsContent,
|
||||||
TaskDetailsWrapper,
|
TaskDetailsWrapper,
|
||||||
TaskDetailsSidebar,
|
TaskDetailsSidebar,
|
||||||
@ -20,8 +27,16 @@ import {
|
|||||||
TaskDetailsControls,
|
TaskDetailsControls,
|
||||||
ConfirmSave,
|
ConfirmSave,
|
||||||
CancelEdit,
|
CancelEdit,
|
||||||
|
TaskDetailSectionTitle,
|
||||||
|
TaskDetailLabel,
|
||||||
|
TaskDetailLabels,
|
||||||
|
TaskDetailAssignee,
|
||||||
|
TaskDetailAssignees,
|
||||||
|
TaskDetailsAddMemberIcon,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
|
||||||
|
import convertDivElementRefToBounds from 'shared/utils/boundingRect';
|
||||||
|
|
||||||
type TaskContentProps = {
|
type TaskContentProps = {
|
||||||
onEditContent: () => void;
|
onEditContent: () => void;
|
||||||
description: string;
|
description: string;
|
||||||
@ -70,7 +85,7 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
|
|||||||
<TaskDetailsControls>
|
<TaskDetailsControls>
|
||||||
<ConfirmSave onClick={handleOutsideClick}>Save</ConfirmSave>
|
<ConfirmSave onClick={handleOutsideClick}>Save</ConfirmSave>
|
||||||
<CancelEdit onClick={onCancel}>
|
<CancelEdit onClick={onCancel}>
|
||||||
<Cross size={16} />
|
<Plus size={16} color="#c2c6dc" />
|
||||||
</CancelEdit>
|
</CancelEdit>
|
||||||
</TaskDetailsControls>
|
</TaskDetailsControls>
|
||||||
</TaskDetailsEditorWrapper>
|
</TaskDetailsEditorWrapper>
|
||||||
@ -79,37 +94,81 @@ const DetailsEditor: React.FC<DetailsEditorProps> = ({
|
|||||||
|
|
||||||
type TaskDetailsProps = {
|
type TaskDetailsProps = {
|
||||||
task: Task;
|
task: Task;
|
||||||
|
onTaskNameChange: (task: Task, newName: string) => void;
|
||||||
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
||||||
onDeleteTask: (task: Task) => void;
|
onDeleteTask: (task: Task) => void;
|
||||||
onCloseModal: () => 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 [editorOpen, setEditorOpen] = useState(false);
|
||||||
|
const [taskName, setTaskName] = useState(task.name);
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
setEditorOpen(!editorOpen);
|
setEditorOpen(!editorOpen);
|
||||||
};
|
};
|
||||||
const handleDeleteTask = () => {
|
const handleDeleteTask = () => {
|
||||||
onDeleteTask(task);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<TaskActions>
|
||||||
|
<TaskAction onClick={handleDeleteTask}>
|
||||||
|
<Bin size={20} color="#c2c6dc" />
|
||||||
|
</TaskAction>
|
||||||
|
<TaskAction onClick={onCloseModal}>
|
||||||
|
<Cross size={20} color="#c2c6dc" />
|
||||||
|
</TaskAction>
|
||||||
|
</TaskActions>
|
||||||
<TaskHeader>
|
<TaskHeader>
|
||||||
<TaskMeta />
|
<TaskDetailsTitleWrapper>
|
||||||
<TaskActions>
|
<TaskDetailsTitle
|
||||||
<TaskAction onClick={handleDeleteTask}>
|
value={taskName}
|
||||||
<Bin size={20} />
|
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setTaskName(e.currentTarget.value)}
|
||||||
</TaskAction>
|
onKeyDown={onKeyDown}
|
||||||
<TaskAction onClick={onCloseModal}>
|
/>
|
||||||
<Cross size={20} />
|
</TaskDetailsTitleWrapper>
|
||||||
</TaskAction>
|
<TaskMeta>
|
||||||
</TaskActions>
|
{task.taskGroup.name && (
|
||||||
|
<TaskGroupLabel>
|
||||||
|
in list <TaskGroupLabelName>{task.taskGroup.name}</TaskGroupLabelName>
|
||||||
|
</TaskGroupLabel>
|
||||||
|
)}
|
||||||
|
</TaskMeta>
|
||||||
</TaskHeader>
|
</TaskHeader>
|
||||||
<TaskDetailsWrapper>
|
<TaskDetailsWrapper>
|
||||||
<TaskDetailsContent>
|
<TaskDetailsContent>
|
||||||
<TaskDetailsTitleWrapper>
|
|
||||||
<TaskDetailsTitle value="Hello darkness my old friend" />
|
|
||||||
</TaskDetailsTitleWrapper>
|
|
||||||
<TaskDetailsLabel>Description</TaskDetailsLabel>
|
<TaskDetailsLabel>Description</TaskDetailsLabel>
|
||||||
{editorOpen ? (
|
{editorOpen ? (
|
||||||
<DetailsEditor
|
<DetailsEditor
|
||||||
@ -126,7 +185,38 @@ const TaskDetails: React.FC<TaskDetailsProps> = ({ task, onTaskDescriptionChange
|
|||||||
<TaskContent description={task.description ?? ''} onEditContent={handleClick} />
|
<TaskContent description={task.description ?? ''} onEditContent={handleClick} />
|
||||||
)}
|
)}
|
||||||
</TaskDetailsContent>
|
</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>
|
</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) => {
|
export const insertItemIntoArray = (arr: Array<DraggableElement>, item: DraggableElement, index: number) => {
|
||||||
const arrClone = [...arr];
|
const arrClone = [...arr];
|
||||||
|
console.log(arrClone, index, item);
|
||||||
arrClone.splice(index, 0, item);
|
arrClone.splice(index, 0, item);
|
||||||
|
console.log(arrClone);
|
||||||
return arrClone;
|
return arrClone;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -56,6 +58,7 @@ export const isPositionChanged = (source: DraggableLocation, destination: Dragga
|
|||||||
if (!destination) return false;
|
if (!destination) return false;
|
||||||
const isSameList = destination.droppableId === source.droppableId;
|
const isSameList = destination.droppableId === source.droppableId;
|
||||||
const isSamePosition = destination.index === source.index;
|
const isSamePosition = destination.index === source.index;
|
||||||
|
console.log(`isSameList: ${isSameList} : isSamePosition: ${isSamePosition}`);
|
||||||
return !isSameList || !isSamePosition;
|
return !isSameList || !isSamePosition;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3179,6 +3179,15 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/react" "*"
|
"@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":
|
"@types/react-dom@*", "@types/react-dom@^16.9.5":
|
||||||
version "16.9.5"
|
version "16.9.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.5.tgz#5de610b04a35d07ffd8f44edad93a71032d9aaa7"
|
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"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@^2.2.5:
|
classnames@^2.2.5, classnames@^2.2.6:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
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"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
|
||||||
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
|
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:
|
de-indent@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
|
||||||
@ -12032,7 +12046,7 @@ polished@^3.3.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.6.3"
|
"@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"
|
version "1.16.1"
|
||||||
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
|
resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
|
||||||
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
|
||||||
@ -13250,6 +13264,17 @@ react-color@^2.17.0:
|
|||||||
reactcss "^1.2.0"
|
reactcss "^1.2.0"
|
||||||
tinycolor2 "^1.4.1"
|
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:
|
react-dev-utils@^10.2.0:
|
||||||
version "10.2.0"
|
version "10.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-10.2.0.tgz#b11cc48aa2be2502fb3c27a50d1dfa95cfa9dfe0"
|
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"
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
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:
|
react-popper-tooltip@^2.8.3:
|
||||||
version "2.10.1"
|
version "2.10.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-popper-tooltip/-/react-popper-tooltip-2.10.1.tgz#e10875f31916297c694d64a677d6f8fa0a48b4d1"
|
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"
|
"@babel/runtime" "^7.7.4"
|
||||||
react-popper "^1.3.6"
|
react-popper "^1.3.6"
|
||||||
|
|
||||||
react-popper@^1.3.6:
|
react-popper@^1.3.4, react-popper@^1.3.6:
|
||||||
version "1.3.7"
|
version "1.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"
|
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"
|
||||||
integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==
|
integrity sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==
|
||||||
|
Loading…
Reference in New Issue
Block a user