feat: add comments badge to task card
This commit is contained in:
parent
3992e4c2de
commit
119a4b2868
@ -1,18 +1,27 @@
|
|||||||
import styled, { css, keyframes } from 'styled-components';
|
import styled, { css, keyframes } from 'styled-components';
|
||||||
import { mixin } from 'shared/utils/styles';
|
import { mixin } from 'shared/utils/styles';
|
||||||
import TextareaAutosize from 'react-autosize-textarea';
|
import TextareaAutosize from 'react-autosize-textarea';
|
||||||
import { CheckCircle, CheckSquareOutline, Clock } from 'shared/icons';
|
import { CheckCircle, CheckSquareOutline, Clock, Bubble } from 'shared/icons';
|
||||||
import TaskAssignee from 'shared/components/TaskAssignee';
|
import TaskAssignee from 'shared/components/TaskAssignee';
|
||||||
|
|
||||||
export const CardMember = styled(TaskAssignee)<{ zIndex: number }>`
|
export const CardMember = styled(TaskAssignee)<{ zIndex: number }>`
|
||||||
box-shadow: 0 0 0 2px ${props => props.theme.colors.bg.secondary},
|
box-shadow: 0 0 0 2px ${(props) => props.theme.colors.bg.secondary},
|
||||||
inset 0 0 0 1px ${props => mixin.rgba(props.theme.colors.bg.secondary, 0.07)};
|
inset 0 0 0 1px ${(props) => mixin.rgba(props.theme.colors.bg.secondary, 0.07)};
|
||||||
z-index: ${props => props.zIndex};
|
z-index: ${(props) => props.zIndex};
|
||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const CommentsIcon = styled(Bubble)<{ color: 'success' | 'normal' }>`
|
||||||
|
${(props) =>
|
||||||
|
props.color === 'success' &&
|
||||||
|
css`
|
||||||
|
fill: ${props.theme.colors.success};
|
||||||
|
stroke: ${props.theme.colors.success};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'normal' }>`
|
export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'normal' }>`
|
||||||
${props =>
|
${(props) =>
|
||||||
props.color === 'success' &&
|
props.color === 'success' &&
|
||||||
css`
|
css`
|
||||||
fill: ${props.theme.colors.success};
|
fill: ${props.theme.colors.success};
|
||||||
@ -21,7 +30,7 @@ export const ChecklistIcon = styled(CheckSquareOutline)<{ color: 'success' | 'no
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ClockIcon = styled(Clock)<{ color: string }>`
|
export const ClockIcon = styled(Clock)<{ color: string }>`
|
||||||
fill: ${props => props.color};
|
fill: ${(props) => props.color};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const EditorTextarea = styled(TextareaAutosize)`
|
export const EditorTextarea = styled(TextareaAutosize)`
|
||||||
@ -40,7 +49,7 @@ export const EditorTextarea = styled(TextareaAutosize)`
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
&:focus {
|
&:focus {
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
@ -54,6 +63,22 @@ export const ListCardBadges = styled.div`
|
|||||||
margin-left: -2px;
|
margin-left: -2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const CommentsBadge = styled.div`
|
||||||
|
color: #5e6c84;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 6px 4px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
padding: 2px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
vertical-align: top;
|
||||||
|
`;
|
||||||
|
|
||||||
export const ListCardBadge = styled.div`
|
export const ListCardBadge = styled.div`
|
||||||
color: #5e6c84;
|
color: #5e6c84;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -76,7 +101,7 @@ export const DescriptionBadge = styled(ListCardBadge)`
|
|||||||
|
|
||||||
export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>`
|
export const DueDateCardBadge = styled(ListCardBadge)<{ isPastDue: boolean }>`
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
${props =>
|
${(props) =>
|
||||||
props.isPastDue &&
|
props.isPastDue &&
|
||||||
css`
|
css`
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
@ -91,7 +116,7 @@ export const ListCardBadgeText = styled.span<{ color?: 'success' | 'normal' }>`
|
|||||||
padding: 0 4px 0 6px;
|
padding: 0 4px 0 6px;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
${props => props.color === 'success' && `color: ${props.theme.colors.success};`}
|
${(props) => props.color === 'success' && `color: ${props.theme.colors.success};`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
|
export const ListCardContainer = styled.div<{ isActive: boolean; editable: boolean }>`
|
||||||
@ -102,7 +127,7 @@ export const ListCardContainer = styled.div<{ isActive: boolean; editable: boole
|
|||||||
cursor: pointer !important;
|
cursor: pointer !important;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
background-color: ${props =>
|
background-color: ${(props) =>
|
||||||
props.isActive && !props.editable
|
props.isActive && !props.editable
|
||||||
? mixin.darken(props.theme.colors.bg.secondary, 0.1)
|
? mixin.darken(props.theme.colors.bg.secondary, 0.1)
|
||||||
: `${props.theme.colors.bg.secondary}`};
|
: `${props.theme.colors.bg.secondary}`};
|
||||||
@ -119,7 +144,7 @@ export const ListCardDetails = styled.div<{ complete: boolean }>`
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
${props => props.complete && 'opacity: 0.6;'}
|
${(props) => props.complete && 'opacity: 0.6;'}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const labelVariantExpandAnimation = keyframes`
|
const labelVariantExpandAnimation = keyframes`
|
||||||
@ -157,7 +182,7 @@ export const ListCardLabelsWrapper = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
|
export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
|
||||||
${props =>
|
${(props) =>
|
||||||
props.variant === 'small'
|
props.variant === 'small'
|
||||||
? css`
|
? css`
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@ -183,14 +208,14 @@ export const ListCardLabel = styled.span<{ variant: 'small' | 'large' }>`
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: ${props => props.color};
|
background-color: ${(props) => props.color};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ListCardLabels = styled.div<{ toggleLabels: boolean; toggleDirection: 'expand' | 'shrink' }>`
|
export const ListCardLabels = styled.div<{ toggleLabels: boolean; toggleDirection: 'expand' | 'shrink' }>`
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
${props =>
|
${(props) =>
|
||||||
props.toggleLabels &&
|
props.toggleLabels &&
|
||||||
props.toggleDirection === 'expand' &&
|
props.toggleDirection === 'expand' &&
|
||||||
css`
|
css`
|
||||||
@ -201,7 +226,7 @@ export const ListCardLabels = styled.div<{ toggleLabels: boolean; toggleDirectio
|
|||||||
animation: ${labelTextVariantExpandAnimation} 0.45s ease-out;
|
animation: ${labelTextVariantExpandAnimation} 0.45s ease-out;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
${props =>
|
${(props) =>
|
||||||
props.toggleLabels &&
|
props.toggleLabels &&
|
||||||
props.toggleDirection === 'shrink' &&
|
props.toggleDirection === 'shrink' &&
|
||||||
css`
|
css`
|
||||||
@ -225,7 +250,7 @@ export const ListCardOperation = styled.span`
|
|||||||
top: 2px;
|
top: 2px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.25)};
|
background-color: ${(props) => mixin.darken(props.theme.colors.bg.secondary, 0.25)};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -234,7 +259,7 @@ export const CardTitle = styled.div`
|
|||||||
margin: 0 0 4px;
|
margin: 0 0 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
display: block;
|
display: block;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
@ -251,7 +276,7 @@ export const CardMembers = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const CompleteIcon = styled(CheckCircle)`
|
export const CompleteIcon = styled(CheckCircle)`
|
||||||
fill: ${props => props.theme.colors.success};
|
fill: ${(props) => props.theme.colors.success};
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
|
@ -23,6 +23,8 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
CardMembers,
|
CardMembers,
|
||||||
CardTitleText,
|
CardTitleText,
|
||||||
|
CommentsIcon,
|
||||||
|
CommentsBadge,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
|
|
||||||
type DueDate = {
|
type DueDate = {
|
||||||
@ -47,6 +49,7 @@ type Props = {
|
|||||||
dueDate?: DueDate;
|
dueDate?: DueDate;
|
||||||
checklists?: Checklist | null;
|
checklists?: Checklist | null;
|
||||||
labels?: Array<ProjectLabel>;
|
labels?: Array<ProjectLabel>;
|
||||||
|
comments?: { unread: boolean; total: number } | null;
|
||||||
watched?: boolean;
|
watched?: boolean;
|
||||||
wrapperProps?: any;
|
wrapperProps?: any;
|
||||||
members?: Array<TaskUser> | null;
|
members?: Array<TaskUser> | null;
|
||||||
@ -72,6 +75,7 @@ const Card = React.forwardRef(
|
|||||||
taskGroupID,
|
taskGroupID,
|
||||||
complete,
|
complete,
|
||||||
toggleLabels = false,
|
toggleLabels = false,
|
||||||
|
comments,
|
||||||
toggleDirection = 'shrink',
|
toggleDirection = 'shrink',
|
||||||
setToggleLabels,
|
setToggleLabels,
|
||||||
onClick,
|
onClick,
|
||||||
@ -138,7 +142,7 @@ const Card = React.forwardRef(
|
|||||||
onMouseEnter={() => setActive(true)}
|
onMouseEnter={() => setActive(true)}
|
||||||
onMouseLeave={() => setActive(false)}
|
onMouseLeave={() => setActive(false)}
|
||||||
ref={$cardRef}
|
ref={$cardRef}
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
onClick(e);
|
onClick(e);
|
||||||
}
|
}
|
||||||
@ -151,7 +155,7 @@ const Card = React.forwardRef(
|
|||||||
<ListCardInnerContainer ref={$innerCardRef}>
|
<ListCardInnerContainer ref={$innerCardRef}>
|
||||||
{!isPublic && isActive && !editable && (
|
{!isPublic && isActive && !editable && (
|
||||||
<ListCardOperation
|
<ListCardOperation
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (onContextMenu) {
|
if (onContextMenu) {
|
||||||
onContextMenu($innerCardRef, taskID, taskGroupID);
|
onContextMenu($innerCardRef, taskID, taskGroupID);
|
||||||
@ -167,7 +171,7 @@ const Card = React.forwardRef(
|
|||||||
<ListCardLabels
|
<ListCardLabels
|
||||||
toggleLabels={toggleLabels}
|
toggleLabels={toggleLabels}
|
||||||
toggleDirection={toggleDirection}
|
toggleDirection={toggleDirection}
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (onCardLabelClick) {
|
if (onCardLabelClick) {
|
||||||
onCardLabelClick();
|
onCardLabelClick();
|
||||||
@ -177,7 +181,7 @@ const Card = React.forwardRef(
|
|||||||
{labels
|
{labels
|
||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => a.labelColor.position - b.labelColor.position)
|
.sort((a, b) => a.labelColor.position - b.labelColor.position)
|
||||||
.map(label => (
|
.map((label) => (
|
||||||
<ListCardLabel
|
<ListCardLabel
|
||||||
onAnimationEnd={() => {
|
onAnimationEnd={() => {
|
||||||
if (setToggleLabels) {
|
if (setToggleLabels) {
|
||||||
@ -198,13 +202,13 @@ const Card = React.forwardRef(
|
|||||||
<EditorContent>
|
<EditorContent>
|
||||||
{complete && <CompleteIcon width={16} height={16} />}
|
{complete && <CompleteIcon width={16} height={16} />}
|
||||||
<EditorTextarea
|
<EditorTextarea
|
||||||
onChange={e => {
|
onChange={(e) => {
|
||||||
setCardTitle(e.currentTarget.value);
|
setCardTitle(e.currentTarget.value);
|
||||||
if (onCardTitleChange) {
|
if (onCardTitleChange) {
|
||||||
onCardTitleChange(e.currentTarget.value);
|
onCardTitleChange(e.currentTarget.value);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
@ -235,6 +239,12 @@ const Card = React.forwardRef(
|
|||||||
<List width={8} height={8} />
|
<List width={8} height={8} />
|
||||||
</DescriptionBadge>
|
</DescriptionBadge>
|
||||||
)}
|
)}
|
||||||
|
{comments && (
|
||||||
|
<CommentsBadge>
|
||||||
|
<CommentsIcon color={comments.unread ? 'success' : 'normal'} width={8} height={8} />
|
||||||
|
<ListCardBadgeText color={comments.unread ? 'success' : 'normal'}>{comments.total}</ListCardBadgeText>
|
||||||
|
</CommentsBadge>
|
||||||
|
)}
|
||||||
{checklists && (
|
{checklists && (
|
||||||
<ListCardBadge>
|
<ListCardBadge>
|
||||||
<ChecklistIcon
|
<ChecklistIcon
|
||||||
@ -256,7 +266,7 @@ const Card = React.forwardRef(
|
|||||||
size={28}
|
size={28}
|
||||||
zIndex={members.length - idx}
|
zIndex={members.length - idx}
|
||||||
member={member}
|
member={member}
|
||||||
onMemberProfile={$target => {
|
onMemberProfile={($target) => {
|
||||||
if (onCardMemberClick) {
|
if (onCardMemberClick) {
|
||||||
onCardMemberClick($target, taskID, member.id);
|
onCardMemberClick($target, taskID, member.id);
|
||||||
}
|
}
|
||||||
|
@ -111,24 +111,16 @@ function shouldStatusFilter(task: Task, filter: TaskStatusFilter) {
|
|||||||
const TODAY = REFERENCE.clone().startOf('day');
|
const TODAY = REFERENCE.clone().startOf('day');
|
||||||
return completedAt.isSame(TODAY, 'd');
|
return completedAt.isSame(TODAY, 'd');
|
||||||
case TaskSince.YESTERDAY:
|
case TaskSince.YESTERDAY:
|
||||||
const YESTERDAY = REFERENCE.clone()
|
const YESTERDAY = REFERENCE.clone().subtract(1, 'day').startOf('day');
|
||||||
.subtract(1, 'day')
|
|
||||||
.startOf('day');
|
|
||||||
return completedAt.isSameOrAfter(YESTERDAY, 'd');
|
return completedAt.isSameOrAfter(YESTERDAY, 'd');
|
||||||
case TaskSince.ONE_WEEK:
|
case TaskSince.ONE_WEEK:
|
||||||
const ONE_WEEK = REFERENCE.clone()
|
const ONE_WEEK = REFERENCE.clone().subtract(7, 'day').startOf('day');
|
||||||
.subtract(7, 'day')
|
|
||||||
.startOf('day');
|
|
||||||
return completedAt.isSameOrAfter(ONE_WEEK, 'd');
|
return completedAt.isSameOrAfter(ONE_WEEK, 'd');
|
||||||
case TaskSince.TWO_WEEKS:
|
case TaskSince.TWO_WEEKS:
|
||||||
const TWO_WEEKS = REFERENCE.clone()
|
const TWO_WEEKS = REFERENCE.clone().subtract(14, 'day').startOf('day');
|
||||||
.subtract(14, 'day')
|
|
||||||
.startOf('day');
|
|
||||||
return completedAt.isSameOrAfter(TWO_WEEKS, 'd');
|
return completedAt.isSameOrAfter(TWO_WEEKS, 'd');
|
||||||
case TaskSince.THREE_WEEKS:
|
case TaskSince.THREE_WEEKS:
|
||||||
const THREE_WEEKS = REFERENCE.clone()
|
const THREE_WEEKS = REFERENCE.clone().subtract(21, 'day').startOf('day');
|
||||||
.subtract(21, 'day')
|
|
||||||
.startOf('day');
|
|
||||||
return completedAt.isSameOrAfter(THREE_WEEKS, 'd');
|
return completedAt.isSameOrAfter(THREE_WEEKS, 'd');
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
@ -203,14 +195,14 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
let beforeDropDraggables: Array<DraggableElement> | null = null;
|
let beforeDropDraggables: Array<DraggableElement> | null = null;
|
||||||
|
|
||||||
if (isList) {
|
if (isList) {
|
||||||
const droppedGroup = taskGroups.find(taskGroup => taskGroup.id === draggableId);
|
const droppedGroup = taskGroups.find((taskGroup) => taskGroup.id === draggableId);
|
||||||
if (droppedGroup) {
|
if (droppedGroup) {
|
||||||
droppedDraggable = {
|
droppedDraggable = {
|
||||||
id: draggableId,
|
id: draggableId,
|
||||||
position: droppedGroup.position,
|
position: droppedGroup.position,
|
||||||
};
|
};
|
||||||
beforeDropDraggables = getSortedDraggables(
|
beforeDropDraggables = getSortedDraggables(
|
||||||
taskGroups.map(taskGroup => {
|
taskGroups.map((taskGroup) => {
|
||||||
return { id: taskGroup.id, position: taskGroup.position };
|
return { id: taskGroup.id, position: taskGroup.position };
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -234,13 +226,13 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const curTaskGroup = taskGroups.findIndex(
|
const curTaskGroup = taskGroups.findIndex(
|
||||||
taskGroup => taskGroup.tasks.findIndex(task => task.id === draggableId) !== -1,
|
(taskGroup) => taskGroup.tasks.findIndex((task) => task.id === draggableId) !== -1,
|
||||||
);
|
);
|
||||||
let targetTaskGroup = curTaskGroup;
|
let targetTaskGroup = curTaskGroup;
|
||||||
if (!isSameList) {
|
if (!isSameList) {
|
||||||
targetTaskGroup = taskGroups.findIndex(taskGroup => taskGroup.id === destination.droppableId);
|
targetTaskGroup = taskGroups.findIndex((taskGroup) => taskGroup.id === destination.droppableId);
|
||||||
}
|
}
|
||||||
const droppedTask = taskGroups[curTaskGroup].tasks.find(task => task.id === draggableId);
|
const droppedTask = taskGroups[curTaskGroup].tasks.find((task) => task.id === draggableId);
|
||||||
|
|
||||||
if (droppedTask) {
|
if (droppedTask) {
|
||||||
droppedDraggable = {
|
droppedDraggable = {
|
||||||
@ -248,7 +240,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
position: droppedTask.position,
|
position: droppedTask.position,
|
||||||
};
|
};
|
||||||
beforeDropDraggables = getSortedDraggables(
|
beforeDropDraggables = getSortedDraggables(
|
||||||
taskGroups[targetTaskGroup].tasks.map(task => {
|
taskGroups[targetTaskGroup].tasks.map((task) => {
|
||||||
return { id: task.id, position: task.position };
|
return { id: task.id, position: task.position };
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -286,7 +278,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
<BoardWrapper>
|
<BoardWrapper>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<Droppable direction="horizontal" type="column" droppableId="root">
|
<Droppable direction="horizontal" type="column" droppableId="root">
|
||||||
{provided => (
|
{(provided) => (
|
||||||
<Container {...provided.droppableProps} ref={provided.innerRef}>
|
<Container {...provided.droppableProps} ref={provided.innerRef}>
|
||||||
{taskGroups
|
{taskGroups
|
||||||
.slice()
|
.slice()
|
||||||
@ -294,14 +286,14 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
.map((taskGroup: TaskGroup, index: number) => {
|
.map((taskGroup: TaskGroup, index: number) => {
|
||||||
return (
|
return (
|
||||||
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
|
<Draggable draggableId={taskGroup.id} key={taskGroup.id} index={index}>
|
||||||
{columnDragProvided => (
|
{(columnDragProvided) => (
|
||||||
<Droppable type="tasks" droppableId={taskGroup.id}>
|
<Droppable type="tasks" droppableId={taskGroup.id}>
|
||||||
{(columnDropProvided, snapshot) => (
|
{(columnDropProvided, snapshot) => (
|
||||||
<List
|
<List
|
||||||
name={taskGroup.name}
|
name={taskGroup.name}
|
||||||
onOpenComposer={id => setCurrentComposer(id)}
|
onOpenComposer={(id) => setCurrentComposer(id)}
|
||||||
isComposerOpen={currentComposer === taskGroup.id}
|
isComposerOpen={currentComposer === taskGroup.id}
|
||||||
onSaveName={name => onChangeTaskGroupName(taskGroup.id, name)}
|
onSaveName={(name) => onChangeTaskGroupName(taskGroup.id, name)}
|
||||||
isPublic={isPublic}
|
isPublic={isPublic}
|
||||||
ref={columnDragProvided.innerRef}
|
ref={columnDragProvided.innerRef}
|
||||||
wrapperProps={columnDragProvided.draggableProps}
|
wrapperProps={columnDragProvided.draggableProps}
|
||||||
@ -314,8 +306,8 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
<ListCards ref={columnDropProvided.innerRef} {...columnDropProvided.droppableProps}>
|
||||||
{taskGroup.tasks
|
{taskGroup.tasks
|
||||||
.slice()
|
.slice()
|
||||||
.filter(t => shouldStatusFilter(t, taskStatusFilter))
|
.filter((t) => shouldStatusFilter(t, taskStatusFilter))
|
||||||
.filter(t => shouldMetaFilter(t, taskMetaFilters))
|
.filter((t) => shouldMetaFilter(t, taskMetaFilters))
|
||||||
.sort((a: any, b: any) => a.position - b.position)
|
.sort((a: any, b: any) => a.position - b.position)
|
||||||
.sort((a: any, b: any) => sortTasks(a, b, taskSorting))
|
.sort((a: any, b: any) => sortTasks(a, b, taskSorting))
|
||||||
.map((task: Task, taskIndex: any) => {
|
.map((task: Task, taskIndex: any) => {
|
||||||
@ -326,7 +318,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
index={taskIndex}
|
index={taskIndex}
|
||||||
isDragDisabled={taskSorting.type !== TaskSortingType.NONE}
|
isDragDisabled={taskSorting.type !== TaskSortingType.NONE}
|
||||||
>
|
>
|
||||||
{taskProvided => {
|
{(taskProvided) => {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
toggleDirection={toggleDirection}
|
toggleDirection={toggleDirection}
|
||||||
@ -352,7 +344,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
complete={task.complete ?? false}
|
complete={task.complete ?? false}
|
||||||
taskGroupID={taskGroup.id}
|
taskGroupID={taskGroup.id}
|
||||||
description=""
|
description=""
|
||||||
labels={task.labels.map(label => label.projectLabel)}
|
labels={task.labels.map((label) => label.projectLabel)}
|
||||||
dueDate={
|
dueDate={
|
||||||
task.dueDate
|
task.dueDate
|
||||||
? {
|
? {
|
||||||
@ -367,6 +359,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
onTaskClick(task);
|
onTaskClick(task);
|
||||||
}}
|
}}
|
||||||
checklists={task.badges && task.badges.checklist}
|
checklists={task.badges && task.badges.checklist}
|
||||||
|
comments={task.badges && task.badges.comments}
|
||||||
onCardMemberClick={onCardMemberClick}
|
onCardMemberClick={onCardMemberClick}
|
||||||
onContextMenu={onQuickEditorOpen}
|
onContextMenu={onQuickEditorOpen}
|
||||||
/>
|
/>
|
||||||
@ -381,7 +374,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setCurrentComposer('');
|
setCurrentComposer('');
|
||||||
}}
|
}}
|
||||||
onCreateCard={name => {
|
onCreateCard={(name) => {
|
||||||
onCreateTask(taskGroup.id, name);
|
onCreateTask(taskGroup.id, name);
|
||||||
}}
|
}}
|
||||||
isOpen
|
isOpen
|
||||||
@ -402,7 +395,7 @@ const SimpleLists: React.FC<SimpleProps> = ({
|
|||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
{!isPublic && (
|
{!isPublic && (
|
||||||
<AddList
|
<AddList
|
||||||
onSave={listName => {
|
onSave={(listName) => {
|
||||||
onCreateTaskGroup(listName);
|
onCreateTaskGroup(listName);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -22,14 +22,14 @@ export const MarkCompleteButton = styled.button<{ invert: boolean; disabled?: bo
|
|||||||
position: relative;
|
position: relative;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: ${props => props.theme.borderRadius.alternate};
|
border-radius: ${(props) => props.theme.borderRadius.alternate};
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
& span {
|
& span {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
${props =>
|
${(props) =>
|
||||||
props.invert
|
props.invert
|
||||||
? css`
|
? css`
|
||||||
background: ${props.theme.colors.success};
|
background: ${props.theme.colors.success};
|
||||||
@ -63,7 +63,7 @@ export const MarkCompleteButton = styled.button<{ invert: boolean; disabled?: bo
|
|||||||
color: ${props.theme.colors.success};
|
color: ${props.theme.colors.success};
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
${props =>
|
${(props) =>
|
||||||
props.invert &&
|
props.invert &&
|
||||||
css`
|
css`
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
@ -89,7 +89,7 @@ export const SidebarTitle = styled.div`
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
color: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -110,12 +110,12 @@ export const skeletonKeyframes = keyframes`
|
|||||||
|
|
||||||
export const SidebarButton = styled.div<{ $loading?: boolean }>`
|
export const SidebarButton = styled.div<{ $loading?: boolean }>`
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
${props =>
|
${(props) =>
|
||||||
props.$loading
|
props.$loading
|
||||||
? css`
|
? css`
|
||||||
background: ${props.theme.colors.bg.primary};
|
background: ${props.theme.colors.bg.primary};
|
||||||
@ -183,7 +183,7 @@ export const TaskDetailsTitleWrapper = styled.div<{ $loading?: boolean }>`
|
|||||||
margin: 8px 0 4px 0;
|
margin: 8px 0 4px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
${props => props.$loading && `background: ${props.theme.colors.bg.primary};`}
|
${(props) => props.$loading && `background: ${props.theme.colors.bg.primary};`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TaskDetailsTitle = styled(TextareaAutosize)<{ $loading?: boolean }>`
|
export const TaskDetailsTitle = styled(TextareaAutosize)<{ $loading?: boolean }>`
|
||||||
@ -201,7 +201,7 @@ export const TaskDetailsTitle = styled(TextareaAutosize)<{ $loading?: boolean }>
|
|||||||
&:disabled {
|
&:disabled {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
${props =>
|
${(props) =>
|
||||||
props.$loading
|
props.$loading
|
||||||
? css`
|
? css`
|
||||||
background-image: linear-gradient(90deg, ${defaultBaseColor}, ${defaultHighlightColor}, ${defaultBaseColor});
|
background-image: linear-gradient(90deg, ${defaultBaseColor}, ${defaultHighlightColor}, ${defaultBaseColor});
|
||||||
@ -226,7 +226,7 @@ export const DueDateTitle = styled.div`
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
min-height: 24px;
|
min-height: 24px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
color: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@ -237,7 +237,7 @@ export const AssignedUsersSection = styled.div`
|
|||||||
padding-right: 32px;
|
padding-right: 32px;
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
padding-bottom: 24px;
|
padding-bottom: 24px;
|
||||||
border-bottom: 1px solid ${props => props.theme.colors.alternate};
|
border-bottom: 1px solid ${(props) => props.theme.colors.alternate};
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
`;
|
`;
|
||||||
@ -255,10 +255,10 @@ export const AssignUserIcon = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid ${props => mixin.rgba(props.theme.colors.text.secondary, 0.75)};
|
border: 1px solid ${(props) => mixin.rgba(props.theme.colors.text.secondary, 0.75)};
|
||||||
}
|
}
|
||||||
&:hover svg {
|
&:hover svg {
|
||||||
fill: ${props => mixin.rgba(props.theme.colors.text.secondary, 0.75)};
|
fill: ${(props) => mixin.rgba(props.theme.colors.text.secondary, 0.75)};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -273,17 +273,17 @@ export const AssignUsersButton = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
&:hover {
|
&:hover {
|
||||||
border: 1px solid ${props => mixin.darken(props.theme.colors.alternate, 0.15)};
|
border: 1px solid ${(props) => mixin.darken(props.theme.colors.alternate, 0.15)};
|
||||||
}
|
}
|
||||||
&:hover ${AssignUserIcon} {
|
&:hover ${AssignUserIcon} {
|
||||||
border: 1px solid ${props => props.theme.colors.alternate};
|
border: 1px solid ${(props) => props.theme.colors.alternate};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AssignUserLabel = styled.span`
|
export const AssignUserLabel = styled.span`
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
line-height: 15px;
|
line-height: 15px;
|
||||||
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
color: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ExtraActionsSection = styled.div`
|
export const ExtraActionsSection = styled.div`
|
||||||
@ -295,7 +295,7 @@ export const ExtraActionsSection = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionButtonsTitle = styled.h3`
|
export const ActionButtonsTitle = styled.h3`
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
@ -305,7 +305,7 @@ export const ActionButton = styled(Button)`
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
margin-left: -10px;
|
margin-left: -10px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.5)};
|
background: ${(props) => mixin.rgba(props.theme.colors.bg.primary, 0.5)};
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: transform 0.2s ease;
|
transition: transform 0.2s ease;
|
||||||
& span {
|
& span {
|
||||||
@ -314,7 +314,7 @@ export const ActionButton = styled(Button)`
|
|||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transform: translateX(4px);
|
transform: translateX(4px);
|
||||||
background: ${props => mixin.rgba(props.theme.colors.bg.primary, 0.75)};
|
background: ${(props) => mixin.rgba(props.theme.colors.bg.primary, 0.75)};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -333,10 +333,10 @@ export const HeaderActionIcon = styled.div`
|
|||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
svg {
|
svg {
|
||||||
fill: ${props => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
fill: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.75)};
|
||||||
}
|
}
|
||||||
&:hover svg {
|
&:hover svg {
|
||||||
fill: ${props => mixin.rgba(props.theme.colors.primary, 0.75)});
|
fill: ${(props) => mixin.rgba(props.theme.colors.primary, 0.75)});
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -393,7 +393,7 @@ export const MetaDetail = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const MetaDetailTitle = styled.h3`
|
export const MetaDetailTitle = styled.h3`
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
@ -412,7 +412,7 @@ export const MetaDetailContent = styled.div`
|
|||||||
`;
|
`;
|
||||||
export const TaskDetailsAddLabel = styled.div`
|
export const TaskDetailsAddLabel = styled.div`
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
|
background: ${(props) => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
@ -427,7 +427,7 @@ export const TaskDetailsAddLabelIcon = styled.div`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background: ${props => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
|
background: ${(props) => mixin.darken(props.theme.colors.bg.secondary, 0.15)};
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
@ -443,7 +443,7 @@ export const TaskDetailLabel = styled.div<{ color: string }>`
|
|||||||
&:hover {
|
&:hover {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
background-color: ${props => props.color};
|
background-color: ${(props) => props.color};
|
||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -496,17 +496,22 @@ export const TabBarSection = styled.div`
|
|||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
padding-left: 23px;
|
padding-left: 23px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
min-height: 35px;
|
min-height: 35px;
|
||||||
border-bottom: 1px solid #414561;
|
border-bottom: 1px solid #414561;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TabBarItem = styled.div`
|
export const TabBarItem = styled.div`
|
||||||
box-shadow: inset 0 -2px ${props => props.theme.colors.primary};
|
box-shadow: inset 0 -2px ${(props) => props.theme.colors.primary};
|
||||||
padding: 12px 7px 14px 7px;
|
padding: 12px 7px 14px 7px;
|
||||||
margin-bottom: -1px;
|
margin-bottom: -1px;
|
||||||
margin-right: 36px;
|
margin-right: 36px;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TabBarButton = styled(Button)`
|
||||||
|
padding: 6px 12px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CommentContainer = styled.div`
|
export const CommentContainer = styled.div`
|
||||||
@ -542,13 +547,13 @@ export const CommentTextArea = styled(TextareaAutosize)<{ $showCommentActions: b
|
|||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
background: #1f243e;
|
background: #1f243e;
|
||||||
border: none;
|
border: none;
|
||||||
transition: max-height 200ms, height 200ms, min-height 200ms;
|
transition: max-height 200ms, height 200ms, min-height 200ms;
|
||||||
min-height: 36px;
|
min-height: 36px;
|
||||||
max-height: 36px;
|
max-height: 36px;
|
||||||
${props =>
|
${(props) =>
|
||||||
props.$showCommentActions
|
props.$showCommentActions
|
||||||
? css`
|
? css`
|
||||||
min-height: 80px;
|
min-height: 80px;
|
||||||
@ -561,7 +566,7 @@ export const CommentTextArea = styled(TextareaAutosize)<{ $showCommentActions: b
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const CommentEditorActions = styled.div<{ visible: boolean }>`
|
export const CommentEditorActions = styled.div<{ visible: boolean }>`
|
||||||
display: ${props => (props.visible ? 'flex' : 'none')};
|
display: ${(props) => (props.visible ? 'flex' : 'none')};
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 5px 5px 5px 9px;
|
padding: 5px 5px 5px 9px;
|
||||||
border-top: 1px solid #414561;
|
border-top: 1px solid #414561;
|
||||||
@ -594,7 +599,7 @@ export const ActivityItemCommentAction = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
svg {
|
svg {
|
||||||
fill: ${props => props.theme.colors.text.primary} !important;
|
fill: ${(props) => props.theme.colors.text.primary} !important;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -614,7 +619,7 @@ export const ActivityItemHeader = styled.div<{ editable?: boolean }>`
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
${props => props.editable && 'width: 100%;'}
|
${(props) => props.editable && 'width: 100%;'}
|
||||||
`;
|
`;
|
||||||
export const ActivityItemHeaderUser = styled(TaskAssignee)`
|
export const ActivityItemHeaderUser = styled(TaskAssignee)`
|
||||||
align-items: start;
|
align-items: start;
|
||||||
@ -623,7 +628,7 @@ export const ActivityItemHeaderUser = styled(TaskAssignee)`
|
|||||||
export const ActivityItemHeaderTitle = styled.div`
|
export const ActivityItemHeaderTitle = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -634,8 +639,8 @@ export const ActivityItemHeaderTitleName = styled.span`
|
|||||||
|
|
||||||
export const ActivityItemTimestamp = styled.span<{ margin: number }>`
|
export const ActivityItemTimestamp = styled.span<{ margin: number }>`
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.65)};
|
color: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.65)};
|
||||||
margin-left: ${props => props.margin}px;
|
margin-left: ${(props) => props.margin}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActivityItemDetails = styled.div`
|
export const ActivityItemDetails = styled.div`
|
||||||
@ -649,11 +654,11 @@ export const ActivityItemComment = styled.div<{ editable: boolean }>`
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
${mixin.boxShadowCard}
|
${mixin.boxShadowCard}
|
||||||
position: relative;
|
position: relative;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
background-color: ${props => mixin.darken(props.theme.colors.alternate, 0.1)};
|
background-color: ${(props) => mixin.darken(props.theme.colors.alternate, 0.1)};
|
||||||
${props => props.editable && 'width: 100%;'}
|
${(props) => props.editable && 'width: 100%;'}
|
||||||
|
|
||||||
& span {
|
& span {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -683,7 +688,7 @@ export const ActivityItemCommentActions = styled.div`
|
|||||||
|
|
||||||
export const ActivityItemLog = styled.span`
|
export const ActivityItemLog = styled.span`
|
||||||
margin-left: 2px;
|
margin-left: 2px;
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ViewRawButton = styled.button`
|
export const ViewRawButton = styled.button`
|
||||||
@ -694,9 +699,9 @@ export const ViewRawButton = styled.button`
|
|||||||
right: 4px;
|
right: 4px;
|
||||||
bottom: -24px;
|
bottom: -24px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: ${props => mixin.rgba(props.theme.colors.text.primary, 0.25)};
|
color: ${(props) => mixin.rgba(props.theme.colors.text.primary, 0.25)};
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${props => props.theme.colors.text.primary};
|
color: ${(props) => props.theme.colors.text.primary};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ import {
|
|||||||
ActivityItemHeaderTitle,
|
ActivityItemHeaderTitle,
|
||||||
ActivityItemHeaderTitleName,
|
ActivityItemHeaderTitleName,
|
||||||
ActivityItemComment,
|
ActivityItemComment,
|
||||||
|
TabBarButton,
|
||||||
} from './Styles';
|
} from './Styles';
|
||||||
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
|
import Checklist, { ChecklistItem, ChecklistItems } from '../Checklist';
|
||||||
import onDragEnd from './onDragEnd';
|
import onDragEnd from './onDragEnd';
|
||||||
|
@ -69,6 +69,12 @@ export type ChecklistBadge = {
|
|||||||
total: Scalars['Int'];
|
total: Scalars['Int'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CommentsBadge = {
|
||||||
|
__typename?: 'CommentsBadge';
|
||||||
|
total: Scalars['Int'];
|
||||||
|
unread: Scalars['Boolean'];
|
||||||
|
};
|
||||||
|
|
||||||
export type CreateTaskChecklist = {
|
export type CreateTaskChecklist = {
|
||||||
taskID: Scalars['UUID'];
|
taskID: Scalars['UUID'];
|
||||||
name: Scalars['String'];
|
name: Scalars['String'];
|
||||||
@ -1027,6 +1033,7 @@ export type TaskActivityData = {
|
|||||||
export type TaskBadges = {
|
export type TaskBadges = {
|
||||||
__typename?: 'TaskBadges';
|
__typename?: 'TaskBadges';
|
||||||
checklist?: Maybe<ChecklistBadge>;
|
checklist?: Maybe<ChecklistBadge>;
|
||||||
|
comments?: Maybe<CommentsBadge>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TaskChecklist = {
|
export type TaskChecklist = {
|
||||||
@ -1592,6 +1599,9 @@ export type TaskFieldsFragment = (
|
|||||||
& { checklist?: Maybe<(
|
& { checklist?: Maybe<(
|
||||||
{ __typename?: 'ChecklistBadge' }
|
{ __typename?: 'ChecklistBadge' }
|
||||||
& Pick<ChecklistBadge, 'complete' | 'total'>
|
& Pick<ChecklistBadge, 'complete' | 'total'>
|
||||||
|
)>, comments?: Maybe<(
|
||||||
|
{ __typename?: 'CommentsBadge' }
|
||||||
|
& Pick<CommentsBadge, 'unread' | 'total'>
|
||||||
)> }
|
)> }
|
||||||
), taskGroup: (
|
), taskGroup: (
|
||||||
{ __typename?: 'TaskGroup' }
|
{ __typename?: 'TaskGroup' }
|
||||||
@ -2693,6 +2703,10 @@ export const TaskFieldsFragmentDoc = gql`
|
|||||||
complete
|
complete
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
|
comments {
|
||||||
|
unread
|
||||||
|
total
|
||||||
|
}
|
||||||
}
|
}
|
||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
|
@ -15,6 +15,10 @@ const TASK_FRAGMENT = gql`
|
|||||||
complete
|
complete
|
||||||
total
|
total
|
||||||
}
|
}
|
||||||
|
comments {
|
||||||
|
unread
|
||||||
|
total
|
||||||
|
}
|
||||||
}
|
}
|
||||||
taskGroup {
|
taskGroup {
|
||||||
id
|
id
|
||||||
|
12
frontend/src/shared/icons/Bubble.tsx
Normal file
12
frontend/src/shared/icons/Bubble.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Icon, { IconProps } from './Icon';
|
||||||
|
|
||||||
|
const Bubble: React.FC<IconProps> = ({ width = '16px', height = '16px', className }) => {
|
||||||
|
return (
|
||||||
|
<Icon width={width} height={height} className={className} viewBox="0 0 576 512">
|
||||||
|
<path d="M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160zm122 220c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z" />
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Bubble;
|
@ -1,6 +1,7 @@
|
|||||||
import Cross from './Cross';
|
import Cross from './Cross';
|
||||||
import Cog from './Cog';
|
import Cog from './Cog';
|
||||||
import Cogs from './Cogs';
|
import Cogs from './Cogs';
|
||||||
|
import Bubble from './Bubble';
|
||||||
import ArrowDown from './ArrowDown';
|
import ArrowDown from './ArrowDown';
|
||||||
import CheckCircleOutline from './CheckCircleOutline';
|
import CheckCircleOutline from './CheckCircleOutline';
|
||||||
import Briefcase from './Briefcase';
|
import Briefcase from './Briefcase';
|
||||||
@ -110,5 +111,6 @@ export {
|
|||||||
Briefcase,
|
Briefcase,
|
||||||
DotCircle,
|
DotCircle,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Bubble,
|
||||||
Cogs,
|
Cogs,
|
||||||
};
|
};
|
||||||
|
5
frontend/src/types.d.ts
vendored
5
frontend/src/types.d.ts
vendored
@ -59,8 +59,13 @@ type ChecklistBadge = {
|
|||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CommentsBadge = {
|
||||||
|
total: number;
|
||||||
|
unread: boolean;
|
||||||
|
};
|
||||||
type TaskBadges = {
|
type TaskBadges = {
|
||||||
checklist?: ChecklistBadge | null;
|
checklist?: ChecklistBadge | null;
|
||||||
|
comments?: CommentsBadge | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TaskActivityData = {
|
type TaskActivityData = {
|
||||||
|
@ -74,6 +74,7 @@ type Querier interface {
|
|||||||
GetAssignedTasksDueDateForUserID(ctx context.Context, arg GetAssignedTasksDueDateForUserIDParams) ([]Task, error)
|
GetAssignedTasksDueDateForUserID(ctx context.Context, arg GetAssignedTasksDueDateForUserIDParams) ([]Task, error)
|
||||||
GetAssignedTasksProjectForUserID(ctx context.Context, arg GetAssignedTasksProjectForUserIDParams) ([]Task, error)
|
GetAssignedTasksProjectForUserID(ctx context.Context, arg GetAssignedTasksProjectForUserIDParams) ([]Task, error)
|
||||||
GetAuthTokenByID(ctx context.Context, tokenID uuid.UUID) (AuthToken, error)
|
GetAuthTokenByID(ctx context.Context, tokenID uuid.UUID) (AuthToken, error)
|
||||||
|
GetCommentCountForTask(ctx context.Context, taskID uuid.UUID) (int64, error)
|
||||||
GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskComment, error)
|
GetCommentsForTaskID(ctx context.Context, taskID uuid.UUID) ([]TaskComment, error)
|
||||||
GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
|
GetConfirmTokenByEmail(ctx context.Context, email string) (UserAccountConfirmToken, error)
|
||||||
GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
|
GetConfirmTokenByID(ctx context.Context, confirmTokenID uuid.UUID) (UserAccountConfirmToken, error)
|
||||||
|
@ -95,3 +95,6 @@ SELECT task.* FROM task_assigned
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
ORDER BY task.due_date DESC, task_group.project_id DESC;
|
ORDER BY task.due_date DESC, task_group.project_id DESC;
|
||||||
|
|
||||||
|
-- name: GetCommentCountForTask :one
|
||||||
|
SELECT COUNT(*) FROM task_comment WHERE task_id = $1;
|
||||||
|
@ -316,6 +316,17 @@ func (q *Queries) GetAssignedTasksProjectForUserID(ctx context.Context, arg GetA
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getCommentCountForTask = `-- name: GetCommentCountForTask :one
|
||||||
|
SELECT COUNT(*) FROM task_comment WHERE task_id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetCommentCountForTask(ctx context.Context, taskID uuid.UUID) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getCommentCountForTask, taskID)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
const getCommentsForTaskID = `-- name: GetCommentsForTaskID :many
|
const getCommentsForTaskID = `-- name: GetCommentsForTaskID :many
|
||||||
SELECT task_comment_id, task_id, created_at, updated_at, created_by, pinned, message FROM task_comment WHERE task_id = $1 ORDER BY created_at
|
SELECT task_comment_id, task_id, created_at, updated_at, created_by, pinned, message FROM task_comment WHERE task_id = $1 ORDER BY created_at
|
||||||
`
|
`
|
||||||
|
@ -73,6 +73,11 @@ type ComplexityRoot struct {
|
|||||||
Total func(childComplexity int) int
|
Total func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CommentsBadge struct {
|
||||||
|
Total func(childComplexity int) int
|
||||||
|
Unread func(childComplexity int) int
|
||||||
|
}
|
||||||
|
|
||||||
CreateTaskCommentPayload struct {
|
CreateTaskCommentPayload struct {
|
||||||
Comment func(childComplexity int) int
|
Comment func(childComplexity int) int
|
||||||
TaskID func(childComplexity int) int
|
TaskID func(childComplexity int) int
|
||||||
@ -419,6 +424,7 @@ type ComplexityRoot struct {
|
|||||||
|
|
||||||
TaskBadges struct {
|
TaskBadges struct {
|
||||||
Checklist func(childComplexity int) int
|
Checklist func(childComplexity int) int
|
||||||
|
Comments func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskChecklist struct {
|
TaskChecklist struct {
|
||||||
@ -768,6 +774,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.ChecklistBadge.Total(childComplexity), true
|
return e.complexity.ChecklistBadge.Total(childComplexity), true
|
||||||
|
|
||||||
|
case "CommentsBadge.total":
|
||||||
|
if e.complexity.CommentsBadge.Total == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.CommentsBadge.Total(childComplexity), true
|
||||||
|
|
||||||
|
case "CommentsBadge.unread":
|
||||||
|
if e.complexity.CommentsBadge.Unread == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.CommentsBadge.Unread(childComplexity), true
|
||||||
|
|
||||||
case "CreateTaskCommentPayload.comment":
|
case "CreateTaskCommentPayload.comment":
|
||||||
if e.complexity.CreateTaskCommentPayload.Comment == nil {
|
if e.complexity.CreateTaskCommentPayload.Comment == nil {
|
||||||
break
|
break
|
||||||
@ -2553,6 +2573,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.TaskBadges.Checklist(childComplexity), true
|
return e.complexity.TaskBadges.Checklist(childComplexity), true
|
||||||
|
|
||||||
|
case "TaskBadges.comments":
|
||||||
|
if e.complexity.TaskBadges.Comments == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.TaskBadges.Comments(childComplexity), true
|
||||||
|
|
||||||
case "TaskChecklist.id":
|
case "TaskChecklist.id":
|
||||||
if e.complexity.TaskChecklist.ID == nil {
|
if e.complexity.TaskChecklist.ID == nil {
|
||||||
break
|
break
|
||||||
@ -3212,8 +3239,14 @@ type ChecklistBadge {
|
|||||||
total: Int!
|
total: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentsBadge {
|
||||||
|
total: Int!
|
||||||
|
unread: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type TaskBadges {
|
type TaskBadges {
|
||||||
checklist: ChecklistBadge
|
checklist: ChecklistBadge
|
||||||
|
comments: CommentsBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
type CausedBy {
|
type CausedBy {
|
||||||
@ -5280,6 +5313,76 @@ func (ec *executionContext) _ChecklistBadge_total(ctx context.Context, field gra
|
|||||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _CommentsBadge_total(ctx context.Context, field graphql.CollectedField, obj *CommentsBadge) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "CommentsBadge",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Total, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _CommentsBadge_unread(ctx context.Context, field graphql.CollectedField, obj *CommentsBadge) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "CommentsBadge",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Unread, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
if !graphql.HasFieldError(ctx, fc) {
|
||||||
|
ec.Errorf(ctx, "must not be null")
|
||||||
|
}
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(bool)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _CreateTaskCommentPayload_taskID(ctx context.Context, field graphql.CollectedField, obj *CreateTaskCommentPayload) (ret graphql.Marshaler) {
|
func (ec *executionContext) _CreateTaskCommentPayload_taskID(ctx context.Context, field graphql.CollectedField, obj *CreateTaskCommentPayload) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -14831,6 +14934,38 @@ func (ec *executionContext) _TaskBadges_checklist(ctx context.Context, field gra
|
|||||||
return ec.marshalOChecklistBadge2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐChecklistBadge(ctx, field.Selections, res)
|
return ec.marshalOChecklistBadge2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐChecklistBadge(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _TaskBadges_comments(ctx context.Context, field graphql.CollectedField, obj *TaskBadges) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "TaskBadges",
|
||||||
|
Field: field,
|
||||||
|
Args: nil,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Comments, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*CommentsBadge)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOCommentsBadge2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCommentsBadge(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _TaskChecklist_id(ctx context.Context, field graphql.CollectedField, obj *db.TaskChecklist) (ret graphql.Marshaler) {
|
func (ec *executionContext) _TaskChecklist_id(ctx context.Context, field graphql.CollectedField, obj *db.TaskChecklist) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
@ -20124,6 +20259,38 @@ func (ec *executionContext) _ChecklistBadge(ctx context.Context, sel ast.Selecti
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var commentsBadgeImplementors = []string{"CommentsBadge"}
|
||||||
|
|
||||||
|
func (ec *executionContext) _CommentsBadge(ctx context.Context, sel ast.SelectionSet, obj *CommentsBadge) graphql.Marshaler {
|
||||||
|
fields := graphql.CollectFields(ec.OperationContext, sel, commentsBadgeImplementors)
|
||||||
|
|
||||||
|
out := graphql.NewFieldSet(fields)
|
||||||
|
var invalids uint32
|
||||||
|
for i, field := range fields {
|
||||||
|
switch field.Name {
|
||||||
|
case "__typename":
|
||||||
|
out.Values[i] = graphql.MarshalString("CommentsBadge")
|
||||||
|
case "total":
|
||||||
|
out.Values[i] = ec._CommentsBadge_total(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "unread":
|
||||||
|
out.Values[i] = ec._CommentsBadge_unread(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.Dispatch()
|
||||||
|
if invalids > 0 {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
var createTaskCommentPayloadImplementors = []string{"CreateTaskCommentPayload"}
|
var createTaskCommentPayloadImplementors = []string{"CreateTaskCommentPayload"}
|
||||||
|
|
||||||
func (ec *executionContext) _CreateTaskCommentPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateTaskCommentPayload) graphql.Marshaler {
|
func (ec *executionContext) _CreateTaskCommentPayload(ctx context.Context, sel ast.SelectionSet, obj *CreateTaskCommentPayload) graphql.Marshaler {
|
||||||
@ -22580,6 +22747,8 @@ func (ec *executionContext) _TaskBadges(ctx context.Context, sel ast.SelectionSe
|
|||||||
out.Values[i] = graphql.MarshalString("TaskBadges")
|
out.Values[i] = graphql.MarshalString("TaskBadges")
|
||||||
case "checklist":
|
case "checklist":
|
||||||
out.Values[i] = ec._TaskBadges_checklist(ctx, field, obj)
|
out.Values[i] = ec._TaskBadges_checklist(ctx, field, obj)
|
||||||
|
case "comments":
|
||||||
|
out.Values[i] = ec._TaskBadges_comments(ctx, field, obj)
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
@ -26306,6 +26475,13 @@ func (ec *executionContext) marshalOChecklistBadge2ᚖgithubᚗcomᚋjordanknott
|
|||||||
return ec._ChecklistBadge(ctx, sel, v)
|
return ec._ChecklistBadge(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOCommentsBadge2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCommentsBadge(ctx context.Context, sel ast.SelectionSet, v *CommentsBadge) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._CommentsBadge(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalOCreateTaskComment2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateTaskComment(ctx context.Context, v interface{}) (*CreateTaskComment, error) {
|
func (ec *executionContext) unmarshalOCreateTaskComment2ᚖgithubᚗcomᚋjordanknottᚋtaskcafeᚋinternalᚋgraphᚐCreateTaskComment(ctx context.Context, v interface{}) (*CreateTaskComment, error) {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -33,6 +33,11 @@ type ChecklistBadge struct {
|
|||||||
Total int `json:"total"`
|
Total int `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentsBadge struct {
|
||||||
|
Total int `json:"total"`
|
||||||
|
Unread bool `json:"unread"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateTaskChecklist struct {
|
type CreateTaskChecklist struct {
|
||||||
TaskID uuid.UUID `json:"taskID"`
|
TaskID uuid.UUID `json:"taskID"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -431,6 +436,7 @@ type TaskActivityData struct {
|
|||||||
|
|
||||||
type TaskBadges struct {
|
type TaskBadges struct {
|
||||||
Checklist *ChecklistBadge `json:"checklist"`
|
Checklist *ChecklistBadge `json:"checklist"`
|
||||||
|
Comments *CommentsBadge `json:"comments"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskPositionUpdate struct {
|
type TaskPositionUpdate struct {
|
||||||
|
@ -138,8 +138,14 @@ type ChecklistBadge {
|
|||||||
total: Int!
|
total: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentsBadge {
|
||||||
|
total: Int!
|
||||||
|
unread: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type TaskBadges {
|
type TaskBadges {
|
||||||
checklist: ChecklistBadge
|
checklist: ChecklistBadge
|
||||||
|
comments: CommentsBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
type CausedBy {
|
type CausedBy {
|
||||||
@ -994,4 +1000,3 @@ type DeleteUserAccountPayload {
|
|||||||
ok: Boolean!
|
ok: Boolean!
|
||||||
userAccount: UserAccount!
|
userAccount: UserAccount!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1721,8 +1721,9 @@ func (r *taskResolver) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &TaskBadges{}, err
|
return &TaskBadges{}, err
|
||||||
}
|
}
|
||||||
if len(checklists) == 0 {
|
comments, err := r.Repository.GetCommentCountForTask(ctx, obj.TaskID)
|
||||||
return &TaskBadges{Checklist: nil}, err
|
if err != nil {
|
||||||
|
return &TaskBadges{}, err
|
||||||
}
|
}
|
||||||
complete := 0
|
complete := 0
|
||||||
total := 0
|
total := 0
|
||||||
@ -1738,10 +1739,15 @@ func (r *taskResolver) Badges(ctx context.Context, obj *db.Task) (*TaskBadges, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if complete == 0 && total == 0 {
|
var taskChecklist *ChecklistBadge
|
||||||
return &TaskBadges{Checklist: nil}, nil
|
if total != 0 {
|
||||||
|
taskChecklist = &ChecklistBadge{Total: total, Complete: complete}
|
||||||
}
|
}
|
||||||
return &TaskBadges{Checklist: &ChecklistBadge{Total: total, Complete: complete}}, nil
|
var taskComments *CommentsBadge
|
||||||
|
if comments != 0 {
|
||||||
|
taskComments = &CommentsBadge{Total: int(comments), Unread: false}
|
||||||
|
}
|
||||||
|
return &TaskBadges{Checklist: taskChecklist, Comments: taskComments}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *taskResolver) Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error) {
|
func (r *taskResolver) Activity(ctx context.Context, obj *db.Task) ([]db.TaskActivity, error) {
|
||||||
|
@ -138,8 +138,14 @@ type ChecklistBadge {
|
|||||||
total: Int!
|
total: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CommentsBadge {
|
||||||
|
total: Int!
|
||||||
|
unread: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
type TaskBadges {
|
type TaskBadges {
|
||||||
checklist: ChecklistBadge
|
checklist: ChecklistBadge
|
||||||
|
comments: CommentsBadge
|
||||||
}
|
}
|
||||||
|
|
||||||
type CausedBy {
|
type CausedBy {
|
||||||
|
Loading…
Reference in New Issue
Block a user