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:
parent
7956503a46
commit
fe91b5241e
478 changed files with 21226 additions and 19495 deletions
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
} */
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue