arch: move web folder into api & move api to top level
This commit is contained in:
391
frontend/src/shared/components/TaskDetails/Styles.ts
Normal file
391
frontend/src/shared/components/TaskDetails/Styles.ts
Normal file
@ -0,0 +1,391 @@
|
||||
import styled from 'styled-components';
|
||||
import TextareaAutosize from 'react-autosize-textarea/lib';
|
||||
import { mixin } from 'shared/utils/styles';
|
||||
import Button from 'shared/components/Button';
|
||||
|
||||
export const TaskHeader = styled.div`
|
||||
padding: 21px 30px 0px;
|
||||
margin-right: 70px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const TaskMeta = styled.div`
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
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;
|
||||
`;
|
||||
|
||||
export const TaskAction = styled.button`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
padding: 0px 9px;
|
||||
`;
|
||||
|
||||
export const TaskDetailsWrapper = styled.div`
|
||||
display: flex;
|
||||
padding: 0px 16px 60px;
|
||||
`;
|
||||
|
||||
export const TaskDetailsContent = styled.div`
|
||||
flex: 1;
|
||||
padding-right: 8px;
|
||||
`;
|
||||
|
||||
export const TaskDetailsSidebar = styled.div`
|
||||
width: 168px;
|
||||
padding-left: 8px;
|
||||
`;
|
||||
|
||||
export const TaskDetailsTitleWrapper = styled.div`
|
||||
height: 44px;
|
||||
width: 100%;
|
||||
margin: 0 0 0 -8px;
|
||||
display: inline-block;
|
||||
`;
|
||||
|
||||
export const TaskDetailsTitle = styled(TextareaAutosize)`
|
||||
line-height: 1.28;
|
||||
resize: none;
|
||||
box-shadow: transparent 0px 0px 0px 1px;
|
||||
font-size: 24px;
|
||||
font-family: 'Droid Sans';
|
||||
font-weight: 700;
|
||||
padding: 4px;
|
||||
background: #262c49;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-image: initial;
|
||||
transition: background 0.1s ease 0s;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
color: #c2c6dc;
|
||||
&:focus {
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsLabel = styled.div`
|
||||
padding: 24px 0px 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #c2c6dc;
|
||||
`;
|
||||
|
||||
export const TaskDetailsAddDetailsButton = styled.div`
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
display: block;
|
||||
min-height: 56px;
|
||||
padding: 8px 12px;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: #c2c6dc;
|
||||
&:hover {
|
||||
background: ${mixin.darken('#262c49', 0.25)};
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsEditorWrapper = styled.div`
|
||||
display: block;
|
||||
float: left;
|
||||
padding-bottom: 9px;
|
||||
z-index: 50;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const TaskDetailsEditor = styled(TextareaAutosize)`
|
||||
width: 100%;
|
||||
min-height: 108px;
|
||||
color: #c2c6dc;
|
||||
background: #262c49;
|
||||
box-shadow: rgb(115, 103, 240) 0px 0px 0px 1px;
|
||||
border-radius: 3px;
|
||||
line-height: 20px;
|
||||
padding: 8px 12px;
|
||||
outline: none;
|
||||
border: none;
|
||||
|
||||
&:focus {
|
||||
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;
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
margin: 8px 8px 8px 24px;
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
p a {
|
||||
color: rgba(${props => props.theme.colors.primary});
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailsControls = styled.div`
|
||||
clear: both;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const ConfirmSave = styled(Button)`
|
||||
padding: 6px 12px;
|
||||
border-radius: 3px;
|
||||
margin-right: 6px;
|
||||
`;
|
||||
|
||||
export const CancelEdit = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
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 TaskDetailsAddMemberIcon = styled.div`
|
||||
float: left;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 100%;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TaskDetailLabels = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
export const TaskDetailLabel = styled.div<{ color: string }>`
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
background-color: ${props => props.color};
|
||||
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`
|
||||
float: left;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 3px;
|
||||
background: ${mixin.darken('#262c49', 0.15)};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
export const NoDueDateLabel = styled.span`
|
||||
color: rgb(137, 147, 164);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const UnassignedLabel = styled.div`
|
||||
color: rgb(137, 147, 164);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
`;
|
||||
|
||||
export const ActionButtons = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const ActionButtonsTitle = styled.h3`
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
`;
|
||||
|
||||
export const ActionButton = styled(Button)`
|
||||
margin-top: 8px;
|
||||
padding: 6px 12px;
|
||||
background: rgba(${props => props.theme.colors.bg.primary}, 0.4);
|
||||
text-align: left;
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background: rgba(${props => props.theme.colors.bg.primary}, 0.6);
|
||||
}
|
||||
`;
|
||||
|
||||
export const MetaDetails = styled.div`
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const TaskDueDateButton = styled(Button)`
|
||||
height: 32px;
|
||||
padding: 6px 12px;
|
||||
background: rgba(${props => props.theme.colors.bg.primary}, 0.4);
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
background: rgba(${props => props.theme.colors.bg.primary}, 0.6);
|
||||
}
|
||||
`;
|
||||
|
||||
export const MetaDetail = styled.div`
|
||||
display: block;
|
||||
float: left;
|
||||
margin: 0 16px 8px 0;
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
export const TaskDetailsSection = styled.div`
|
||||
display: block;
|
||||
`;
|
||||
|
||||
export const MetaDetailTitle = styled.h3`
|
||||
color: rgba(${props => props.theme.colors.text.primary});
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
margin-top: 16px;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
margin: 0 8px 4px 0;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export const MetaDetailContent = styled.div``;
|
@ -0,0 +1,84 @@
|
||||
import React, { useState } from 'react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import NormalizeStyles from 'App/NormalizeStyles';
|
||||
import BaseStyles from 'App/BaseStyles';
|
||||
import Modal from 'shared/components/Modal';
|
||||
import TaskDetails from '.';
|
||||
|
||||
export default {
|
||||
component: TaskDetails,
|
||||
title: 'TaskDetails',
|
||||
parameters: {
|
||||
backgrounds: [
|
||||
{ name: 'white', value: '#ffffff' },
|
||||
{ name: 'gray', value: '#cdd3e1', default: true },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = () => {
|
||||
const [description, setDescription] = useState('');
|
||||
return (
|
||||
<>
|
||||
<NormalizeStyles />
|
||||
<BaseStyles />
|
||||
<Modal
|
||||
width={1040}
|
||||
onClose={action('on close')}
|
||||
renderContent={() => {
|
||||
return (
|
||||
<TaskDetails
|
||||
onDeleteItem={action('delete item')}
|
||||
onChangeItemName={action('change item name')}
|
||||
task={{
|
||||
id: '1',
|
||||
taskGroup: { name: 'General', id: '1' },
|
||||
name: 'Hello, world',
|
||||
position: 1,
|
||||
labels: [
|
||||
{
|
||||
id: 'soft-skills',
|
||||
assignedDate: new Date().toString(),
|
||||
projectLabel: {
|
||||
createdDate: new Date().toString(),
|
||||
id: 'label-soft-skills',
|
||||
name: 'Soft Skills',
|
||||
labelColor: {
|
||||
id: '1',
|
||||
name: 'white',
|
||||
colorHex: '#fff',
|
||||
position: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
description,
|
||||
assigned: [
|
||||
{
|
||||
id: '1',
|
||||
profileIcon: { bgColor: null, url: null, initials: null },
|
||||
fullName: 'Jordan Knott',
|
||||
},
|
||||
],
|
||||
}}
|
||||
onTaskNameChange={action('task name change')}
|
||||
onTaskDescriptionChange={(_task, desc) => setDescription(desc)}
|
||||
onDeleteTask={action('delete task')}
|
||||
onCloseModal={action('close modal')}
|
||||
onMemberProfile={action('profile')}
|
||||
onOpenAddMemberPopup={action('open add member popup')}
|
||||
onAddItem={action('add item')}
|
||||
onToggleTaskComplete={action('toggle task complete')}
|
||||
onToggleChecklistItem={action('toggle checklist item')}
|
||||
onOpenAddLabelPopup={action('open add label popup')}
|
||||
onChangeChecklistName={action('change checklist name')}
|
||||
onDeleteChecklist={action('delete checklist')}
|
||||
onOpenAddChecklistPopup={action(' open checklist')}
|
||||
onOpenDueDatePopop={action('open due date popup')}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
340
frontend/src/shared/components/TaskDetails/index.tsx
Normal file
340
frontend/src/shared/components/TaskDetails/index.tsx
Normal file
@ -0,0 +1,340 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Bin, Cross, Plus } from 'shared/icons';
|
||||
import useOnOutsideClick from 'shared/hooks/onOutsideClick';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||
import moment from 'moment';
|
||||
|
||||
import {
|
||||
NoDueDateLabel,
|
||||
TaskDueDateButton,
|
||||
UnassignedLabel,
|
||||
TaskGroupLabel,
|
||||
TaskGroupLabelName,
|
||||
TaskDetailsSection,
|
||||
TaskActions,
|
||||
TaskDetailsAddLabel,
|
||||
TaskDetailsAddLabelIcon,
|
||||
TaskAction,
|
||||
TaskMeta,
|
||||
ActionButtons,
|
||||
ActionButton,
|
||||
ActionButtonsTitle,
|
||||
TaskHeader,
|
||||
ProfileIcon,
|
||||
TaskDetailsContent,
|
||||
TaskDetailsWrapper,
|
||||
TaskDetailsSidebar,
|
||||
TaskDetailsTitleWrapper,
|
||||
TaskDetailsTitle,
|
||||
TaskDetailsLabel,
|
||||
TaskDetailsAddDetailsButton,
|
||||
TaskDetailsEditor,
|
||||
TaskDetailsEditorWrapper,
|
||||
TaskDetailsMarkdown,
|
||||
TaskDetailsControls,
|
||||
ConfirmSave,
|
||||
CancelEdit,
|
||||
TaskDetailSectionTitle,
|
||||
TaskDetailLabel,
|
||||
TaskDetailLabels,
|
||||
TaskDetailAssignee,
|
||||
TaskDetailAssignees,
|
||||
TaskDetailsAddMemberIcon,
|
||||
MetaDetails,
|
||||
MetaDetail,
|
||||
MetaDetailTitle,
|
||||
MetaDetailContent,
|
||||
} from './Styles';
|
||||
import Checklist from '../Checklist';
|
||||
|
||||
type TaskContentProps = {
|
||||
onEditContent: () => void;
|
||||
description: string;
|
||||
};
|
||||
|
||||
type TaskLabelProps = {
|
||||
label: TaskLabel;
|
||||
onClick: ($target: React.RefObject<HTMLElement>) => void;
|
||||
};
|
||||
|
||||
const TaskLabelItem: React.FC<TaskLabelProps> = ({ label, onClick }) => {
|
||||
const $label = useRef<HTMLDivElement>(null);
|
||||
return (
|
||||
<TaskDetailLabel
|
||||
onClick={() => {
|
||||
onClick($label);
|
||||
}}
|
||||
ref={$label}
|
||||
color={label.projectLabel.labelColor.colorHex}
|
||||
>
|
||||
{label.projectLabel.name}
|
||||
</TaskDetailLabel>
|
||||
);
|
||||
};
|
||||
|
||||
const TaskContent: React.FC<TaskContentProps> = ({ description, onEditContent }) => {
|
||||
return description === '' ? (
|
||||
<TaskDetailsAddDetailsButton onClick={onEditContent}>Add a more detailed description</TaskDetailsAddDetailsButton>
|
||||
) : (
|
||||
<TaskDetailsMarkdown onClick={onEditContent}>
|
||||
<ReactMarkdown source={description} />
|
||||
</TaskDetailsMarkdown>
|
||||
);
|
||||
};
|
||||
|
||||
type DetailsEditorProps = {
|
||||
description: string;
|
||||
onTaskDescriptionChange: (newDescription: string) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
|
||||
const DetailsEditor: React.FC<DetailsEditorProps> = ({
|
||||
description: initialDescription,
|
||||
onTaskDescriptionChange,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [description, setDescription] = useState(initialDescription);
|
||||
const $editorWrapperRef = useRef<HTMLDivElement>(null);
|
||||
const $editorRef = useRef<HTMLTextAreaElement>(null);
|
||||
const handleOutsideClick = () => {
|
||||
onTaskDescriptionChange(description);
|
||||
};
|
||||
useEffect(() => {
|
||||
if ($editorRef && $editorRef.current) {
|
||||
$editorRef.current.focus();
|
||||
$editorRef.current.select();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useOnOutsideClick($editorWrapperRef, true, handleOutsideClick, null);
|
||||
return (
|
||||
<TaskDetailsEditorWrapper ref={$editorWrapperRef}>
|
||||
<TaskDetailsEditor
|
||||
ref={$editorRef}
|
||||
value={description}
|
||||
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setDescription(e.currentTarget.value)}
|
||||
/>
|
||||
<TaskDetailsControls>
|
||||
<ConfirmSave variant="relief" onClick={handleOutsideClick}>
|
||||
Save
|
||||
</ConfirmSave>
|
||||
<CancelEdit onClick={onCancel}>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</CancelEdit>
|
||||
</TaskDetailsControls>
|
||||
</TaskDetailsEditorWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
type TaskDetailsProps = {
|
||||
task: Task;
|
||||
onTaskNameChange: (task: Task, newName: string) => void;
|
||||
onTaskDescriptionChange: (task: Task, newDescription: string) => void;
|
||||
onDeleteTask: (task: Task) => void;
|
||||
onAddItem: (checklistID: string, name: string, position: number) => void;
|
||||
onDeleteItem: (itemID: string) => void;
|
||||
onChangeItemName: (itemID: string, itemName: string) => void;
|
||||
onToggleTaskComplete: (task: Task) => void;
|
||||
onToggleChecklistItem: (itemID: string, complete: boolean) => void;
|
||||
onOpenAddMemberPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
||||
onOpenAddLabelPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
||||
onOpenDueDatePopop: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
||||
onOpenAddChecklistPopup: (task: Task, $targetRef: React.RefObject<HTMLElement>) => void;
|
||||
onMemberProfile: ($targetRef: React.RefObject<HTMLElement>, memberID: string) => void;
|
||||
onChangeChecklistName: (checklistID: string, name: string) => void;
|
||||
onDeleteChecklist: ($target: React.RefObject<HTMLElement>, checklistID: string) => void;
|
||||
onCloseModal: () => void;
|
||||
};
|
||||
|
||||
const TaskDetails: React.FC<TaskDetailsProps> = ({
|
||||
task,
|
||||
onDeleteChecklist,
|
||||
onTaskNameChange,
|
||||
onOpenAddChecklistPopup,
|
||||
onChangeChecklistName,
|
||||
onToggleTaskComplete,
|
||||
onTaskDescriptionChange,
|
||||
onChangeItemName,
|
||||
onDeleteItem,
|
||||
onDeleteTask,
|
||||
onCloseModal,
|
||||
onOpenAddMemberPopup,
|
||||
onOpenAddLabelPopup,
|
||||
onOpenDueDatePopop,
|
||||
onAddItem,
|
||||
onToggleChecklistItem,
|
||||
onMemberProfile,
|
||||
}) => {
|
||||
const [editorOpen, setEditorOpen] = useState(false);
|
||||
const [description, setDescription] = useState(task.description ?? '');
|
||||
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 $unassignedRef = useRef<HTMLDivElement>(null);
|
||||
const $addMemberRef = useRef<HTMLDivElement>(null);
|
||||
const onUnassignedClick = () => {
|
||||
onOpenAddMemberPopup(task, $unassignedRef);
|
||||
};
|
||||
const onAddMember = ($target: React.RefObject<HTMLElement>) => {
|
||||
onOpenAddMemberPopup(task, $target);
|
||||
};
|
||||
const onAddChecklist = ($target: React.RefObject<HTMLElement>) => {
|
||||
onOpenAddChecklistPopup(task, $target)
|
||||
}
|
||||
const $dueDateLabel = useRef<HTMLDivElement>(null);
|
||||
const $addLabelRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onAddLabel = ($target: React.RefObject<HTMLElement>) => {
|
||||
onOpenAddLabelPopup(task, $target);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<TaskActions>
|
||||
<TaskAction onClick={handleDeleteTask}>
|
||||
<Bin size={20} color="#c2c6dc" />
|
||||
</TaskAction>
|
||||
<TaskAction onClick={onCloseModal}>
|
||||
<Cross width={16} height={16} />
|
||||
</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>
|
||||
<MetaDetails>
|
||||
{task.assigned && task.assigned.length !== 0 && (
|
||||
<MetaDetail>
|
||||
<MetaDetailTitle>MEMBERS</MetaDetailTitle>
|
||||
<MetaDetailContent>
|
||||
{task.assigned &&
|
||||
task.assigned.map(member => (
|
||||
<TaskAssignee key={member.id} size={32} member={member} onMemberProfile={onMemberProfile} />
|
||||
))}
|
||||
<TaskDetailsAddMemberIcon ref={$addMemberRef} onClick={() => onAddMember($addMemberRef)}>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</TaskDetailsAddMemberIcon>
|
||||
</MetaDetailContent>
|
||||
</MetaDetail>
|
||||
)}
|
||||
{task.labels.length !== 0 && (
|
||||
<MetaDetail>
|
||||
<MetaDetailTitle>LABELS</MetaDetailTitle>
|
||||
<MetaDetailContent>
|
||||
{task.labels.map(label => {
|
||||
return (
|
||||
<TaskLabelItem
|
||||
key={label.projectLabel.id}
|
||||
label={label}
|
||||
onClick={$target => {
|
||||
onOpenAddLabelPopup(task, $target);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<TaskDetailsAddLabelIcon ref={$addLabelRef} onClick={() => onAddLabel($addLabelRef)}>
|
||||
<Plus size={16} color="#c2c6dc" />
|
||||
</TaskDetailsAddLabelIcon>
|
||||
</MetaDetailContent>
|
||||
</MetaDetail>
|
||||
)}
|
||||
{task.dueDate && (
|
||||
<MetaDetail>
|
||||
<MetaDetailTitle>DUE DATE</MetaDetailTitle>
|
||||
<MetaDetailContent>
|
||||
<TaskDueDateButton>{moment(task.dueDate).format('MMM D [at] h:mm A')}</TaskDueDateButton>
|
||||
</MetaDetailContent>
|
||||
</MetaDetail>
|
||||
)}
|
||||
</MetaDetails>
|
||||
|
||||
<TaskDetailsSection>
|
||||
<TaskDetailsLabel>Description</TaskDetailsLabel>
|
||||
{editorOpen ? (
|
||||
<DetailsEditor
|
||||
description={description}
|
||||
onTaskDescriptionChange={newDescription => {
|
||||
setEditorOpen(false);
|
||||
setDescription(newDescription);
|
||||
onTaskDescriptionChange(task, newDescription);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setEditorOpen(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TaskContent description={description} onEditContent={handleClick} />
|
||||
)}
|
||||
{task.checklists &&
|
||||
task.checklists
|
||||
.slice()
|
||||
.sort((a, b) => a.position - b.position)
|
||||
.map(checklist => (
|
||||
<Checklist
|
||||
key={checklist.id}
|
||||
name={checklist.name}
|
||||
checklistID={checklist.id}
|
||||
items={checklist.items}
|
||||
onDeleteChecklist={onDeleteChecklist}
|
||||
onChangeName={newName => onChangeChecklistName(checklist.id, newName)}
|
||||
onToggleItem={onToggleChecklistItem}
|
||||
onDeleteItem={onDeleteItem}
|
||||
onAddItem={n => {
|
||||
if (task.checklists) {
|
||||
let position = 65535;
|
||||
const [lastItem] = checklist.items.sort((a, b) => a.position - b.position).slice(-1);
|
||||
if (lastItem) {
|
||||
position = lastItem.position * 2 + 1;
|
||||
}
|
||||
onAddItem(checklist.id, n, position);
|
||||
}
|
||||
}}
|
||||
onChangeItemName={onChangeItemName}
|
||||
/>
|
||||
))}
|
||||
</TaskDetailsSection>
|
||||
</TaskDetailsContent>
|
||||
<TaskDetailsSidebar>
|
||||
<ActionButtons>
|
||||
<ActionButtonsTitle>ADD TO CARD</ActionButtonsTitle>
|
||||
<ActionButton onClick={() => onToggleTaskComplete(task)}>
|
||||
{task.complete ? 'Mark Incomplete' : 'Mark Complete'}
|
||||
</ActionButton>
|
||||
<ActionButton onClick={$target => onAddMember($target)}>Members</ActionButton>
|
||||
<ActionButton onClick={$target => onAddLabel($target)}>Labels</ActionButton>
|
||||
<ActionButton onClick={$target => onAddChecklist($target)}>Checklist</ActionButton>
|
||||
<ActionButton onClick={$target => onOpenDueDatePopop(task, $target)}>Due Date</ActionButton>
|
||||
<ActionButton>Attachment</ActionButton>
|
||||
<ActionButton>Cover</ActionButton>
|
||||
</ActionButtons>
|
||||
</TaskDetailsSidebar>
|
||||
</TaskDetailsWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskDetails;
|
Reference in New Issue
Block a user