1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-07-27 17:19:43 +02:00

Project managers, board members, auto-update after reconnection, refactoring

This commit is contained in:
Maksim Eltyshev 2021-06-24 01:05:22 +05:00
parent 7956503a46
commit fe91b5241e
478 changed files with 21226 additions and 19495 deletions

View file

@ -14,7 +14,8 @@ const Actions = React.memo(
items,
isFetching,
isAllFetched,
isEditable,
canEdit,
canEditAllComments,
onFetch,
onCommentCreate,
onCommentUpdate,
@ -38,13 +39,15 @@ const Actions = React.memo(
return (
<>
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="comment outline" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.addComment')}</div>
<CommentAdd onCreate={onCommentCreate} />
{canEdit && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="comment outline" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.addComment')}</div>
<CommentAdd onCreate={onCommentCreate} />
</div>
</div>
</div>
)}
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="list ul" className={styles.moduleIcon} />
@ -59,7 +62,7 @@ const Actions = React.memo(
createdAt={item.createdAt}
isPersisted={item.isPersisted}
user={item.user}
isEditable={isEditable}
canEdit={(item.user.isCurrent && canEdit) || canEditAllComments}
onUpdate={(data) => handleCommentUpdate(item.id, data)}
onDelete={() => handleCommentDelete(item.id)}
/>
@ -91,7 +94,8 @@ Actions.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
isFetching: PropTypes.bool.isRequired,
isAllFetched: PropTypes.bool.isRequired,
isEditable: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
canEditAllComments: PropTypes.bool.isRequired,
onFetch: PropTypes.func.isRequired,
onCommentCreate: PropTypes.func.isRequired,
onCommentUpdate: PropTypes.func.isRequired,

View file

@ -12,7 +12,7 @@ import DeletePopup from '../../DeletePopup';
import styles from './ItemComment.module.scss';
const ItemComment = React.memo(
({ data, createdAt, isPersisted, user, isEditable, onUpdate, onDelete }) => {
({ data, createdAt, isPersisted, user, canEdit, onUpdate, onDelete }) => {
const [t] = useTranslation();
const commentEdit = useRef(null);
@ -38,17 +38,17 @@ const ItemComment = React.memo(
</div>
<CommentEdit ref={commentEdit} defaultData={data} onUpdate={onUpdate}>
<>
<Markdown source={data.text} linkTarget="_blank" className={styles.text} />
<Comment.Actions>
{user.isCurrent && (
<Markdown linkTarget="_blank" className={styles.text}>
{data.text}
</Markdown>
{canEdit && (
<Comment.Actions>
<Comment.Action
as="button"
content={t('action.edit')}
disabled={!isPersisted}
onClick={handleEditClick}
/>
)}
{(user.isCurrent || isEditable) && (
<DeletePopup
title={t('common.deleteComment', {
context: 'title',
@ -63,8 +63,8 @@ const ItemComment = React.memo(
disabled={!isPersisted}
/>
</DeletePopup>
)}
</Comment.Actions>
</Comment.Actions>
)}
</>
</CommentEdit>
</div>
@ -78,7 +78,7 @@ ItemComment.propTypes = {
createdAt: PropTypes.instanceOf(Date).isRequired,
isPersisted: PropTypes.bool.isRequired,
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
isEditable: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};

View file

@ -10,7 +10,7 @@
position: absolute;
text-align: center;
width: 100%;
z-index: 1;
z-index: 2000;
}
.wrapper {

View file

@ -16,7 +16,7 @@ import User from '../User';
import Label from '../Label';
import DueDate from '../DueDate';
import Timer from '../Timer';
import ProjectMembershipsPopup from '../ProjectMembershipsPopup';
import BoardMembershipsPopup from '../BoardMembershipsPopup';
import LabelsPopup from '../LabelsPopup';
import DueDateEditPopup from '../DueDateEditPopup';
import TimerEditPopup from '../TimerEditPopup';
@ -43,9 +43,10 @@ const CardModal = React.memo(
attachments,
actions,
allProjectsToLists,
allProjectMemberships,
allBoardMemberships,
allLabels,
isEditable,
canEdit,
canEditAllCommentActions,
onUpdate,
onMove,
onTransfer,
@ -126,137 +127,163 @@ const CardModal = React.memo(
const userIds = users.map((user) => user.id);
const labelIds = labels.map((label) => label.id);
return (
<Modal open closeIcon size="small" centered={false} onClose={onClose}>
<AttachmentAddZone onCreate={onAttachmentCreate}>
<Grid className={styles.grid}>
<Grid.Row className={styles.headerPadding}>
<Grid.Column width={16} className={styles.headerPadding}>
<div className={styles.headerWrapper}>
<Icon name="list alternate outline" className={styles.moduleIcon} />
<div className={styles.headerTitle}>
<NameField defaultValue={name} onUpdate={handleNameUpdate} />
</div>
</div>
</Grid.Column>
</Grid.Row>
<Grid.Row className={styles.modalPadding}>
<Grid.Column width={12} className={styles.contentPadding}>
{(users.length > 0 || labels.length > 0 || dueDate || timer) && (
<div className={styles.moduleWrapper}>
{users.length > 0 && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.members', {
context: 'title',
})}
</div>
{users.map((user) => (
<span key={user.id} className={styles.attachment}>
<ProjectMembershipsPopup
items={allProjectMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<User name={user.name} avatarUrl={user.avatarUrl} />
</ProjectMembershipsPopup>
</span>
))}
<ProjectMembershipsPopup
items={allProjectMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<button
type="button"
className={classNames(styles.attachment, styles.dueDate)}
const contentNode = (
<Grid className={styles.grid}>
<Grid.Row className={styles.headerPadding}>
<Grid.Column width={16} className={styles.headerPadding}>
<div className={styles.headerWrapper}>
<Icon name="list alternate outline" className={styles.moduleIcon} />
<div className={styles.headerTitleWrapper}>
{canEdit ? (
<NameField defaultValue={name} onUpdate={handleNameUpdate} />
) : (
<div className={styles.headerTitle}>{name}</div>
)}
</div>
</div>
</Grid.Column>
</Grid.Row>
<Grid.Row className={styles.modalPadding}>
<Grid.Column width={canEdit ? 12 : 16} className={styles.contentPadding}>
{(users.length > 0 || labels.length > 0 || dueDate || timer) && (
<div className={styles.moduleWrapper}>
{users.length > 0 && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.members', {
context: 'title',
})}
</div>
{users.map((user) => (
<span key={user.id} className={styles.attachment}>
{canEdit ? (
<BoardMembershipsPopup
items={allBoardMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<Icon name="add" size="small" className={styles.addAttachment} />
</button>
</ProjectMembershipsPopup>
</div>
)}
{labels.length > 0 && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.labels', {
context: 'title',
})}
</div>
{labels.map((label) => (
<span key={label.id} className={styles.attachment}>
<LabelsPopup
key={label.id}
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Label name={label.name} color={label.color} />
</LabelsPopup>
</span>
))}
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
<User name={user.name} avatarUrl={user.avatarUrl} />
</BoardMembershipsPopup>
) : (
<User name={user.name} avatarUrl={user.avatarUrl} />
)}
</span>
))}
{canEdit && (
<BoardMembershipsPopup
items={allBoardMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<button
type="button"
className={classNames(styles.attachment, styles.dueDate)}
>
<button
type="button"
className={classNames(styles.attachment, styles.dueDate)}
>
<Icon name="add" size="small" className={styles.addAttachment} />
</button>
</LabelsPopup>
</div>
)}
{dueDate && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.dueDate', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate value={dueDate} />
</DueDateEditPopup>
</span>
</div>
)}
{timer && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.timer', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Timer startedAt={timer.startedAt} total={timer.total} />
</TimerEditPopup>
</span>
</div>
<Icon name="add" size="small" className={styles.addAttachment} />
</button>
</BoardMembershipsPopup>
)}
</div>
)}
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="align justify" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.description')}</div>
{labels.length > 0 && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.labels', {
context: 'title',
})}
</div>
{labels.map((label) => (
<span key={label.id} className={styles.attachment}>
{canEdit ? (
<LabelsPopup
key={label.id}
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Label name={label.name} color={label.color} />
</LabelsPopup>
) : (
<Label name={label.name} color={label.color} />
)}
</span>
))}
{canEdit && (
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<button
type="button"
className={classNames(styles.attachment, styles.dueDate)}
>
<Icon name="add" size="small" className={styles.addAttachment} />
</button>
</LabelsPopup>
)}
</div>
)}
{dueDate && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.dueDate', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
{canEdit ? (
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<DueDate value={dueDate} />
</DueDateEditPopup>
) : (
<DueDate value={dueDate} />
)}
</span>
</div>
)}
{timer && (
<div className={styles.attachments}>
<div className={styles.text}>
{t('common.timer', {
context: 'title',
})}
</div>
<span className={styles.attachment}>
{canEdit ? (
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Timer startedAt={timer.startedAt} total={timer.total} />
</TimerEditPopup>
) : (
<Timer startedAt={timer.startedAt} total={timer.total} />
)}
</span>
</div>
)}
</div>
)}
{(description || canEdit) && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="align justify" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.description')}</div>
{canEdit ? (
<DescriptionEdit defaultValue={description} onUpdate={handleDescriptionUpdate}>
{description ? (
<button type="button" className={styles.descriptionText}>
<Markdown linkStopPropagation source={description} linkTarget="_blank" />
<Markdown linkStopPropagation linkTarget="_blank">
{description}
</Markdown>
</button>
) : (
<button type="button" className={styles.descriptionButton}>
@ -266,142 +293,164 @@ const CardModal = React.memo(
</button>
)}
</DescriptionEdit>
</div>
</div>
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="check square outline" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.tasks')}</div>
<Tasks
items={tasks}
onCreate={onTaskCreate}
onUpdate={onTaskUpdate}
onDelete={onTaskDelete}
/>
</div>
</div>
{attachments.length > 0 && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="attach" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.attachments')}</div>
<Attachments
items={attachments}
onUpdate={onAttachmentUpdate}
onDelete={onAttachmentDelete}
onCoverUpdate={handleCoverUpdate}
/>
) : (
<div className={styles.descriptionText}>
<Markdown linkStopPropagation linkTarget="_blank">
{description}
</Markdown>
</div>
</div>
)}
<Actions
items={actions}
isFetching={isActionsFetching}
isAllFetched={isAllActionsFetched}
isEditable={isEditable}
onFetch={onActionsFetch}
onCommentCreate={onCommentActionCreate}
onCommentUpdate={onCommentActionUpdate}
onCommentDelete={onCommentActionDelete}
/>
</Grid.Column>
<Grid.Column width={4} className={styles.sidebarPadding}>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('action.addToCard')}</span>
<ProjectMembershipsPopup
items={allProjectMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<Button fluid className={styles.actionButton}>
<Icon name="user outline" className={styles.actionIcon} />
{t('common.members')}
</Button>
</ProjectMembershipsPopup>
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Button fluid className={styles.actionButton}>
<Icon name="bookmark outline" className={styles.actionIcon} />
{t('common.labels')}
</Button>
</LabelsPopup>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="calendar check outline" className={styles.actionIcon} />
{t('common.dueDate', {
context: 'title',
})}
</Button>
</DueDateEditPopup>
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="clock outline" className={styles.actionIcon} />
{t('common.timer')}
</Button>
</TimerEditPopup>
<AttachmentAddPopup onCreate={onAttachmentCreate}>
<Button fluid className={styles.actionButton}>
<Icon name="attach" className={styles.actionIcon} />
{t('common.attachment')}
</Button>
</AttachmentAddPopup>
)}
</div>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('common.actions')}</span>
</div>
)}
{(tasks.length > 0 || canEdit) && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="check square outline" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.tasks')}</div>
<Tasks
items={tasks}
canEdit={canEdit}
onCreate={onTaskCreate}
onUpdate={onTaskUpdate}
onDelete={onTaskDelete}
/>
</div>
</div>
)}
{attachments.length > 0 && (
<div className={styles.contentModule}>
<div className={styles.moduleWrapper}>
<Icon name="attach" className={styles.moduleIcon} />
<div className={styles.moduleHeader}>{t('common.attachments')}</div>
<Attachments
items={attachments}
onUpdate={onAttachmentUpdate}
onDelete={onAttachmentDelete}
onCoverUpdate={handleCoverUpdate}
/>
</div>
</div>
)}
<Actions
items={actions}
isFetching={isActionsFetching}
isAllFetched={isAllActionsFetched}
canEdit={canEdit}
canEditAllComments={canEditAllCommentActions}
onFetch={onActionsFetch}
onCommentCreate={onCommentActionCreate}
onCommentUpdate={onCommentActionUpdate}
onCommentDelete={onCommentActionDelete}
/>
</Grid.Column>
{canEdit && (
<Grid.Column width={4} className={styles.sidebarPadding}>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('action.addToCard')}</span>
<BoardMembershipsPopup
items={allBoardMemberships}
currentUserIds={userIds}
onUserSelect={onUserAdd}
onUserDeselect={onUserRemove}
>
<Button fluid className={styles.actionButton}>
<Icon name="user outline" className={styles.actionIcon} />
{t('common.members')}
</Button>
</BoardMembershipsPopup>
<LabelsPopup
items={allLabels}
currentIds={labelIds}
onSelect={onLabelAdd}
onDeselect={onLabelRemove}
onCreate={onLabelCreate}
onUpdate={onLabelUpdate}
onDelete={onLabelDelete}
>
<Button fluid className={styles.actionButton}>
<Icon name="bookmark outline" className={styles.actionIcon} />
{t('common.labels')}
</Button>
</LabelsPopup>
<DueDateEditPopup defaultValue={dueDate} onUpdate={handleDueDateUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="calendar check outline" className={styles.actionIcon} />
{t('common.dueDate', {
context: 'title',
})}
</Button>
</DueDateEditPopup>
<TimerEditPopup defaultValue={timer} onUpdate={handleTimerUpdate}>
<Button fluid className={styles.actionButton}>
<Icon name="clock outline" className={styles.actionIcon} />
{t('common.timer')}
</Button>
</TimerEditPopup>
<AttachmentAddPopup onCreate={onAttachmentCreate}>
<Button fluid className={styles.actionButton}>
<Icon name="attach" className={styles.actionIcon} />
{t('common.attachment')}
</Button>
</AttachmentAddPopup>
</div>
<div className={styles.actions}>
<span className={styles.actionsTitle}>{t('common.actions')}</span>
<Button
fluid
className={styles.actionButton}
onClick={handleToggleSubscriptionClick}
>
<Icon name="paper plane outline" className={styles.actionIcon} />
{isSubscribed ? t('action.unsubscribe') : t('action.subscribe')}
</Button>
<CardMovePopup
projectsToLists={allProjectsToLists}
defaultPath={{
projectId,
boardId,
listId,
}}
onMove={onMove}
onTransfer={onTransfer}
onBoardFetch={onBoardFetch}
>
<Button
fluid
className={styles.actionButton}
onClick={handleToggleSubscriptionClick}
>
<Icon name="paper plane outline" className={styles.actionIcon} />
{isSubscribed ? t('action.unsubscribe') : t('action.subscribe')}
<Icon name="share square outline" className={styles.actionIcon} />
{t('action.move')}
</Button>
<CardMovePopup
projectsToLists={allProjectsToLists}
defaultPath={{
projectId,
boardId,
listId,
}}
onMove={onMove}
onTransfer={onTransfer}
onBoardFetch={onBoardFetch}
>
<Button
fluid
className={styles.actionButton}
onClick={handleToggleSubscriptionClick}
>
<Icon name="share square outline" className={styles.actionIcon} />
{t('action.move')}
</Button>
</CardMovePopup>
<DeletePopup
title={t('common.deleteCard', {
context: 'title',
})}
content={t('common.areYouSureYouWantToDeleteThisCard')}
buttonContent={t('action.deleteCard')}
onConfirm={onDelete}
>
<Button fluid className={styles.actionButton}>
<Icon name="trash alternate outline" className={styles.actionIcon} />
{t('action.delete')}
</Button>
</DeletePopup>
</div>
</Grid.Column>
</Grid.Row>
</Grid>
</AttachmentAddZone>
</CardMovePopup>
<DeletePopup
title={t('common.deleteCard', {
context: 'title',
})}
content={t('common.areYouSureYouWantToDeleteThisCard')}
buttonContent={t('action.deleteCard')}
onConfirm={onDelete}
>
<Button fluid className={styles.actionButton}>
<Icon name="trash alternate outline" className={styles.actionIcon} />
{t('action.delete')}
</Button>
</DeletePopup>
</div>
</Grid.Column>
)}
</Grid.Row>
</Grid>
);
return (
<Modal open closeIcon size="small" centered={false} onClose={onClose}>
{canEdit ? (
<AttachmentAddZone onCreate={onAttachmentCreate}>{contentNode}</AttachmentAddZone>
) : (
contentNode
)}
</Modal>
);
},
@ -425,10 +474,11 @@ CardModal.propTypes = {
attachments: PropTypes.array.isRequired,
actions: PropTypes.array.isRequired,
allProjectsToLists: PropTypes.array.isRequired,
allProjectMemberships: PropTypes.array.isRequired,
allBoardMemberships: PropTypes.array.isRequired,
allLabels: PropTypes.array.isRequired,
/* eslint-enable react/forbid-prop-types */
isEditable: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
canEditAllCommentActions: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onMove: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired,

View file

@ -44,7 +44,6 @@
}
.attachment {
cursor: pointer;
display: inline-block;
margin: 0 4px 4px 0;
max-width: 100%;
@ -70,6 +69,7 @@
border: none;
border-radius: 3px;
color: #6b808c;
cursor: pointer;
line-height: 20px;
outline: none;
padding: 6px 14px;
@ -141,6 +141,13 @@
}
.headerTitle {
color: #17394d;
font-size: 20px;
font-weight: 700;
line-height: 24px;
}
.headerTitleWrapper {
margin: 4px 0;
padding: 6px 0 0;
}
@ -193,8 +200,4 @@
margin: 0 8px 4px 0;
text-transform: uppercase;
}
/* .wrapper {
min-width: 768px;
} */
}

View file

@ -8,14 +8,14 @@ import ActionsPopup from './ActionsPopup';
import styles from './Item.module.scss';
const Item = React.memo(({ name, isCompleted, isPersisted, onUpdate, onDelete }) => {
const Item = React.memo(({ name, isCompleted, isPersisted, canEdit, onUpdate, onDelete }) => {
const nameEdit = useRef(null);
const handleClick = useCallback(() => {
if (isPersisted) {
if (isPersisted && canEdit) {
nameEdit.current.open();
}
}, [isPersisted]);
}, [isPersisted, canEdit]);
const handleNameUpdate = useCallback(
(newName) => {
@ -41,23 +41,26 @@ const Item = React.memo(({ name, isCompleted, isPersisted, onUpdate, onDelete })
<span className={styles.checkboxWrapper}>
<Checkbox
checked={isCompleted}
disabled={!isPersisted}
disabled={!isPersisted || !canEdit}
className={styles.checkbox}
onChange={handleToggleChange}
/>
</span>
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
<div className={styles.content}>
<div className={classNames(canEdit && styles.contentHoverable)}>
{/* eslint-disable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<span className={styles.text} onClick={handleClick}>
<span
className={classNames(styles.text, canEdit && styles.textEditable)}
onClick={handleClick}
>
{/* eslint-enable jsx-a11y/click-events-have-key-events,
jsx-a11y/no-static-element-interactions */}
<span className={classNames(styles.task, isCompleted && styles.taskCompleted)}>
{name}
</span>
</span>
{isPersisted && (
{isPersisted && canEdit && (
<ActionsPopup onNameEdit={handleNameEdit} onDelete={onDelete}>
<Button className={classNames(styles.button, styles.target)}>
<Icon fitted name="pencil" size="small" />
@ -74,6 +77,7 @@ Item.propTypes = {
name: PropTypes.string.isRequired,
isCompleted: PropTypes.bool.isRequired,
isPersisted: PropTypes.bool.isRequired,
canEdit: PropTypes.bool.isRequired,
onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
};

View file

@ -30,7 +30,7 @@
height: 32px;
}
.content:hover {
.contentHoverable:hover {
background: rgba(9, 30, 66, 0.04);
.target {
@ -55,7 +55,6 @@
background: transparent;
border-radius: 3px;
color: #17394d;
cursor: pointer;
display: inline-block;
font-size: 15px;
line-height: 1.5;
@ -64,6 +63,10 @@
width: 100%;
}
.textEditable {
cursor: pointer;
}
.wrapper {
border-radius: 3px;
margin-left: -40px;

View file

@ -8,7 +8,7 @@ import Add from './Add';
import styles from './Tasks.module.scss';
const Tasks = React.memo(({ items, onCreate, onUpdate, onDelete }) => {
const Tasks = React.memo(({ items, canEdit, onCreate, onUpdate, onDelete }) => {
const [t] = useTranslation();
const handleUpdate = useCallback(
@ -45,23 +45,27 @@ const Tasks = React.memo(({ items, onCreate, onUpdate, onDelete }) => {
name={item.name}
isCompleted={item.isCompleted}
isPersisted={item.isPersisted}
canEdit={canEdit}
onUpdate={(data) => handleUpdate(item.id, data)}
onDelete={() => handleDelete(item.id)}
/>
))}
<Add onCreate={onCreate}>
<button type="button" className={styles.taskButton}>
<span className={styles.taskButtonText}>
{items.length > 0 ? t('action.addAnotherTask') : t('action.addTask')}
</span>
</button>
</Add>
{canEdit && (
<Add onCreate={onCreate}>
<button type="button" className={styles.taskButton}>
<span className={styles.taskButtonText}>
{items.length > 0 ? t('action.addAnotherTask') : t('action.addTask')}
</span>
</button>
</Add>
)}
</>
);
});
Tasks.propTypes = {
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
canEdit: PropTypes.bool.isRequired,
onCreate: PropTypes.func.isRequired,
onUpdate: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,