mirror of
https://github.com/plankanban/planka.git
synced 2025-12-25 09:15:00 +03:00
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const fetchActivities = (cardId) => ({
|
||||
@@ -24,31 +29,6 @@ fetchActivities.failure = (cardId, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const toggleActivitiesDetails = (cardId, isVisible) => ({
|
||||
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE,
|
||||
payload: {
|
||||
cardId,
|
||||
isVisible,
|
||||
},
|
||||
});
|
||||
|
||||
toggleActivitiesDetails.success = (cardId, activities, users) => ({
|
||||
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE__SUCCESS,
|
||||
payload: {
|
||||
cardId,
|
||||
activities,
|
||||
users,
|
||||
},
|
||||
});
|
||||
|
||||
toggleActivitiesDetails.failure = (cardId, error) => ({
|
||||
type: ActionTypes.ACTIVITIES_DETAILS_TOGGLE__FAILURE,
|
||||
payload: {
|
||||
cardId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleActivityCreate = (activity) => ({
|
||||
type: ActionTypes.ACTIVITY_CREATE_HANDLE,
|
||||
payload: {
|
||||
@@ -56,24 +36,7 @@ const handleActivityCreate = (activity) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleActivityUpdate = (activity) => ({
|
||||
type: ActionTypes.ACTIVITY_UPDATE_HANDLE,
|
||||
payload: {
|
||||
activity,
|
||||
},
|
||||
});
|
||||
|
||||
const handleActivityDelete = (activity) => ({
|
||||
type: ActionTypes.ACTIVITY_DELETE_HANDLE,
|
||||
payload: {
|
||||
activity,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
fetchActivities,
|
||||
toggleActivitiesDetails,
|
||||
handleActivityCreate,
|
||||
handleActivityUpdate,
|
||||
handleActivityDelete,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createAttachment = (attachment) => ({
|
||||
|
||||
72
client/src/actions/background-images.js
Normal file
72
client/src/actions/background-images.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createBackgroundImage = (backgroundImage) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_CREATE,
|
||||
payload: {
|
||||
backgroundImage,
|
||||
},
|
||||
});
|
||||
|
||||
createBackgroundImage.success = (localId, backgroundImage) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
backgroundImage,
|
||||
},
|
||||
});
|
||||
|
||||
createBackgroundImage.failure = (localId, error) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleBackgroundImageCreate = (backgroundImage) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_CREATE_HANDLE,
|
||||
payload: {
|
||||
backgroundImage,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteBackgroundImage = (id) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteBackgroundImage.success = (backgroundImage) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_DELETE__SUCCESS,
|
||||
payload: {
|
||||
backgroundImage,
|
||||
},
|
||||
});
|
||||
|
||||
deleteBackgroundImage.failure = (id, error) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleBackgroundImageDelete = (backgroundImage) => ({
|
||||
type: ActionTypes.BACKGROUND_IMAGE_DELETE_HANDLE,
|
||||
payload: {
|
||||
backgroundImage,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createBackgroundImage,
|
||||
handleBackgroundImageCreate,
|
||||
deleteBackgroundImage,
|
||||
handleBackgroundImageDelete,
|
||||
};
|
||||
104
client/src/actions/base-custom-field-groups.js
Normal file
104
client/src/actions/base-custom-field-groups.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createBaseCustomFieldGroup = (baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE,
|
||||
payload: {
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
createBaseCustomFieldGroup.success = (localId, baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
createBaseCustomFieldGroup.failure = (localId, error) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleBaseCustomFieldGroupCreate = (baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_CREATE_HANDLE,
|
||||
payload: {
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
const updateBaseCustomFieldGroup = (id, data) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateBaseCustomFieldGroup.success = (baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
updateBaseCustomFieldGroup.failure = (id, error) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleBaseCustomFieldGroupUpdate = (baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_UPDATE_HANDLE,
|
||||
payload: {
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteBaseCustomFieldGroup = (id) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteBaseCustomFieldGroup.success = (baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__SUCCESS,
|
||||
payload: {
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
deleteBaseCustomFieldGroup.failure = (id, error) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleBaseCustomFieldGroupDelete = (baseCustomFieldGroup) => ({
|
||||
type: ActionTypes.BASE_CUSTOM_FIELD_GROUP_DELETE_HANDLE,
|
||||
payload: {
|
||||
baseCustomFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createBaseCustomFieldGroup,
|
||||
handleBaseCustomFieldGroupCreate,
|
||||
updateBaseCustomFieldGroup,
|
||||
handleBaseCustomFieldGroupUpdate,
|
||||
deleteBaseCustomFieldGroup,
|
||||
handleBaseCustomFieldGroupDelete,
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createBoardMembership = (boardMembership) => ({
|
||||
@@ -25,10 +30,13 @@ createBoardMembership.failure = (localId, error) => ({
|
||||
|
||||
const handleBoardMembershipCreate = (
|
||||
boardMembership,
|
||||
isProjectAvailable,
|
||||
project,
|
||||
board,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -36,17 +44,25 @@ const handleBoardMembershipCreate = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
deletedNotifications,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE,
|
||||
payload: {
|
||||
boardMembership,
|
||||
isProjectAvailable,
|
||||
project,
|
||||
board,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -54,18 +70,14 @@ const handleBoardMembershipCreate = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
deletedNotifications,
|
||||
},
|
||||
});
|
||||
|
||||
handleBoardMembershipCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
|
||||
type: ActionTypes.BOARD_MEMBERSHIP_CREATE_HANDLE__PROJECT_FETCH,
|
||||
payload: {
|
||||
id,
|
||||
currentUserId,
|
||||
currentBoardId,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -99,17 +111,19 @@ const handleBoardMembershipUpdate = (boardMembership) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const deleteBoardMembership = (id) => ({
|
||||
const deleteBoardMembership = (id, isCurrentUser) => ({
|
||||
type: ActionTypes.BOARD_MEMBERSHIP_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
isCurrentUser,
|
||||
},
|
||||
});
|
||||
|
||||
deleteBoardMembership.success = (boardMembership) => ({
|
||||
deleteBoardMembership.success = (boardMembership, isCurrentUser) => ({
|
||||
type: ActionTypes.BOARD_MEMBERSHIP_DELETE__SUCCESS,
|
||||
payload: {
|
||||
boardMembership,
|
||||
isCurrentUser,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -121,10 +135,11 @@ deleteBoardMembership.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleBoardMembershipDelete = (boardMembership) => ({
|
||||
const handleBoardMembershipDelete = (boardMembership, isCurrentUser) => ({
|
||||
type: ActionTypes.BOARD_MEMBERSHIP_DELETE_HANDLE,
|
||||
payload: {
|
||||
boardMembership,
|
||||
isCurrentUser,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createBoard = (board) => ({
|
||||
@@ -24,10 +29,11 @@ createBoard.failure = (localId, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleBoardCreate = (board) => ({
|
||||
const handleBoardCreate = (board, boardMemberships) => ({
|
||||
type: ActionTypes.BOARD_CREATE_HANDLE,
|
||||
payload: {
|
||||
board,
|
||||
boardMemberships,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,8 +54,12 @@ fetchBoard.success = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
) => ({
|
||||
type: ActionTypes.BOARD_FETCH__SUCCESS,
|
||||
payload: {
|
||||
@@ -62,8 +72,12 @@ fetchBoard.success = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,6 +119,23 @@ const handleBoardUpdate = (board) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const updateBoardContext = (id, value) => ({
|
||||
type: ActionTypes.BOARD_CONTEXT_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
const searchInBoard = (id, value, currentListId) => ({
|
||||
type: ActionTypes.IN_BOARD_SEARCH,
|
||||
payload: {
|
||||
id,
|
||||
value,
|
||||
currentListId,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteBoard = (id) => ({
|
||||
type: ActionTypes.BOARD_DELETE,
|
||||
payload: {
|
||||
@@ -140,6 +171,8 @@ export default {
|
||||
fetchBoard,
|
||||
updateBoard,
|
||||
handleBoardUpdate,
|
||||
updateBoardContext,
|
||||
searchInBoard,
|
||||
deleteBoard,
|
||||
handleBoardDelete,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,67 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createCard = (card) => ({
|
||||
const fetchCards = (listId) => ({
|
||||
type: ActionTypes.CARDS_FETCH,
|
||||
payload: {
|
||||
listId,
|
||||
},
|
||||
});
|
||||
|
||||
fetchCards.success = (
|
||||
listId,
|
||||
cards,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
) => ({
|
||||
type: ActionTypes.CARDS_FETCH__SUCCESS,
|
||||
payload: {
|
||||
listId,
|
||||
cards,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
});
|
||||
|
||||
fetchCards.failure = (listId, error) => ({
|
||||
type: ActionTypes.CARDS_FETCH__FAILURE,
|
||||
payload: {
|
||||
listId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCardsUpdate = (cards, activities) => ({
|
||||
type: ActionTypes.CARDS_UPDATE_HANDLE,
|
||||
payload: {
|
||||
cards,
|
||||
activities,
|
||||
},
|
||||
});
|
||||
|
||||
const createCard = (card, autoOpen) => ({
|
||||
type: ActionTypes.CARD_CREATE,
|
||||
payload: {
|
||||
card,
|
||||
autoOpen,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -23,14 +81,30 @@ createCard.failure = (localId, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleCardCreate = (card, cardMemberships, cardLabels, tasks, attachments) => ({
|
||||
const handleCardCreate = (
|
||||
card,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
) => ({
|
||||
type: ActionTypes.CARD_CREATE_HANDLE,
|
||||
payload: {
|
||||
card,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -57,42 +131,75 @@ updateCard.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleCardUpdate = (card, isFetched, cardMemberships, cardLabels, tasks, attachments) => ({
|
||||
const handleCardUpdate = (
|
||||
card,
|
||||
isFetched,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
) => ({
|
||||
type: ActionTypes.CARD_UPDATE_HANDLE,
|
||||
payload: {
|
||||
card,
|
||||
isFetched,
|
||||
users,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
});
|
||||
|
||||
const duplicateCard = (id, card, taskIds) => ({
|
||||
const duplicateCard = (id, localId, data) => ({
|
||||
type: ActionTypes.CARD_DUPLICATE,
|
||||
payload: {
|
||||
id,
|
||||
card,
|
||||
taskIds,
|
||||
localId,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
duplicateCard.success = (localId, card, cardMemberships, cardLabels, tasks) => ({
|
||||
duplicateCard.success = (
|
||||
localId,
|
||||
card,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
) => ({
|
||||
type: ActionTypes.CARD_DUPLICATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
card,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
},
|
||||
});
|
||||
|
||||
duplicateCard.failure = (id, error) => ({
|
||||
duplicateCard.failure = (localId, error) => ({
|
||||
type: ActionTypes.CARD_DUPLICATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
@@ -126,15 +233,9 @@ const handleCardDelete = (card) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const filterText = (boardId, text) => ({
|
||||
type: ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD,
|
||||
payload: {
|
||||
boardId,
|
||||
text,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
fetchCards,
|
||||
handleCardsUpdate,
|
||||
createCard,
|
||||
handleCardCreate,
|
||||
updateCard,
|
||||
@@ -142,5 +243,4 @@ export default {
|
||||
duplicateCard,
|
||||
deleteCard,
|
||||
handleCardDelete,
|
||||
filterText,
|
||||
};
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createCommentActivity = (activity) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_CREATE,
|
||||
payload: {
|
||||
activity,
|
||||
},
|
||||
});
|
||||
|
||||
createCommentActivity.success = (localId, activity) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
activity,
|
||||
},
|
||||
});
|
||||
|
||||
createCommentActivity.failure = (localId, error) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const updateCommentActivity = (id, data) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateCommentActivity.success = (activity) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
activity,
|
||||
},
|
||||
});
|
||||
|
||||
updateCommentActivity.failure = (id, error) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteCommentActivity = (id) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCommentActivity.success = (activity) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_DELETE__SUCCESS,
|
||||
payload: {
|
||||
activity,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCommentActivity.failure = (id, error) => ({
|
||||
type: ActionTypes.COMMENT_ACTIVITY_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createCommentActivity,
|
||||
updateCommentActivity,
|
||||
deleteCommentActivity,
|
||||
};
|
||||
130
client/src/actions/comments.js
Normal file
130
client/src/actions/comments.js
Normal file
@@ -0,0 +1,130 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const fetchComments = (cardId) => ({
|
||||
type: ActionTypes.COMMENTS_FETCH,
|
||||
payload: {
|
||||
cardId,
|
||||
},
|
||||
});
|
||||
|
||||
fetchComments.success = (cardId, comments, users) => ({
|
||||
type: ActionTypes.COMMENTS_FETCH__SUCCESS,
|
||||
payload: {
|
||||
cardId,
|
||||
comments,
|
||||
users,
|
||||
},
|
||||
});
|
||||
|
||||
fetchComments.failure = (cardId, error) => ({
|
||||
type: ActionTypes.COMMENTS_FETCH__FAILURE,
|
||||
payload: {
|
||||
cardId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const createComment = (comment) => ({
|
||||
type: ActionTypes.COMMENT_CREATE,
|
||||
payload: {
|
||||
comment,
|
||||
},
|
||||
});
|
||||
|
||||
createComment.success = (localId, comment) => ({
|
||||
type: ActionTypes.COMMENT_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
comment,
|
||||
},
|
||||
});
|
||||
|
||||
createComment.failure = (localId, error) => ({
|
||||
type: ActionTypes.COMMENT_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCommentCreate = (comment, users) => ({
|
||||
type: ActionTypes.COMMENT_CREATE_HANDLE,
|
||||
payload: {
|
||||
comment,
|
||||
users,
|
||||
},
|
||||
});
|
||||
|
||||
const updateComment = (id, data) => ({
|
||||
type: ActionTypes.COMMENT_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateComment.success = (comment) => ({
|
||||
type: ActionTypes.COMMENT_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
comment,
|
||||
},
|
||||
});
|
||||
|
||||
updateComment.failure = (id, error) => ({
|
||||
type: ActionTypes.COMMENT_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCommentUpdate = (comment) => ({
|
||||
type: ActionTypes.COMMENT_UPDATE_HANDLE,
|
||||
payload: {
|
||||
comment,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteComment = (id) => ({
|
||||
type: ActionTypes.COMMENT_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteComment.success = (comment) => ({
|
||||
type: ActionTypes.COMMENT_DELETE__SUCCESS,
|
||||
payload: {
|
||||
comment,
|
||||
},
|
||||
});
|
||||
|
||||
deleteComment.failure = (id, error) => ({
|
||||
type: ActionTypes.COMMENT_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCommentDelete = (comment) => ({
|
||||
type: ActionTypes.COMMENT_DELETE_HANDLE,
|
||||
payload: {
|
||||
comment,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
fetchComments,
|
||||
createComment,
|
||||
handleCommentCreate,
|
||||
updateComment,
|
||||
handleCommentUpdate,
|
||||
deleteComment,
|
||||
handleCommentDelete,
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const initializeCore = (
|
||||
@@ -6,6 +11,8 @@ const initializeCore = (
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -13,10 +20,14 @@ const initializeCore = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
activities,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notifications,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.CORE_INITIALIZE,
|
||||
payload: {
|
||||
@@ -25,6 +36,8 @@ const initializeCore = (
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -32,14 +45,17 @@ const initializeCore = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
activities,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notifications,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: with success?
|
||||
initializeCore.fetchConfig = (config) => ({
|
||||
type: ActionTypes.CORE_INITIALIZE__CONFIG_FETCH,
|
||||
payload: {
|
||||
@@ -47,13 +63,32 @@ initializeCore.fetchConfig = (config) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const logout = (invalidateAccessToken) => ({
|
||||
type: ActionTypes.LOGOUT,
|
||||
const toggleFavorites = (isEnabled) => ({
|
||||
type: ActionTypes.FAVORITES_TOGGLE,
|
||||
payload: {
|
||||
invalidateAccessToken,
|
||||
isEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
const toggleEditMode = (isEnabled) => ({
|
||||
type: ActionTypes.EDIT_MODE_TOGGLE,
|
||||
payload: {
|
||||
isEnabled,
|
||||
},
|
||||
});
|
||||
|
||||
const updateHomeView = (value) => ({
|
||||
type: ActionTypes.HOME_VIEW_UPDATE,
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
const logout = () => ({
|
||||
type: ActionTypes.LOGOUT,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
logout.invalidateAccessToken = () => ({
|
||||
type: ActionTypes.LOGOUT__ACCESS_TOKEN_INVALIDATE,
|
||||
payload: {},
|
||||
@@ -61,5 +96,8 @@ logout.invalidateAccessToken = () => ({
|
||||
|
||||
export default {
|
||||
initializeCore,
|
||||
toggleFavorites,
|
||||
toggleEditMode,
|
||||
updateHomeView,
|
||||
logout,
|
||||
};
|
||||
|
||||
104
client/src/actions/custom-field-groups.js
Normal file
104
client/src/actions/custom-field-groups.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createCustomFieldGroup = (customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE,
|
||||
payload: {
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
createCustomFieldGroup.success = (localId, customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
createCustomFieldGroup.failure = (localId, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldGroupCreate = (customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_CREATE_HANDLE,
|
||||
payload: {
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
const updateCustomFieldGroup = (id, data) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateCustomFieldGroup.success = (customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
updateCustomFieldGroup.failure = (id, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldGroupUpdate = (customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_UPDATE_HANDLE,
|
||||
payload: {
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteCustomFieldGroup = (id) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCustomFieldGroup.success = (customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE__SUCCESS,
|
||||
payload: {
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCustomFieldGroup.failure = (id, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldGroupDelete = (customFieldGroup) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_GROUP_DELETE_HANDLE,
|
||||
payload: {
|
||||
customFieldGroup,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createCustomFieldGroup,
|
||||
handleCustomFieldGroupCreate,
|
||||
updateCustomFieldGroup,
|
||||
handleCustomFieldGroupUpdate,
|
||||
deleteCustomFieldGroup,
|
||||
handleCustomFieldGroupDelete,
|
||||
};
|
||||
72
client/src/actions/custom-field-values.js
Normal file
72
client/src/actions/custom-field-values.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const updateCustomFieldValue = (customFieldValue) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE,
|
||||
payload: {
|
||||
customFieldValue,
|
||||
},
|
||||
});
|
||||
|
||||
updateCustomFieldValue.success = (localId, customFieldValue) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
customFieldValue,
|
||||
},
|
||||
});
|
||||
|
||||
updateCustomFieldValue.failure = (localId, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldValueUpdate = (customFieldValue) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_UPDATE_HANDLE,
|
||||
payload: {
|
||||
customFieldValue,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteCustomFieldValue = (id) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCustomFieldValue.success = (customFieldValue) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE__SUCCESS,
|
||||
payload: {
|
||||
customFieldValue,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCustomFieldValue.failure = (id, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldValueDelete = (customFieldValue) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_VALUE_DELETE_HANDLE,
|
||||
payload: {
|
||||
customFieldValue,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
updateCustomFieldValue,
|
||||
handleCustomFieldValueUpdate,
|
||||
deleteCustomFieldValue,
|
||||
handleCustomFieldValueDelete,
|
||||
};
|
||||
104
client/src/actions/custom-fields.js
Normal file
104
client/src/actions/custom-fields.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createCustomField = (customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_CREATE,
|
||||
payload: {
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
createCustomField.success = (localId, customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
createCustomField.failure = (localId, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldCreate = (customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_CREATE_HANDLE,
|
||||
payload: {
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
const updateCustomField = (id, data) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateCustomField.success = (customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
updateCustomField.failure = (id, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldUpdate = (customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_UPDATE_HANDLE,
|
||||
payload: {
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteCustomField = (id) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCustomField.success = (customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_DELETE__SUCCESS,
|
||||
payload: {
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
deleteCustomField.failure = (id, error) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleCustomFieldDelete = (customField) => ({
|
||||
type: ActionTypes.CUSTOM_FIELD_DELETE_HANDLE,
|
||||
payload: {
|
||||
customField,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createCustomField,
|
||||
handleCustomFieldCreate,
|
||||
updateCustomField,
|
||||
handleCustomFieldUpdate,
|
||||
deleteCustomField,
|
||||
handleCustomFieldDelete,
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import router from './router';
|
||||
import socket from './socket';
|
||||
import login from './login';
|
||||
@@ -6,16 +11,23 @@ import modals from './modals';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
import backgroundImages from './background-images';
|
||||
import baseCustomFieldGroups from './base-custom-field-groups';
|
||||
import boards from './boards';
|
||||
import boardMemberships from './board-memberships';
|
||||
import labels from './labels';
|
||||
import lists from './lists';
|
||||
import cards from './cards';
|
||||
import taskLists from './task-lists';
|
||||
import tasks from './tasks';
|
||||
import attachments from './attachments';
|
||||
import customFieldGroups from './custom-field-groups';
|
||||
import customFields from './custom-fields';
|
||||
import customFieldValues from './custom-field-values';
|
||||
import comments from './comments';
|
||||
import activities from './activities';
|
||||
import commentActivities from './comment-activities';
|
||||
import notifications from './notifications';
|
||||
import notificationServices from './notification-services';
|
||||
|
||||
export default {
|
||||
...router,
|
||||
@@ -26,14 +38,21 @@ export default {
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
...backgroundImages,
|
||||
...baseCustomFieldGroups,
|
||||
...boards,
|
||||
...boardMemberships,
|
||||
...labels,
|
||||
...lists,
|
||||
...cards,
|
||||
...taskLists,
|
||||
...tasks,
|
||||
...attachments,
|
||||
...customFieldGroups,
|
||||
...customFields,
|
||||
...customFieldValues,
|
||||
...comments,
|
||||
...activities,
|
||||
...commentActivities,
|
||||
...notifications,
|
||||
...notificationServices,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createLabel = (label) => ({
|
||||
@@ -23,6 +28,32 @@ createLabel.failure = (localId, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const createLabelFromCard = (cardId, label) => ({
|
||||
type: ActionTypes.LABEL_FROM_CARD_CREATE,
|
||||
payload: {
|
||||
cardId,
|
||||
label,
|
||||
},
|
||||
});
|
||||
|
||||
createLabelFromCard.success = (localId, label, cardLabel) => ({
|
||||
type: ActionTypes.LABEL_FROM_CARD_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
label,
|
||||
cardLabel,
|
||||
},
|
||||
});
|
||||
|
||||
createLabelFromCard.failure = (cardId, localId, error) => ({
|
||||
type: ActionTypes.LABEL_FROM_CARD_CREATE__FAILURE,
|
||||
payload: {
|
||||
cardId,
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleLabelCreate = (label) => ({
|
||||
type: ActionTypes.LABEL_CREATE_HANDLE,
|
||||
payload: {
|
||||
@@ -151,24 +182,27 @@ const handleLabelFromCardRemove = (cardLabel) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const addLabelToBoardFilter = (id, boardId) => ({
|
||||
const addLabelToBoardFilter = (id, boardId, currentListId) => ({
|
||||
type: ActionTypes.LABEL_TO_BOARD_FILTER_ADD,
|
||||
payload: {
|
||||
id,
|
||||
boardId,
|
||||
currentListId,
|
||||
},
|
||||
});
|
||||
|
||||
const removeLabelFromBoardFilter = (id, boardId) => ({
|
||||
const removeLabelFromBoardFilter = (id, boardId, currentListId) => ({
|
||||
type: ActionTypes.LABEL_FROM_BOARD_FILTER_REMOVE,
|
||||
payload: {
|
||||
id,
|
||||
boardId,
|
||||
currentListId,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createLabel,
|
||||
createLabelFromCard,
|
||||
handleLabelCreate,
|
||||
updateLabel,
|
||||
handleLabelUpdate,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createList = (list) => ({
|
||||
@@ -84,25 +89,75 @@ sortList.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleListSort = (list, cards) => ({
|
||||
type: ActionTypes.LIST_SORT_HANDLE,
|
||||
const moveListCards = (id, nextId, cardIds) => ({
|
||||
type: ActionTypes.LIST_CARDS_MOVE,
|
||||
payload: {
|
||||
list,
|
||||
cards,
|
||||
id,
|
||||
nextId,
|
||||
cardIds,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteList = (id) => ({
|
||||
type: ActionTypes.LIST_DELETE,
|
||||
moveListCards.success = (list, cards, activities) => ({
|
||||
type: ActionTypes.LIST_CARDS_MOVE__SUCCESS,
|
||||
payload: {
|
||||
list,
|
||||
cards,
|
||||
activities,
|
||||
},
|
||||
});
|
||||
|
||||
moveListCards.failure = (id, error) => ({
|
||||
type: ActionTypes.LIST_CARDS_MOVE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const clearList = (id) => ({
|
||||
type: ActionTypes.LIST_CLEAR,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteList.success = (list) => ({
|
||||
clearList.success = (list) => ({
|
||||
type: ActionTypes.LIST_CLEAR__SUCCESS,
|
||||
payload: {
|
||||
list,
|
||||
},
|
||||
});
|
||||
|
||||
clearList.failure = (id, error) => ({
|
||||
type: ActionTypes.LIST_CLEAR__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleListClear = (list) => ({
|
||||
type: ActionTypes.LIST_CLEAR_HANDLE,
|
||||
payload: {
|
||||
list,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteList = (id, trashId, cardIds) => ({
|
||||
type: ActionTypes.LIST_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
trashId,
|
||||
cardIds,
|
||||
},
|
||||
});
|
||||
|
||||
deleteList.success = (list, cards) => ({
|
||||
type: ActionTypes.LIST_DELETE__SUCCESS,
|
||||
payload: {
|
||||
list,
|
||||
cards,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -114,10 +169,11 @@ deleteList.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleListDelete = (list) => ({
|
||||
const handleListDelete = (list, cards) => ({
|
||||
type: ActionTypes.LIST_DELETE_HANDLE,
|
||||
payload: {
|
||||
list,
|
||||
cards,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -127,7 +183,9 @@ export default {
|
||||
updateList,
|
||||
handleListUpdate,
|
||||
sortList,
|
||||
handleListSort,
|
||||
moveListCards,
|
||||
clearList,
|
||||
handleListClear,
|
||||
deleteList,
|
||||
handleListDelete,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const initializeLogin = (config) => ({
|
||||
@@ -28,20 +33,20 @@ authenticate.failure = (error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const authenticateUsingOidc = () => ({
|
||||
type: ActionTypes.USING_OIDC_AUTHENTICATE,
|
||||
const authenticateWithOidc = () => ({
|
||||
type: ActionTypes.WITH_OIDC_AUTHENTICATE,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
authenticateUsingOidc.success = (accessToken) => ({
|
||||
type: ActionTypes.USING_OIDC_AUTHENTICATE__SUCCESS,
|
||||
authenticateWithOidc.success = (accessToken) => ({
|
||||
type: ActionTypes.WITH_OIDC_AUTHENTICATE__SUCCESS,
|
||||
payload: {
|
||||
accessToken,
|
||||
},
|
||||
});
|
||||
|
||||
authenticateUsingOidc.failure = (error) => ({
|
||||
type: ActionTypes.USING_OIDC_AUTHENTICATE__FAILURE,
|
||||
authenticateWithOidc.failure = (error) => ({
|
||||
type: ActionTypes.WITH_OIDC_AUTHENTICATE__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
@@ -55,6 +60,6 @@ const clearAuthenticateError = () => ({
|
||||
export default {
|
||||
initializeLogin,
|
||||
authenticate,
|
||||
authenticateUsingOidc,
|
||||
authenticateWithOidc,
|
||||
clearAuthenticateError,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const openModal = (type) => ({
|
||||
const openModal = (type, params = {}) => ({
|
||||
type: ActionTypes.MODAL_OPEN,
|
||||
payload: {
|
||||
type,
|
||||
params,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
127
client/src/actions/notification-services.js
Normal file
127
client/src/actions/notification-services.js
Normal file
@@ -0,0 +1,127 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createNotificationService = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_CREATE,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
createNotificationService.success = (localId, notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
createNotificationService.failure = (localId, error) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleNotificationServiceCreate = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_CREATE_HANDLE,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
const updateNotificationService = (id, data) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateNotificationService.success = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
updateNotificationService.failure = (id, error) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleNotificationServiceUpdate = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_UPDATE_HANDLE,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
const testNotificationService = (id) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_TEST,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
testNotificationService.success = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_TEST__SUCCESS,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
testNotificationService.failure = (id, error) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_TEST__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteNotificationService = (id) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteNotificationService.success = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_DELETE__SUCCESS,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
deleteNotificationService.failure = (id, error) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleNotificationServiceDelete = (notificationService) => ({
|
||||
type: ActionTypes.NOTIFICATION_SERVICE_DELETE_HANDLE,
|
||||
payload: {
|
||||
notificationService,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createNotificationService,
|
||||
handleNotificationServiceCreate,
|
||||
updateNotificationService,
|
||||
handleNotificationServiceUpdate,
|
||||
testNotificationService,
|
||||
deleteNotificationService,
|
||||
handleNotificationServiceDelete,
|
||||
};
|
||||
@@ -1,12 +1,34 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const handleNotificationCreate = (notification, users, cards, activities) => ({
|
||||
const deleteAllNotifications = () => ({
|
||||
type: ActionTypes.ALL_NOTIFICATIONS_DELETE,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
deleteAllNotifications.success = (notifications) => ({
|
||||
type: ActionTypes.ALL_NOTIFICATIONS_DELETE__SUCCESS,
|
||||
payload: {
|
||||
notifications,
|
||||
},
|
||||
});
|
||||
|
||||
deleteAllNotifications.failure = (error) => ({
|
||||
type: ActionTypes.ALL_NOTIFICATIONS_DELETE__FAILURE,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleNotificationCreate = (notification, users) => ({
|
||||
type: ActionTypes.NOTIFICATION_CREATE_HANDLE,
|
||||
payload: {
|
||||
notification,
|
||||
users,
|
||||
cards,
|
||||
activities,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,6 +62,7 @@ const handleNotificationDelete = (notification) => ({
|
||||
});
|
||||
|
||||
export default {
|
||||
deleteAllNotifications,
|
||||
handleNotificationCreate,
|
||||
deleteNotification,
|
||||
handleNotificationDelete,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createProjectManager = (projectManager) => ({
|
||||
@@ -25,10 +30,15 @@ createProjectManager.failure = (localId, error) => ({
|
||||
|
||||
const handleProjectManagerCreate = (
|
||||
projectManager,
|
||||
boardIds,
|
||||
isCurrentUser,
|
||||
isProjectAvailable,
|
||||
project,
|
||||
board,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -36,17 +46,27 @@ const handleProjectManagerCreate = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
deletedNotifications,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE,
|
||||
payload: {
|
||||
projectManager,
|
||||
boardIds,
|
||||
isCurrentUser,
|
||||
isProjectAvailable,
|
||||
project,
|
||||
board,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -54,27 +74,21 @@ const handleProjectManagerCreate = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
deletedNotifications,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
handleProjectManagerCreate.fetchProject = (id, currentUserId, currentBoardId) => ({
|
||||
type: ActionTypes.PROJECT_MANAGER_CREATE_HANDLE__PROJECT_FETCH,
|
||||
payload: {
|
||||
id,
|
||||
currentUserId,
|
||||
currentBoardId,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteProjectManager = (id, isCurrentUser, isCurrentProject) => ({
|
||||
const deleteProjectManager = (id) => ({
|
||||
type: ActionTypes.PROJECT_MANAGER_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
isCurrentUser,
|
||||
isCurrentProject,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -93,12 +107,10 @@ deleteProjectManager.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleProjectManagerDelete = (projectManager, isCurrentUser, isCurrentProject) => ({
|
||||
const handleProjectManagerDelete = (projectManager) => ({
|
||||
type: ActionTypes.PROJECT_MANAGER_DELETE_HANDLE,
|
||||
payload: {
|
||||
projectManager,
|
||||
isCurrentUser,
|
||||
isCurrentProject,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const searchProjects = (value) => ({
|
||||
type: ActionTypes.PROJECTS_SEARCH,
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
const updateProjectsOrder = (value) => ({
|
||||
type: ActionTypes.PROJECTS_ORDER_UPDATE,
|
||||
payload: {
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
const toggleHiddenProjects = (isVisible) => ({
|
||||
type: ActionTypes.HIDDEN_PROJECTS_TOGGLE,
|
||||
payload: {
|
||||
isVisible,
|
||||
},
|
||||
});
|
||||
|
||||
const createProject = (data) => ({
|
||||
type: ActionTypes.PROJECT_CREATE,
|
||||
payload: {
|
||||
@@ -22,14 +48,28 @@ createProject.failure = (error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleProjectCreate = (project, users, projectManagers, boards, boardMemberships) => ({
|
||||
const handleProjectCreate = (
|
||||
project,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
customFields,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.PROJECT_CREATE_HANDLE,
|
||||
payload: {
|
||||
project,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
customFields,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -56,32 +96,56 @@ updateProject.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleProjectUpdate = (project) => ({
|
||||
const handleProjectUpdate = (
|
||||
project,
|
||||
boardIds,
|
||||
isAvailable,
|
||||
board,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
lists,
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.PROJECT_UPDATE_HANDLE,
|
||||
payload: {
|
||||
project,
|
||||
},
|
||||
});
|
||||
|
||||
const updateProjectBackgroundImage = (id) => ({
|
||||
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
updateProjectBackgroundImage.success = (project) => ({
|
||||
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
project,
|
||||
},
|
||||
});
|
||||
|
||||
updateProjectBackgroundImage.failure = (id, error) => ({
|
||||
type: ActionTypes.PROJECT_BACKGROUND_IMAGE_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
boardIds,
|
||||
isAvailable,
|
||||
board,
|
||||
users,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
lists,
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -115,11 +179,13 @@ const handleProjectDelete = (project) => ({
|
||||
});
|
||||
|
||||
export default {
|
||||
searchProjects,
|
||||
updateProjectsOrder,
|
||||
toggleHiddenProjects,
|
||||
createProject,
|
||||
handleProjectCreate,
|
||||
updateProject,
|
||||
handleProjectUpdate,
|
||||
updateProjectBackgroundImage,
|
||||
deleteProject,
|
||||
handleProjectDelete,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const handleLocationChange = (
|
||||
pathname,
|
||||
currentBoardId,
|
||||
currentCardId,
|
||||
isEditModeEnabled,
|
||||
board,
|
||||
users,
|
||||
projects,
|
||||
@@ -10,12 +19,20 @@ const handleLocationChange = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
deletedNotifications,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
) => ({
|
||||
type: ActionTypes.LOCATION_CHANGE_HANDLE,
|
||||
payload: {
|
||||
pathname,
|
||||
currentBoardId,
|
||||
currentCardId,
|
||||
isEditModeEnabled,
|
||||
board,
|
||||
users,
|
||||
projects,
|
||||
@@ -25,12 +42,21 @@ const handleLocationChange = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
deletedNotifications,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
},
|
||||
});
|
||||
|
||||
handleLocationChange.fetchContent = () => ({
|
||||
type: ActionTypes.LOCATION_CHANGE_HANDLE__CONTENT_FETCH,
|
||||
payload: {},
|
||||
});
|
||||
|
||||
handleLocationChange.fetchBoard = (id) => ({
|
||||
type: ActionTypes.LOCATION_CHANGE_HANDLE__BOARD_FETCH,
|
||||
payload: {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const handleSocketDisconnect = () => ({
|
||||
@@ -6,11 +11,14 @@ const handleSocketDisconnect = () => ({
|
||||
});
|
||||
|
||||
const handleSocketReconnect = (
|
||||
config,
|
||||
user,
|
||||
board,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -18,18 +26,25 @@ const handleSocketReconnect = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
activities,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notifications,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.SOCKET_RECONNECT_HANDLE,
|
||||
payload: {
|
||||
config,
|
||||
user,
|
||||
board,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
@@ -37,10 +52,14 @@ const handleSocketReconnect = (
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
activities,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notifications,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
104
client/src/actions/task-lists.js
Normal file
104
client/src/actions/task-lists.js
Normal file
@@ -0,0 +1,104 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createTaskList = (taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_CREATE,
|
||||
payload: {
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
createTaskList.success = (localId, taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_CREATE__SUCCESS,
|
||||
payload: {
|
||||
localId,
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
createTaskList.failure = (localId, error) => ({
|
||||
type: ActionTypes.TASK_LIST_CREATE__FAILURE,
|
||||
payload: {
|
||||
localId,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleTaskListCreate = (taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_CREATE_HANDLE,
|
||||
payload: {
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
const updateTaskList = (id, data) => ({
|
||||
type: ActionTypes.TASK_LIST_UPDATE,
|
||||
payload: {
|
||||
id,
|
||||
data,
|
||||
},
|
||||
});
|
||||
|
||||
updateTaskList.success = (taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_UPDATE__SUCCESS,
|
||||
payload: {
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
updateTaskList.failure = (id, error) => ({
|
||||
type: ActionTypes.TASK_LIST_UPDATE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleTaskListUpdate = (taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_UPDATE_HANDLE,
|
||||
payload: {
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
const deleteTaskList = (id) => ({
|
||||
type: ActionTypes.TASK_LIST_DELETE,
|
||||
payload: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
deleteTaskList.success = (taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_DELETE__SUCCESS,
|
||||
payload: {
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
deleteTaskList.failure = (id, error) => ({
|
||||
type: ActionTypes.TASK_LIST_DELETE__FAILURE,
|
||||
payload: {
|
||||
id,
|
||||
error,
|
||||
},
|
||||
});
|
||||
|
||||
const handleTaskListDelete = (taskList) => ({
|
||||
type: ActionTypes.TASK_LIST_DELETE_HANDLE,
|
||||
payload: {
|
||||
taskList,
|
||||
},
|
||||
});
|
||||
|
||||
export default {
|
||||
createTaskList,
|
||||
handleTaskListCreate,
|
||||
updateTaskList,
|
||||
handleTaskListUpdate,
|
||||
deleteTaskList,
|
||||
handleTaskListDelete,
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createTask = (task) => ({
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import ActionTypes from '../constants/ActionTypes';
|
||||
|
||||
const createUser = (data) => ({
|
||||
@@ -56,12 +61,60 @@ updateUser.failure = (id, error) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const handleUserUpdate = (user, users, isCurrent) => ({
|
||||
const handleUserUpdate = (
|
||||
user,
|
||||
projectIds,
|
||||
boardIds,
|
||||
config,
|
||||
board,
|
||||
users,
|
||||
projects,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
lists,
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
) => ({
|
||||
type: ActionTypes.USER_UPDATE_HANDLE,
|
||||
payload: {
|
||||
user,
|
||||
projectIds,
|
||||
boardIds,
|
||||
config,
|
||||
board,
|
||||
users,
|
||||
isCurrent,
|
||||
projects,
|
||||
projectManagers,
|
||||
backgroundImages,
|
||||
baseCustomFieldGroups,
|
||||
boards,
|
||||
boardMemberships,
|
||||
labels,
|
||||
lists,
|
||||
cards,
|
||||
cardMemberships,
|
||||
cardLabels,
|
||||
taskLists,
|
||||
tasks,
|
||||
attachments,
|
||||
customFieldGroups,
|
||||
customFields,
|
||||
customFieldValues,
|
||||
notificationsToDelete,
|
||||
notificationServices,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -270,19 +323,22 @@ const handleUserFromCardRemove = (cardMembership) => ({
|
||||
},
|
||||
});
|
||||
|
||||
const addUserToBoardFilter = (id, boardId) => ({
|
||||
const addUserToBoardFilter = (id, boardId, replace, currentListId) => ({
|
||||
type: ActionTypes.USER_TO_BOARD_FILTER_ADD,
|
||||
payload: {
|
||||
id,
|
||||
boardId,
|
||||
replace,
|
||||
currentListId,
|
||||
},
|
||||
});
|
||||
|
||||
const removeUserFromBoardFilter = (id, boardId) => ({
|
||||
const removeUserFromBoardFilter = (id, boardId, currentListId) => ({
|
||||
type: ActionTypes.USER_FROM_BOARD_FILTER_REMOVE,
|
||||
payload: {
|
||||
id,
|
||||
boardId,
|
||||
currentListId,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
|
||||
/* Actions */
|
||||
@@ -5,13 +10,13 @@ import http from './http';
|
||||
const createAccessToken = (data, headers) =>
|
||||
http.post('/access-tokens?withHttpOnlyToken=true', data, headers);
|
||||
|
||||
const exchangeForAccessTokenUsingOidc = (data, headers) =>
|
||||
http.post('/access-tokens/exchange-using-oidc?withHttpOnlyToken=true', data, headers);
|
||||
const exchangeForAccessTokenWithOidc = (data, headers) =>
|
||||
http.post('/access-tokens/exchange-with-oidc?withHttpOnlyToken=true', data, headers);
|
||||
|
||||
const deleteCurrentAccessToken = (headers) => http.delete('/access-tokens/me', undefined, headers);
|
||||
|
||||
export default {
|
||||
createAccessToken,
|
||||
exchangeForAccessTokenUsingOidc,
|
||||
exchangeForAccessTokenWithOidc,
|
||||
deleteCurrentAccessToken,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
import { transformUser } from './users';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformActivity = (activity) => ({
|
||||
...activity,
|
||||
createdAt: new Date(activity.createdAt),
|
||||
...(activity.createdAt && {
|
||||
createdAt: new Date(activity.createdAt),
|
||||
}),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
@@ -14,10 +20,6 @@ const getActivities = (cardId, data, headers) =>
|
||||
socket.get(`/cards/${cardId}/actions`, data, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformActivity),
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
},
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
@@ -29,13 +31,7 @@ const makeHandleActivityCreate = (next) => (body) => {
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleActivityUpdate = makeHandleActivityCreate;
|
||||
|
||||
const makeHandleActivityDelete = makeHandleActivityCreate;
|
||||
|
||||
export default {
|
||||
getActivities,
|
||||
makeHandleActivityCreate,
|
||||
makeHandleActivityUpdate,
|
||||
makeHandleActivityDelete,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
|
||||
@@ -5,17 +10,34 @@ import socket from './socket';
|
||||
|
||||
export const transformAttachment = (attachment) => ({
|
||||
...attachment,
|
||||
createdAt: new Date(attachment.createdAt),
|
||||
...(attachment.createdAt && {
|
||||
createdAt: new Date(attachment.createdAt),
|
||||
}),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createAttachment = (cardId, data, requestId, headers) =>
|
||||
http.post(`/cards/${cardId}/attachments?requestId=${requestId}`, data, headers).then((body) => ({
|
||||
const createAttachment = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/attachments`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformAttachment(body.item),
|
||||
}));
|
||||
|
||||
const createAttachmentWithFile = (cardId, { file, ...data }, requestId, headers) =>
|
||||
http
|
||||
.post(
|
||||
`/cards/${cardId}/attachments?requestId=${requestId}`,
|
||||
{
|
||||
...data,
|
||||
file,
|
||||
},
|
||||
headers,
|
||||
)
|
||||
.then((body) => ({
|
||||
...body,
|
||||
item: transformAttachment(body.item),
|
||||
}));
|
||||
|
||||
const updateAttachment = (id, data, headers) =>
|
||||
socket.patch(`/attachments/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
@@ -43,6 +65,7 @@ const makeHandleAttachmentDelete = makeHandleAttachmentCreate;
|
||||
|
||||
export default {
|
||||
createAttachment,
|
||||
createAttachmentWithFile,
|
||||
updateAttachment,
|
||||
deleteAttachment,
|
||||
makeHandleAttachmentCreate,
|
||||
|
||||
27
client/src/api/background-images.js
Normal file
27
client/src/api/background-images.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createBackgroundImage = (projectId, { file, ...data }, requestId, headers) =>
|
||||
http.post(
|
||||
`/projects/${projectId}/background-images?requestId=${requestId}`,
|
||||
{
|
||||
...data,
|
||||
file,
|
||||
},
|
||||
headers,
|
||||
);
|
||||
|
||||
const deleteBackgroundImage = (id, headers) =>
|
||||
socket.delete(`/background-images/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createBackgroundImage,
|
||||
deleteBackgroundImage,
|
||||
};
|
||||
23
client/src/api/base-custom-field-groups.js
Executable file
23
client/src/api/base-custom-field-groups.js
Executable file
@@ -0,0 +1,23 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createBaseCustomFieldGroup = (projectId, data, headers) =>
|
||||
socket.post(`/projects/${projectId}/base-custom-field-groups`, data, headers);
|
||||
|
||||
const updateBaseCustomFieldGroup = (id, data, headers) =>
|
||||
socket.patch(`/base-custom-field-groups/${id}`, data, headers);
|
||||
|
||||
const deleteBaseCustomFieldGroup = (id, headers) =>
|
||||
socket.delete(`/base-custom-field-groups/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createBaseCustomFieldGroup,
|
||||
updateBaseCustomFieldGroup,
|
||||
deleteBaseCustomFieldGroup,
|
||||
};
|
||||
@@ -1,50 +1,23 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformBoardMembership = (boardMembership) => ({
|
||||
...boardMembership,
|
||||
createdAt: new Date(boardMembership.createdAt),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createBoardMembership = (boardId, data, headers) =>
|
||||
socket.post(`/boards/${boardId}/memberships`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
}));
|
||||
socket.post(`/boards/${boardId}/board-memberships`, data, headers);
|
||||
|
||||
const updateBoardMembership = (id, data, headers) =>
|
||||
socket.patch(`/board-memberships/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
}));
|
||||
socket.patch(`/board-memberships/${id}`, data, headers);
|
||||
|
||||
const deleteBoardMembership = (id, headers) =>
|
||||
socket.delete(`/board-memberships/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleBoardMembershipCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformBoardMembership(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleBoardMembershipUpdate = makeHandleBoardMembershipCreate;
|
||||
|
||||
const makeHandleBoardMembershipDelete = makeHandleBoardMembershipCreate;
|
||||
socket.delete(`/board-memberships/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createBoardMembership,
|
||||
updateBoardMembership,
|
||||
deleteBoardMembership,
|
||||
makeHandleBoardMembershipCreate,
|
||||
makeHandleBoardMembershipUpdate,
|
||||
makeHandleBoardMembershipDelete,
|
||||
};
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import socket from './socket';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
import { transformUser } from './users';
|
||||
import { transformBoardMembership } from './board-memberships';
|
||||
import socket from './socket';
|
||||
import { transformCard } from './cards';
|
||||
import { transformAttachment } from './attachments';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createBoard = (projectId, data, headers) =>
|
||||
socket.post(`/projects/${projectId}/boards`, data, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
},
|
||||
}));
|
||||
socket.post(`/projects/${projectId}/boards`, data, headers);
|
||||
|
||||
const createBoardWithImport = (projectId, data, requestId, headers) =>
|
||||
http.post(`/projects/${projectId}/boards?requestId=${requestId}`, data, headers);
|
||||
@@ -26,8 +23,6 @@ const getBoard = (id, subscribe, headers) =>
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
attachments: body.included.attachments.map(transformAttachment),
|
||||
},
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createCardLabel = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/labels`, data, headers);
|
||||
socket.post(`/cards/${cardId}/card-labels`, data, headers);
|
||||
|
||||
const deleteCardLabel = (cardId, labelId, headers) =>
|
||||
socket.delete(`/cards/${cardId}/labels/${labelId}`, undefined, headers);
|
||||
socket.delete(`/cards/${cardId}/card-labels/labelId:${labelId}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createCardLabel,
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createCardMembership = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/memberships`, data, headers);
|
||||
socket.post(`/cards/${cardId}/card-memberships`, data, headers);
|
||||
|
||||
const deleteCardMembership = (cardId, userId, headers) =>
|
||||
socket.delete(`/cards/${cardId}/memberships?userId=${userId}`, undefined, headers);
|
||||
socket.delete(`/cards/${cardId}/card-memberships/userId:${userId}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createCardMembership,
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
import socket from './socket';
|
||||
import { transformAttachment } from './attachments';
|
||||
import { transformActivity } from './activities';
|
||||
import { transformNotification } from './notifications';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
@@ -16,6 +25,12 @@ export const transformCard = (card) => ({
|
||||
}),
|
||||
},
|
||||
}),
|
||||
...(card.createdAt && {
|
||||
createdAt: new Date(card.createdAt),
|
||||
}),
|
||||
...(card.listChangedAt && {
|
||||
listChangedAt: new Date(card.listChangedAt),
|
||||
}),
|
||||
});
|
||||
|
||||
export const transformCardData = (data) => ({
|
||||
@@ -35,6 +50,16 @@ export const transformCardData = (data) => ({
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getCards = (listId, data, headers) =>
|
||||
socket.get(`/lists/${listId}/cards`, data, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformCard),
|
||||
included: {
|
||||
...body.included,
|
||||
attachments: body.included.attachments.map(transformAttachment),
|
||||
},
|
||||
}));
|
||||
|
||||
const createCard = (listId, data, headers) =>
|
||||
socket.post(`/lists/${listId}/cards`, transformCardData(data), headers).then((body) => ({
|
||||
...body,
|
||||
@@ -61,6 +86,20 @@ const duplicateCard = (id, data, headers) =>
|
||||
socket.post(`/cards/${id}/duplicate`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformCard(body.item),
|
||||
included: {
|
||||
...body.included,
|
||||
attachments: body.included.attachments.map(transformAttachment),
|
||||
},
|
||||
}));
|
||||
|
||||
const readCardNotifications = (id, headers) =>
|
||||
socket.post(`/cards/${id}/read-notifications`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformCard(body.item),
|
||||
included: {
|
||||
...body.included,
|
||||
notifications: body.included.notifications.map(transformNotification),
|
||||
},
|
||||
}));
|
||||
|
||||
const deleteCard = (id, headers) =>
|
||||
@@ -71,6 +110,17 @@ const deleteCard = (id, headers) =>
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleCardsUpdate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
items: body.items.map(transformCard),
|
||||
included: body.included && {
|
||||
...omit(body.included, 'actions'),
|
||||
activities: body.included.actions.map(transformActivity),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleCardCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
@@ -80,14 +130,17 @@ const makeHandleCardCreate = (next) => (body) => {
|
||||
|
||||
const makeHandleCardUpdate = makeHandleCardCreate;
|
||||
|
||||
const makeHandleCardDelete = makeHandleCardCreate;
|
||||
const makeHandleCardDelete = makeHandleCardUpdate;
|
||||
|
||||
export default {
|
||||
getCards,
|
||||
createCard,
|
||||
getCard,
|
||||
updateCard,
|
||||
deleteCard,
|
||||
duplicateCard,
|
||||
readCardNotifications,
|
||||
deleteCard,
|
||||
makeHandleCardsUpdate,
|
||||
makeHandleCardCreate,
|
||||
makeHandleCardUpdate,
|
||||
makeHandleCardDelete,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import socket from './socket';
|
||||
import { transformActivity } from './activities';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createCommentActivity = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/comment-actions`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformActivity(body.item),
|
||||
}));
|
||||
|
||||
const updateCommentActivity = (id, data, headers) =>
|
||||
socket.patch(`/comment-actions/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformActivity(body.item),
|
||||
}));
|
||||
|
||||
const deleteCommentActivity = (id, headers) =>
|
||||
socket.delete(`/comment-actions/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformActivity(body.item),
|
||||
}));
|
||||
|
||||
export default {
|
||||
createCommentActivity,
|
||||
updateCommentActivity,
|
||||
deleteCommentActivity,
|
||||
};
|
||||
64
client/src/api/comments.js
Normal file
64
client/src/api/comments.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformComment = (comment) => ({
|
||||
...comment,
|
||||
...(comment.createdAt && {
|
||||
createdAt: new Date(comment.createdAt),
|
||||
}),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getComments = (cardId, data, headers) =>
|
||||
socket.get(`/cards/${cardId}/comments`, data, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformComment),
|
||||
}));
|
||||
|
||||
const createComment = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/comments`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformComment(body.item),
|
||||
}));
|
||||
|
||||
const updateComment = (id, data, headers) =>
|
||||
socket.patch(`/comments/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformComment(body.item),
|
||||
}));
|
||||
|
||||
const deleteComment = (id, headers) =>
|
||||
socket.delete(`/comments/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformComment(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleCommentCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformComment(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleCommentUpdate = makeHandleCommentCreate;
|
||||
|
||||
const makeHandleCommentDelete = makeHandleCommentUpdate;
|
||||
|
||||
export default {
|
||||
getComments,
|
||||
createComment,
|
||||
updateComment,
|
||||
deleteComment,
|
||||
makeHandleCommentCreate,
|
||||
makeHandleCommentUpdate,
|
||||
makeHandleCommentDelete,
|
||||
};
|
||||
14
client/src/api/config.js
Normal file
14
client/src/api/config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getConfig = (headers) => http.get('/config', undefined, headers);
|
||||
|
||||
export default {
|
||||
getConfig,
|
||||
};
|
||||
31
client/src/api/custom-field-groups.js
Executable file
31
client/src/api/custom-field-groups.js
Executable file
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createCustomFieldGroupInBoard = (cardId, data, headers) =>
|
||||
socket.post(`/boards/${cardId}/custom-field-groups`, data, headers);
|
||||
|
||||
const createCustomFieldGroupInCard = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/custom-field-groups`, data, headers);
|
||||
|
||||
const getCustomFieldGroup = (id, headers) =>
|
||||
socket.get(`/custom-field-groups/${id}`, undefined, headers);
|
||||
|
||||
const updateCustomFieldGroup = (id, data, headers) =>
|
||||
socket.patch(`/custom-field-groups/${id}`, data, headers);
|
||||
|
||||
const deleteCustomFieldGroup = (id, headers) =>
|
||||
socket.delete(`/custom-field-groups/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createCustomFieldGroupInBoard,
|
||||
createCustomFieldGroupInCard,
|
||||
getCustomFieldGroup,
|
||||
updateCustomFieldGroup,
|
||||
deleteCustomFieldGroup,
|
||||
};
|
||||
27
client/src/api/custom-field-values.js
Normal file
27
client/src/api/custom-field-values.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const updateCustomFieldValue = (cardId, customFieldGroupId, customFieldId, data, headers) =>
|
||||
socket.patch(
|
||||
`/cards/${cardId}/custom-field-values/customFieldGroupId:${customFieldGroupId}:customFieldId:${customFieldId}`,
|
||||
data,
|
||||
headers,
|
||||
);
|
||||
|
||||
const deleteCustomFieldValue = (cardId, customFieldGroupId, customFieldId, headers) =>
|
||||
socket.delete(
|
||||
`/cards/${cardId}/custom-field-values/customFieldGroupId:${customFieldGroupId}:customFieldId:${customFieldId}`,
|
||||
undefined,
|
||||
headers,
|
||||
);
|
||||
|
||||
export default {
|
||||
updateCustomFieldValue,
|
||||
deleteCustomFieldValue,
|
||||
};
|
||||
27
client/src/api/custom-fields.js
Executable file
27
client/src/api/custom-fields.js
Executable file
@@ -0,0 +1,27 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createCustomFieldInBaseGroup = (baseCustomFieldGroupId, data, headers) =>
|
||||
socket.post(`/base-custom-field-groups/${baseCustomFieldGroupId}/custom-fields`, data, headers);
|
||||
|
||||
const createCustomFieldInGroup = (customFieldGroupId, data, headers) =>
|
||||
socket.post(`/custom-field-groups/${customFieldGroupId}/custom-fields`, data, headers);
|
||||
|
||||
const updateCustomField = (id, data, headers) =>
|
||||
socket.patch(`/custom-fields/${id}`, data, headers);
|
||||
|
||||
const deleteCustomField = (id, headers) =>
|
||||
socket.delete(`/custom-fields/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createCustomFieldInBaseGroup,
|
||||
createCustomFieldInGroup,
|
||||
updateCustomField,
|
||||
deleteCustomField,
|
||||
};
|
||||
@@ -1,4 +1,7 @@
|
||||
import { fetch } from 'whatwg-fetch';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import Config from '../constants/Config';
|
||||
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
import root from './root';
|
||||
import config from './config';
|
||||
import accessTokens from './access-tokens';
|
||||
import users from './users';
|
||||
import projects from './projects';
|
||||
import projectManagers from './project-managers';
|
||||
import backgroundImages from './background-images';
|
||||
import baseCustomFieldGroups from './base-custom-field-groups';
|
||||
import boards from './boards';
|
||||
import boardMemberships from './board-memberships';
|
||||
import labels from './labels';
|
||||
@@ -12,20 +19,27 @@ import lists from './lists';
|
||||
import cards from './cards';
|
||||
import cardMemberships from './card-memberships';
|
||||
import cardLabels from './card-labels';
|
||||
import taskLists from './task-lists';
|
||||
import tasks from './tasks';
|
||||
import attachments from './attachments';
|
||||
import customFieldGroups from './custom-field-groups';
|
||||
import customFields from './custom-fields';
|
||||
import customFieldValues from './custom-field-values';
|
||||
import comments from './comments';
|
||||
import activities from './activities';
|
||||
import commentActivities from './comment-activities';
|
||||
import notifications from './notifications';
|
||||
import notificationServices from './notification-services';
|
||||
|
||||
export { http, socket };
|
||||
|
||||
export default {
|
||||
...root,
|
||||
...config,
|
||||
...accessTokens,
|
||||
...users,
|
||||
...projects,
|
||||
...projectManagers,
|
||||
...backgroundImages,
|
||||
...baseCustomFieldGroups,
|
||||
...boards,
|
||||
...boardMemberships,
|
||||
...labels,
|
||||
@@ -33,9 +47,14 @@ export default {
|
||||
...cards,
|
||||
...cardMemberships,
|
||||
...cardLabels,
|
||||
...taskLists,
|
||||
...tasks,
|
||||
...attachments,
|
||||
...customFieldGroups,
|
||||
...customFields,
|
||||
...customFieldValues,
|
||||
...comments,
|
||||
...activities,
|
||||
...commentActivities,
|
||||
...notifications,
|
||||
...notificationServices,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
import socket from './socket';
|
||||
import { transformCard } from './cards';
|
||||
import { transformAttachment } from './attachments';
|
||||
import { transformActivity } from './activities';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createList = (boardId, data, headers) =>
|
||||
socket.post(`/boards/${boardId}/lists`, data, headers);
|
||||
|
||||
const getList = (id, headers) =>
|
||||
socket.get(`/lists/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
cards: body.included.cards.map(transformCard),
|
||||
attachments: body.included.attachments.map(transformAttachment),
|
||||
},
|
||||
}));
|
||||
|
||||
const updateList = (id, data, headers) => socket.patch(`/lists/${id}`, data, headers);
|
||||
|
||||
const sortList = (id, data, headers) =>
|
||||
@@ -17,11 +36,30 @@ const sortList = (id, data, headers) =>
|
||||
},
|
||||
}));
|
||||
|
||||
const deleteList = (id, headers) => socket.delete(`/lists/${id}`, undefined, headers);
|
||||
const moveListCards = (id, data, headers) =>
|
||||
socket.post(`/lists/${id}/move-cards`, data, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...omit(body.included, 'actions'),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
activities: body.included.actions.map(transformActivity),
|
||||
},
|
||||
}));
|
||||
|
||||
const clearList = (id, headers) => socket.post(`/lists/${id}/clear`, undefined, headers);
|
||||
|
||||
const deleteList = (id, headers) =>
|
||||
socket.delete(`/lists/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
cards: body.included.cards.map(transformCard),
|
||||
},
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleListSort = (next) => (body) => {
|
||||
const makeHandleListDelete = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
included: {
|
||||
@@ -33,8 +71,11 @@ const makeHandleListSort = (next) => (body) => {
|
||||
|
||||
export default {
|
||||
createList,
|
||||
getList,
|
||||
updateList,
|
||||
sortList,
|
||||
moveListCards,
|
||||
clearList,
|
||||
deleteList,
|
||||
makeHandleListSort,
|
||||
makeHandleListDelete,
|
||||
};
|
||||
|
||||
31
client/src/api/notification-services.js
Executable file
31
client/src/api/notification-services.js
Executable file
@@ -0,0 +1,31 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createNotificationServiceInUser = (userId, data, headers) =>
|
||||
socket.post(`/users/${userId}/notification-services`, data, headers);
|
||||
|
||||
const createNotificationServiceInBoard = (boardId, data, headers) =>
|
||||
socket.post(`/boards/${boardId}/notification-services`, data, headers);
|
||||
|
||||
const updateNotificationService = (id, data, headers) =>
|
||||
socket.patch(`/notification-services/${id}`, data, headers);
|
||||
|
||||
const testNotificationService = (id, headers) =>
|
||||
socket.post(`/notification-services/${id}/test`, undefined, headers);
|
||||
|
||||
const deleteNotificationService = (id, headers) =>
|
||||
socket.delete(`/notification-services/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createNotificationServiceInUser,
|
||||
createNotificationServiceInBoard,
|
||||
updateNotificationService,
|
||||
testNotificationService,
|
||||
deleteNotificationService,
|
||||
};
|
||||
@@ -1,15 +1,21 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import omit from 'lodash/omit';
|
||||
|
||||
import socket from './socket';
|
||||
import { transformUser } from './users';
|
||||
import { transformCard } from './cards';
|
||||
import { transformActivity } from './activities';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformNotification = (notification) => ({
|
||||
...omit(notification, 'actionId'),
|
||||
activityId: notification.actionId,
|
||||
...(notification.actionId
|
||||
? {
|
||||
...omit(notification, 'actionId'),
|
||||
activityId: notification.actionId,
|
||||
}
|
||||
: notification),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
@@ -18,28 +24,25 @@ const getNotifications = (headers) =>
|
||||
socket.get('/notifications', undefined, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformNotification),
|
||||
included: {
|
||||
...omit(body.included, 'actions'),
|
||||
users: body.included.users.map(transformUser),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
activities: body.included.actions.map(transformActivity),
|
||||
},
|
||||
}));
|
||||
|
||||
const getNotification = (id, headers) =>
|
||||
/* const getNotification = (id, headers) =>
|
||||
socket.get(`/notifications/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformNotification(body.item),
|
||||
included: {
|
||||
...omit(body.included, 'actions'),
|
||||
users: body.included.users.map(transformUser),
|
||||
cards: body.included.cards.map(transformCard),
|
||||
activities: body.included.actions.map(transformActivity),
|
||||
},
|
||||
})); */
|
||||
|
||||
const updateNotification = (id, data, headers) =>
|
||||
socket.patch(`/notifications/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformNotification(body.item),
|
||||
}));
|
||||
|
||||
const updateNotifications = (ids, data, headers) =>
|
||||
socket.patch(`/notifications/${ids.join(',')}`, data, headers).then((body) => ({
|
||||
const readAllNotifications = (headers) =>
|
||||
socket.post('/notifications/read-all', undefined, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformNotification),
|
||||
}));
|
||||
@@ -57,8 +60,9 @@ const makeHandleNotificationUpdate = makeHandleNotificationCreate;
|
||||
|
||||
export default {
|
||||
getNotifications,
|
||||
getNotification,
|
||||
updateNotifications,
|
||||
// getNotification,
|
||||
updateNotification,
|
||||
readAllNotifications,
|
||||
makeHandleNotificationCreate,
|
||||
makeHandleNotificationUpdate,
|
||||
};
|
||||
|
||||
@@ -1,40 +1,19 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformProjectManager = (projectManager) => ({
|
||||
...projectManager,
|
||||
createdAt: new Date(projectManager.createdAt),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createProjectManager = (projectId, data, headers) =>
|
||||
socket.post(`/projects/${projectId}/managers`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformProjectManager(body.item),
|
||||
}));
|
||||
socket.post(`/projects/${projectId}/project-managers`, data, headers);
|
||||
|
||||
const deleteProjectManager = (id, headers) =>
|
||||
socket.delete(`/project-managers/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformProjectManager(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleProjectManagerCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformProjectManager(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleProjectManagerDelete = makeHandleProjectManagerCreate;
|
||||
socket.delete(`/project-managers/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createProjectManager,
|
||||
deleteProjectManager,
|
||||
makeHandleProjectManagerCreate,
|
||||
makeHandleProjectManagerDelete,
|
||||
};
|
||||
|
||||
@@ -1,47 +1,20 @@
|
||||
import http from './http';
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
import { transformUser } from './users';
|
||||
import { transformProjectManager } from './project-managers';
|
||||
import { transformBoardMembership } from './board-memberships';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getProjects = (headers) =>
|
||||
socket.get('/projects', undefined, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
},
|
||||
}));
|
||||
const getProjects = (headers) => socket.get('/projects', undefined, headers);
|
||||
|
||||
const createProject = (data, headers) =>
|
||||
socket.post('/projects', data, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||
},
|
||||
}));
|
||||
const createProject = (data, headers) => socket.post('/projects', data, headers);
|
||||
|
||||
const getProject = (id, headers) =>
|
||||
socket.get(`/projects/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
included: {
|
||||
...body.included,
|
||||
users: body.included.users.map(transformUser),
|
||||
projectManagers: body.included.projectManagers.map(transformProjectManager),
|
||||
boardMemberships: body.included.boardMemberships.map(transformBoardMembership),
|
||||
},
|
||||
}));
|
||||
const getProject = (id, headers) => socket.get(`/projects/${id}`, undefined, headers);
|
||||
|
||||
const updateProject = (id, data, headers) => socket.patch(`/projects/${id}`, data, headers);
|
||||
|
||||
const updateProjectBackgroundImage = (id, data, headers) =>
|
||||
http.post(`/projects/${id}/background-image`, data, headers);
|
||||
|
||||
const deleteProject = (id, headers) => socket.delete(`/projects/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
@@ -49,6 +22,5 @@ export default {
|
||||
createProject,
|
||||
getProject,
|
||||
updateProject,
|
||||
updateProjectBackgroundImage,
|
||||
deleteProject,
|
||||
};
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import http from './http';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getConfig = (headers) => http.get('/config', undefined, headers);
|
||||
|
||||
export default {
|
||||
getConfig,
|
||||
};
|
||||
@@ -1,3 +1,8 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socketIOClient from 'socket.io-client';
|
||||
import sailsIOClient from 'sails.io.js';
|
||||
|
||||
@@ -5,15 +10,14 @@ import Config from '../constants/Config';
|
||||
|
||||
const io = sailsIOClient(socketIOClient);
|
||||
|
||||
io.sails.url = Config.SERVER_HOST_NAME;
|
||||
io.sails.url = Config.SERVER_BASE_URL;
|
||||
io.sails.autoConnect = false;
|
||||
io.sails.reconnection = true;
|
||||
io.sails.useCORSRouteToGetCookie = false;
|
||||
io.sails.environment = process.env.NODE_ENV;
|
||||
io.sails.environment = import.meta.env.MODE;
|
||||
|
||||
const { socket } = io;
|
||||
|
||||
socket.path = `${Config.SERVER_BASE_PATH}/socket.io`;
|
||||
socket.connect = socket._connect; // eslint-disable-line no-underscore-dangle
|
||||
|
||||
['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].forEach((method) => {
|
||||
|
||||
24
client/src/api/task-lists.js
Normal file
24
client/src/api/task-lists.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createTaskList = (cardId, data, headers) =>
|
||||
socket.post(`/cards/${cardId}/task-lists`, data, headers);
|
||||
|
||||
const getTaskList = (id, headers) => socket.get(`/task-lists/${id}`, undefined, headers);
|
||||
|
||||
const updateTaskList = (id, data, headers) => socket.patch(`/task-lists/${id}`, data, headers);
|
||||
|
||||
const deleteTaskList = (id, headers) => socket.delete(`/task-lists/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
createTaskList,
|
||||
getTaskList,
|
||||
updateTaskList,
|
||||
deleteTaskList,
|
||||
};
|
||||
@@ -1,8 +1,14 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import socket from './socket';
|
||||
|
||||
/* Actions */
|
||||
|
||||
const createTask = (cardId, data, headers) => socket.post(`/cards/${cardId}/tasks`, data, headers);
|
||||
const createTask = (taskListId, data, headers) =>
|
||||
socket.post(`/task-lists/${taskListId}/tasks`, data, headers);
|
||||
|
||||
const updateTask = (id, data, headers) => socket.patch(`/tasks/${id}`, data, headers);
|
||||
|
||||
|
||||
@@ -1,92 +1,44 @@
|
||||
/*!
|
||||
* Copyright (c) 2024 PLANKA Software GmbH
|
||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
import http from './http';
|
||||
import socket from './socket';
|
||||
|
||||
/* Transformers */
|
||||
|
||||
export const transformUser = (user) => ({
|
||||
...user,
|
||||
createdAt: new Date(user.createdAt),
|
||||
});
|
||||
|
||||
/* Actions */
|
||||
|
||||
const getUsers = (headers) =>
|
||||
socket.get('/users', undefined, headers).then((body) => ({
|
||||
...body,
|
||||
items: body.items.map(transformUser),
|
||||
}));
|
||||
const getUsers = (headers) => socket.get('/users', undefined, headers);
|
||||
|
||||
const createUser = (data, headers) =>
|
||||
socket.post('/users', data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
const createUser = (data, headers) => socket.post('/users', data, headers);
|
||||
|
||||
const getUser = (id, headers) =>
|
||||
/* const getUser = (id, headers) =>
|
||||
socket.get(`/users/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
})); */
|
||||
|
||||
const getCurrentUser = (subscribe, headers) =>
|
||||
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
socket.get(`/users/me${subscribe ? '?subscribe=true' : ''}`, undefined, headers);
|
||||
|
||||
const updateUser = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
const updateUser = (id, data, headers) => socket.patch(`/users/${id}`, data, headers);
|
||||
|
||||
const updateUserEmail = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}/email`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
const updateUserEmail = (id, data, headers) => socket.patch(`/users/${id}/email`, data, headers);
|
||||
|
||||
const updateUserPassword = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}/password`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
socket.patch(`/users/${id}/password`, data, headers);
|
||||
|
||||
const updateUserUsername = (id, data, headers) =>
|
||||
socket.patch(`/users/${id}/username`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
socket.patch(`/users/${id}/username`, data, headers);
|
||||
|
||||
const updateUserAvatar = (id, data, headers) =>
|
||||
http.post(`/users/${id}/avatar`, data, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
const updateUserAvatar = (id, data, headers) => http.post(`/users/${id}/avatar`, data, headers);
|
||||
|
||||
const deleteUser = (id, headers) =>
|
||||
socket.delete(`/users/${id}`, undefined, headers).then((body) => ({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
}));
|
||||
|
||||
/* Event handlers */
|
||||
|
||||
const makeHandleUserCreate = (next) => (body) => {
|
||||
next({
|
||||
...body,
|
||||
item: transformUser(body.item),
|
||||
});
|
||||
};
|
||||
|
||||
const makeHandleUserUpdate = makeHandleUserCreate;
|
||||
|
||||
const makeHandleUserDelete = makeHandleUserCreate;
|
||||
const deleteUser = (id, headers) => socket.delete(`/users/${id}`, undefined, headers);
|
||||
|
||||
export default {
|
||||
getUsers,
|
||||
createUser,
|
||||
getUser,
|
||||
// getUser,
|
||||
getCurrentUser,
|
||||
updateUser,
|
||||
updateUserEmail,
|
||||
@@ -94,7 +46,4 @@ export default {
|
||||
updateUserUsername,
|
||||
updateUserAvatar,
|
||||
deleteUser,
|
||||
makeHandleUserCreate,
|
||||
makeHandleUserUpdate,
|
||||
makeHandleUserDelete,
|
||||
};
|
||||
|
||||
2337
client/src/assets/css/font-awesome.css
vendored
2337
client/src/assets/css/font-awesome.css
vendored
File diff suppressed because it is too large
Load Diff
4
client/src/assets/css/font-awesome.min.css
vendored
4
client/src/assets/css/font-awesome.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 559 KiB After Width: | Height: | Size: 157 KiB |
BIN
client/src/assets/images/deleted-user.png
Normal file
BIN
client/src/assets/images/deleted-user.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 25 KiB |
@@ -1,38 +0,0 @@
|
||||
import upperFirst from 'lodash/upperFirst';
|
||||
import camelCase from 'lodash/camelCase';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { ProjectBackgroundTypes } from '../../constants/Enums';
|
||||
|
||||
import styles from './Background.module.scss';
|
||||
import globalStyles from '../../styles.module.scss';
|
||||
|
||||
function Background({ type, name, imageUrl }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
styles.wrapper,
|
||||
type === ProjectBackgroundTypes.GRADIENT &&
|
||||
globalStyles[`background${upperFirst(camelCase(name))}`],
|
||||
)}
|
||||
style={{
|
||||
background: type === 'image' && `url("${imageUrl}") center / cover`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Background.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
name: PropTypes.string,
|
||||
imageUrl: PropTypes.string,
|
||||
};
|
||||
|
||||
Background.defaultProps = {
|
||||
name: undefined,
|
||||
imageUrl: undefined,
|
||||
};
|
||||
|
||||
export default Background;
|
||||
@@ -1,10 +0,0 @@
|
||||
:global(#app) {
|
||||
.wrapper {
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
max-width: 100vw;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Background from './Background';
|
||||
|
||||
export default Background;
|
||||
@@ -1,201 +0,0 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
import { closePopup } from '../../lib/popup';
|
||||
|
||||
import DroppableTypes from '../../constants/DroppableTypes';
|
||||
import ListContainer from '../../containers/ListContainer';
|
||||
import CardModalContainer from '../../containers/CardModalContainer';
|
||||
import ListAdd from './ListAdd';
|
||||
import { ReactComponent as PlusMathIcon } from '../../assets/images/plus-math-icon.svg';
|
||||
|
||||
import styles from './Board.module.scss';
|
||||
import globalStyles from '../../styles.module.scss';
|
||||
|
||||
const parseDndId = (dndId) => dndId.split(':')[1];
|
||||
|
||||
const Board = React.memo(
|
||||
({ listIds, isCardModalOpened, canEdit, onListCreate, onListMove, onCardMove }) => {
|
||||
const [t] = useTranslation();
|
||||
const [isListAddOpened, setIsListAddOpened] = useState(false);
|
||||
|
||||
const wrapper = useRef(null);
|
||||
const prevPosition = useRef(null);
|
||||
|
||||
const handleAddListClick = useCallback(() => {
|
||||
setIsListAddOpened(true);
|
||||
}, []);
|
||||
|
||||
const handleAddListClose = useCallback(() => {
|
||||
setIsListAddOpened(false);
|
||||
}, []);
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
document.body.classList.add(globalStyles.dragging);
|
||||
closePopup();
|
||||
}, []);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
({ draggableId, type, source, destination }) => {
|
||||
document.body.classList.remove(globalStyles.dragging);
|
||||
|
||||
if (
|
||||
!destination ||
|
||||
(source.droppableId === destination.droppableId && source.index === destination.index)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = parseDndId(draggableId);
|
||||
|
||||
switch (type) {
|
||||
case DroppableTypes.LIST:
|
||||
onListMove(id, destination.index);
|
||||
|
||||
break;
|
||||
case DroppableTypes.CARD:
|
||||
onCardMove(id, parseDndId(destination.droppableId), destination.index);
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
[onListMove, onCardMove],
|
||||
);
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(event) => {
|
||||
// If button is defined and not equal to 0 (left click)
|
||||
if (event.button) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target !== wrapper.current && !event.target.dataset.dragScroller) {
|
||||
return;
|
||||
}
|
||||
|
||||
prevPosition.current = event.clientX;
|
||||
|
||||
window.getSelection().removeAllRanges();
|
||||
document.body.classList.add(globalStyles.dragScrolling);
|
||||
},
|
||||
[wrapper],
|
||||
);
|
||||
|
||||
const handleWindowMouseMove = useCallback(
|
||||
(event) => {
|
||||
if (prevPosition.current === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
window.scrollBy({
|
||||
left: prevPosition.current - event.clientX,
|
||||
});
|
||||
|
||||
prevPosition.current = event.clientX;
|
||||
},
|
||||
[prevPosition],
|
||||
);
|
||||
|
||||
const handleWindowMouseRelease = useCallback(() => {
|
||||
if (prevPosition.current === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
prevPosition.current = null;
|
||||
document.body.classList.remove(globalStyles.dragScrolling);
|
||||
}, [prevPosition]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.style.overflowX = 'auto';
|
||||
|
||||
return () => {
|
||||
document.body.style.overflowX = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isListAddOpened) {
|
||||
window.scroll(document.body.scrollWidth, 0);
|
||||
}
|
||||
}, [listIds, isListAddOpened]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('mousemove', handleWindowMouseMove);
|
||||
|
||||
window.addEventListener('mouseup', handleWindowMouseRelease);
|
||||
window.addEventListener('blur', handleWindowMouseRelease);
|
||||
window.addEventListener('contextmenu', handleWindowMouseRelease);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('mousemove', handleWindowMouseMove);
|
||||
|
||||
window.removeEventListener('mouseup', handleWindowMouseRelease);
|
||||
window.removeEventListener('blur', handleWindowMouseRelease);
|
||||
window.removeEventListener('contextmenu', handleWindowMouseRelease);
|
||||
};
|
||||
}, [handleWindowMouseMove, handleWindowMouseRelease]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div ref={wrapper} className={styles.wrapper} onMouseDown={handleMouseDown}>
|
||||
<div>
|
||||
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId="board" type={DroppableTypes.LIST} direction="horizontal">
|
||||
{({ innerRef, droppableProps, placeholder }) => (
|
||||
<div
|
||||
{...droppableProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
data-drag-scroller
|
||||
ref={innerRef}
|
||||
className={styles.lists}
|
||||
>
|
||||
{listIds.map((listId, index) => (
|
||||
<ListContainer key={listId} id={listId} index={index} />
|
||||
))}
|
||||
{placeholder}
|
||||
{canEdit && (
|
||||
<div data-drag-scroller className={styles.list}>
|
||||
{isListAddOpened ? (
|
||||
<ListAdd onCreate={onListCreate} onClose={handleAddListClose} />
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className={styles.addListButton}
|
||||
onClick={handleAddListClick}
|
||||
>
|
||||
<PlusMathIcon className={styles.addListButtonIcon} />
|
||||
<span className={styles.addListButtonText}>
|
||||
{listIds.length > 0
|
||||
? t('action.addAnotherList')
|
||||
: t('action.addList')}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</div>
|
||||
{isCardModalOpened && <CardModalContainer />}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Board.propTypes = {
|
||||
listIds: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isCardModalOpened: PropTypes.bool.isRequired,
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
onListCreate: PropTypes.func.isRequired,
|
||||
onListMove: PropTypes.func.isRequired,
|
||||
onCardMove: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Board;
|
||||
@@ -1,89 +0,0 @@
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Input } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../lib/hooks';
|
||||
|
||||
import { useClosableForm, useForm } from '../../hooks';
|
||||
|
||||
import styles from './ListAdd.module.scss';
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
name: '',
|
||||
};
|
||||
|
||||
const ListAdd = React.memo(({ onCreate, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||
const [focusNameFieldState, focusNameField] = useToggle();
|
||||
|
||||
const nameField = useRef(null);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[onClose],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(onClose);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
name: data.name.trim(),
|
||||
};
|
||||
|
||||
if (!cleanData.name) {
|
||||
nameField.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
onCreate(cleanData);
|
||||
|
||||
setData(DEFAULT_DATA);
|
||||
focusNameField();
|
||||
}, [onCreate, data, setData, focusNameField]);
|
||||
|
||||
useEffect(() => {
|
||||
nameField.current.focus();
|
||||
}, []);
|
||||
|
||||
useDidUpdate(() => {
|
||||
nameField.current.focus();
|
||||
}, [focusNameFieldState]);
|
||||
|
||||
return (
|
||||
<Form className={styles.wrapper} onSubmit={handleSubmit}>
|
||||
<Input
|
||||
ref={nameField}
|
||||
name="name"
|
||||
value={data.name}
|
||||
placeholder={t('common.enterListTitle')}
|
||||
className={styles.field}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<Button
|
||||
positive
|
||||
content={t('action.addList')}
|
||||
className={styles.button}
|
||||
onMouseOver={handleControlMouseOver}
|
||||
onMouseOut={handleControlMouseOut}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
ListAdd.propTypes = {
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ListAdd;
|
||||
@@ -1,33 +0,0 @@
|
||||
:global(#app) {
|
||||
.button {
|
||||
min-height: 30px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.field {
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 1px 0 #ccc;
|
||||
color: #333;
|
||||
outline: none;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
&:focus {
|
||||
border-color: #298fca;
|
||||
box-shadow: 0 0 2px #298fca;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background: #e2e4e6;
|
||||
border-radius: 3px;
|
||||
padding: 4px;
|
||||
transition: opacity 40ms ease-in;
|
||||
width: 272px;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import Board from './Board';
|
||||
|
||||
export default Board;
|
||||
@@ -1,97 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Filters from './Filters';
|
||||
import Memberships from '../Memberships';
|
||||
import BoardMembershipPermissionsSelectStep from '../BoardMembershipPermissionsSelectStep';
|
||||
|
||||
import styles from './BoardActions.module.scss';
|
||||
|
||||
const BoardActions = React.memo(
|
||||
({
|
||||
memberships,
|
||||
labels,
|
||||
filterUsers,
|
||||
filterLabels,
|
||||
filterText,
|
||||
allUsers,
|
||||
canEdit,
|
||||
canEditMemberships,
|
||||
onMembershipCreate,
|
||||
onMembershipUpdate,
|
||||
onMembershipDelete,
|
||||
onUserToFilterAdd,
|
||||
onUserFromFilterRemove,
|
||||
onLabelToFilterAdd,
|
||||
onLabelFromFilterRemove,
|
||||
onLabelCreate,
|
||||
onLabelUpdate,
|
||||
onLabelMove,
|
||||
onLabelDelete,
|
||||
onTextFilterUpdate,
|
||||
}) => {
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.actions}>
|
||||
<div className={styles.action}>
|
||||
<Memberships
|
||||
items={memberships}
|
||||
allUsers={allUsers}
|
||||
permissionsSelectStep={BoardMembershipPermissionsSelectStep}
|
||||
canEdit={canEditMemberships}
|
||||
onCreate={onMembershipCreate}
|
||||
onUpdate={onMembershipUpdate}
|
||||
onDelete={onMembershipDelete}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.action}>
|
||||
<Filters
|
||||
users={filterUsers}
|
||||
labels={filterLabels}
|
||||
filterText={filterText}
|
||||
allBoardMemberships={memberships}
|
||||
allLabels={labels}
|
||||
canEdit={canEdit}
|
||||
onUserAdd={onUserToFilterAdd}
|
||||
onUserRemove={onUserFromFilterRemove}
|
||||
onLabelAdd={onLabelToFilterAdd}
|
||||
onLabelRemove={onLabelFromFilterRemove}
|
||||
onLabelCreate={onLabelCreate}
|
||||
onLabelUpdate={onLabelUpdate}
|
||||
onLabelMove={onLabelMove}
|
||||
onLabelDelete={onLabelDelete}
|
||||
onTextFilterUpdate={onTextFilterUpdate}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BoardActions.propTypes = {
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
memberships: PropTypes.array.isRequired,
|
||||
labels: PropTypes.array.isRequired,
|
||||
filterUsers: PropTypes.array.isRequired,
|
||||
filterLabels: PropTypes.array.isRequired,
|
||||
filterText: PropTypes.string.isRequired,
|
||||
allUsers: PropTypes.array.isRequired,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
canEditMemberships: PropTypes.bool.isRequired,
|
||||
onMembershipCreate: PropTypes.func.isRequired,
|
||||
onMembershipUpdate: PropTypes.func.isRequired,
|
||||
onMembershipDelete: PropTypes.func.isRequired,
|
||||
onUserToFilterAdd: PropTypes.func.isRequired,
|
||||
onUserFromFilterRemove: PropTypes.func.isRequired,
|
||||
onLabelToFilterAdd: PropTypes.func.isRequired,
|
||||
onLabelFromFilterRemove: PropTypes.func.isRequired,
|
||||
onLabelCreate: PropTypes.func.isRequired,
|
||||
onLabelUpdate: PropTypes.func.isRequired,
|
||||
onLabelMove: PropTypes.func.isRequired,
|
||||
onLabelDelete: PropTypes.func.isRequired,
|
||||
onTextFilterUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default BoardActions;
|
||||
@@ -1,26 +0,0 @@
|
||||
:global(#app) {
|
||||
.action {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
justify-content: flex-start;
|
||||
margin: 20px 20px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Icon } from 'semantic-ui-react';
|
||||
import { usePopup } from '../../lib/popup';
|
||||
import { Input } from '../../lib/custom-ui';
|
||||
|
||||
import User from '../User';
|
||||
import Label from '../Label';
|
||||
import BoardMembershipsStep from '../BoardMembershipsStep';
|
||||
import LabelsStep from '../LabelsStep';
|
||||
|
||||
import styles from './Filters.module.scss';
|
||||
|
||||
const Filters = React.memo(
|
||||
({
|
||||
users,
|
||||
labels,
|
||||
filterText,
|
||||
allBoardMemberships,
|
||||
allLabels,
|
||||
canEdit,
|
||||
onUserAdd,
|
||||
onUserRemove,
|
||||
onLabelAdd,
|
||||
onLabelRemove,
|
||||
onLabelCreate,
|
||||
onLabelUpdate,
|
||||
onLabelMove,
|
||||
onLabelDelete,
|
||||
onTextFilterUpdate,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const [isSearchFocused, setIsSearchFocused] = useState(false);
|
||||
|
||||
const searchFieldRef = useRef(null);
|
||||
|
||||
const cancelSearch = useCallback(() => {
|
||||
onTextFilterUpdate('');
|
||||
searchFieldRef.current.blur();
|
||||
}, [onTextFilterUpdate]);
|
||||
|
||||
const handleRemoveUserClick = useCallback(
|
||||
(id) => {
|
||||
onUserRemove(id);
|
||||
},
|
||||
[onUserRemove],
|
||||
);
|
||||
|
||||
const handleRemoveLabelClick = useCallback(
|
||||
(id) => {
|
||||
onLabelRemove(id);
|
||||
},
|
||||
[onLabelRemove],
|
||||
);
|
||||
|
||||
const handleSearchChange = useCallback(
|
||||
(_, { value }) => {
|
||||
onTextFilterUpdate(value);
|
||||
},
|
||||
[onTextFilterUpdate],
|
||||
);
|
||||
|
||||
const handleSearchFocus = useCallback(() => {
|
||||
setIsSearchFocused(true);
|
||||
}, []);
|
||||
|
||||
const handleSearchKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.key === 'Escape') {
|
||||
cancelSearch();
|
||||
}
|
||||
},
|
||||
[cancelSearch],
|
||||
);
|
||||
|
||||
const handleSearchBlur = useCallback(() => {
|
||||
setIsSearchFocused(false);
|
||||
}, []);
|
||||
|
||||
const handleCancelSearchClick = useCallback(() => {
|
||||
cancelSearch();
|
||||
}, [cancelSearch]);
|
||||
|
||||
const BoardMembershipsPopup = usePopup(BoardMembershipsStep);
|
||||
const LabelsPopup = usePopup(LabelsStep);
|
||||
|
||||
const isSearchActive = filterText || isSearchFocused;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={styles.filter}>
|
||||
<BoardMembershipsPopup
|
||||
items={allBoardMemberships}
|
||||
currentUserIds={users.map((user) => user.id)}
|
||||
title="common.filterByMembers"
|
||||
onUserSelect={onUserAdd}
|
||||
onUserDeselect={onUserRemove}
|
||||
>
|
||||
<button type="button" className={styles.filterButton}>
|
||||
<span className={styles.filterTitle}>{`${t('common.members')}:`}</span>
|
||||
{users.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
|
||||
</button>
|
||||
</BoardMembershipsPopup>
|
||||
{users.map((user) => (
|
||||
<span key={user.id} className={styles.filterItem}>
|
||||
<User
|
||||
name={user.name}
|
||||
avatarUrl={user.avatarUrl}
|
||||
size="tiny"
|
||||
onClick={() => handleRemoveUserClick(user.id)}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
<span className={styles.filter}>
|
||||
<LabelsPopup
|
||||
items={allLabels}
|
||||
currentIds={labels.map((label) => label.id)}
|
||||
title="common.filterByLabels"
|
||||
canEdit={canEdit}
|
||||
onSelect={onLabelAdd}
|
||||
onDeselect={onLabelRemove}
|
||||
onCreate={onLabelCreate}
|
||||
onUpdate={onLabelUpdate}
|
||||
onMove={onLabelMove}
|
||||
onDelete={onLabelDelete}
|
||||
>
|
||||
<button type="button" className={styles.filterButton}>
|
||||
<span className={styles.filterTitle}>{`${t('common.labels')}:`}</span>
|
||||
{labels.length === 0 && <span className={styles.filterLabel}>{t('common.all')}</span>}
|
||||
</button>
|
||||
</LabelsPopup>
|
||||
{labels.map((label) => (
|
||||
<span key={label.id} className={styles.filterItem}>
|
||||
<Label
|
||||
name={label.name}
|
||||
color={label.color}
|
||||
size="small"
|
||||
onClick={() => handleRemoveLabelClick(label.id)}
|
||||
/>
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
<span className={styles.filter}>
|
||||
<Input
|
||||
ref={searchFieldRef}
|
||||
value={filterText}
|
||||
placeholder={t('common.searchCards')}
|
||||
icon={
|
||||
isSearchActive ? (
|
||||
<Icon link name="cancel" onClick={handleCancelSearchClick} />
|
||||
) : (
|
||||
'search'
|
||||
)
|
||||
}
|
||||
className={classNames(styles.search, !isSearchActive && styles.searchInactive)}
|
||||
onFocus={handleSearchFocus}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
onChange={handleSearchChange}
|
||||
onBlur={handleSearchBlur}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Filters.propTypes = {
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
users: PropTypes.array.isRequired,
|
||||
labels: PropTypes.array.isRequired,
|
||||
filterText: PropTypes.string.isRequired,
|
||||
allBoardMemberships: PropTypes.array.isRequired,
|
||||
allLabels: PropTypes.array.isRequired,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
onUserAdd: PropTypes.func.isRequired,
|
||||
onUserRemove: PropTypes.func.isRequired,
|
||||
onLabelAdd: PropTypes.func.isRequired,
|
||||
onLabelRemove: PropTypes.func.isRequired,
|
||||
onLabelCreate: PropTypes.func.isRequired,
|
||||
onLabelUpdate: PropTypes.func.isRequired,
|
||||
onLabelMove: PropTypes.func.isRequired,
|
||||
onLabelDelete: PropTypes.func.isRequired,
|
||||
onTextFilterUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Filters;
|
||||
@@ -1,3 +0,0 @@
|
||||
import BoardActions from './BoardActions';
|
||||
|
||||
export default BoardActions;
|
||||
@@ -1,107 +0,0 @@
|
||||
import { dequal } from 'dequal';
|
||||
import omit from 'lodash/omit';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form, Menu, Radio, Segment } from 'semantic-ui-react';
|
||||
import { Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { BoardMembershipRoles } from '../../constants/Enums';
|
||||
|
||||
import styles from './BoardMembershipPermissionsSelectStep.module.scss';
|
||||
|
||||
const BoardMembershipPermissionsSelectStep = React.memo(
|
||||
({ defaultData, title, buttonContent, onSelect, onBack, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [data, setData] = useState(() => ({
|
||||
role: BoardMembershipRoles.EDITOR,
|
||||
canComment: null,
|
||||
...defaultData,
|
||||
}));
|
||||
|
||||
const handleSelectRoleClick = useCallback((role) => {
|
||||
setData((prevData) => ({
|
||||
...prevData,
|
||||
role,
|
||||
canComment: role === BoardMembershipRoles.VIEWER ? !!prevData.canComment : null,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSettingChange = useCallback((_, { name: fieldName, checked: value }) => {
|
||||
setData((prevData) => ({
|
||||
...prevData,
|
||||
[fieldName]: value,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
if (!dequal(data, defaultData)) {
|
||||
onSelect(data.role === BoardMembershipRoles.VIEWER ? data : omit(data, 'canComment'));
|
||||
}
|
||||
|
||||
onClose();
|
||||
}, [defaultData, onSelect, onClose, data]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popup.Header onBack={onBack}>
|
||||
{t(title, {
|
||||
context: 'title',
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
<Menu.Item
|
||||
active={data.role === BoardMembershipRoles.EDITOR}
|
||||
onClick={() => handleSelectRoleClick(BoardMembershipRoles.EDITOR)}
|
||||
>
|
||||
<div className={styles.menuItemTitle}>{t('common.editor')}</div>
|
||||
<div className={styles.menuItemDescription}>
|
||||
{t('common.canEditContentOfBoard')}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
active={data.role === BoardMembershipRoles.VIEWER}
|
||||
onClick={() => handleSelectRoleClick(BoardMembershipRoles.VIEWER)}
|
||||
>
|
||||
<div className={styles.menuItemTitle}>{t('common.viewer')}</div>
|
||||
<div className={styles.menuItemDescription}>{t('common.canOnlyViewBoard')}</div>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
{data.role === BoardMembershipRoles.VIEWER && (
|
||||
<Segment basic className={styles.settings}>
|
||||
<Radio
|
||||
toggle
|
||||
name="canComment"
|
||||
checked={data.canComment}
|
||||
label={t('common.canComment')}
|
||||
onChange={handleSettingChange}
|
||||
/>
|
||||
</Segment>
|
||||
)}
|
||||
<Button positive content={t(buttonContent)} />
|
||||
</Form>
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BoardMembershipPermissionsSelectStep.propTypes = {
|
||||
defaultData: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
title: PropTypes.string,
|
||||
buttonContent: PropTypes.string,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onBack: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
BoardMembershipPermissionsSelectStep.defaultProps = {
|
||||
defaultData: undefined,
|
||||
title: 'common.selectPermissions',
|
||||
buttonContent: 'action.selectPermissions',
|
||||
};
|
||||
|
||||
export default BoardMembershipPermissionsSelectStep;
|
||||
@@ -1,18 +0,0 @@
|
||||
:global(#app) {
|
||||
.menu {
|
||||
margin: 0 auto 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.menuItemDescription {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.menuItemTitle {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.settings {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
import BoardMembershipPermissionsSelectStep from './BoardMembershipPermissionsSelectStep';
|
||||
|
||||
export default BoardMembershipPermissionsSelectStep;
|
||||
@@ -1,103 +0,0 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Menu } from 'semantic-ui-react';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { useField } from '../../hooks';
|
||||
import Item from './Item';
|
||||
|
||||
import styles from './BoardMembershipsStep.module.scss';
|
||||
|
||||
const BoardMembershipsStep = React.memo(
|
||||
({ items, currentUserIds, title, onUserSelect, onUserDeselect, onBack }) => {
|
||||
const [t] = useTranslation();
|
||||
const [search, handleSearchChange] = useField('');
|
||||
const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);
|
||||
|
||||
const filteredItems = useMemo(
|
||||
() =>
|
||||
items.filter(
|
||||
({ user }) =>
|
||||
user.email.includes(cleanSearch) ||
|
||||
user.name.toLowerCase().includes(cleanSearch) ||
|
||||
(user.username && user.username.includes(cleanSearch)),
|
||||
),
|
||||
[items, cleanSearch],
|
||||
);
|
||||
|
||||
const searchField = useRef(null);
|
||||
|
||||
const handleUserSelect = useCallback(
|
||||
(id) => {
|
||||
onUserSelect(id);
|
||||
},
|
||||
[onUserSelect],
|
||||
);
|
||||
|
||||
const handleUserDeselect = useCallback(
|
||||
(id) => {
|
||||
onUserDeselect(id);
|
||||
},
|
||||
[onUserDeselect],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
searchField.current.focus({
|
||||
preventScroll: true,
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popup.Header onBack={onBack}>
|
||||
{t(title, {
|
||||
context: 'title',
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Input
|
||||
fluid
|
||||
ref={searchField}
|
||||
value={search}
|
||||
placeholder={t('common.searchMembers')}
|
||||
icon="search"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
{filteredItems.length > 0 && (
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
{filteredItems.map((item) => (
|
||||
<Item
|
||||
key={item.id}
|
||||
isPersisted={item.isPersisted}
|
||||
isActive={currentUserIds.includes(item.user.id)}
|
||||
user={item.user}
|
||||
onUserSelect={() => handleUserSelect(item.user.id)}
|
||||
onUserDeselect={() => handleUserDeselect(item.user.id)}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
BoardMembershipsStep.propTypes = {
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
items: PropTypes.array.isRequired,
|
||||
currentUserIds: PropTypes.array.isRequired,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
title: PropTypes.string,
|
||||
onUserSelect: PropTypes.func.isRequired,
|
||||
onUserDeselect: PropTypes.func.isRequired,
|
||||
onBack: PropTypes.func,
|
||||
};
|
||||
|
||||
BoardMembershipsStep.defaultProps = {
|
||||
title: 'common.members',
|
||||
onBack: undefined,
|
||||
};
|
||||
|
||||
export default BoardMembershipsStep;
|
||||
@@ -1,21 +0,0 @@
|
||||
:global(#app) {
|
||||
.menu {
|
||||
margin: 8px auto 0;
|
||||
max-height: 60vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Menu } from 'semantic-ui-react';
|
||||
|
||||
import User from '../User';
|
||||
|
||||
import styles from './Item.module.scss';
|
||||
|
||||
const Item = React.memo(({ isPersisted, isActive, user, onUserSelect, onUserDeselect }) => {
|
||||
const handleToggleClick = useCallback(() => {
|
||||
if (isActive) {
|
||||
onUserDeselect();
|
||||
} else {
|
||||
onUserSelect();
|
||||
}
|
||||
}, [isActive, onUserSelect, onUserDeselect]);
|
||||
|
||||
return (
|
||||
<Menu.Item
|
||||
active={isActive}
|
||||
disabled={!isPersisted}
|
||||
className={classNames(styles.menuItem, isActive && styles.menuItemActive)}
|
||||
onClick={handleToggleClick}
|
||||
>
|
||||
<span className={styles.user}>
|
||||
<User name={user.name} avatarUrl={user.avatarUrl} />
|
||||
</span>
|
||||
<div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>
|
||||
{user.name}
|
||||
</div>
|
||||
</Menu.Item>
|
||||
);
|
||||
});
|
||||
|
||||
Item.propTypes = {
|
||||
isPersisted: PropTypes.bool.isRequired,
|
||||
isActive: PropTypes.bool.isRequired,
|
||||
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
onUserSelect: PropTypes.func.isRequired,
|
||||
onUserDeselect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Item;
|
||||
@@ -1,3 +0,0 @@
|
||||
import BoardMembershipsStep from './BoardMembershipsStep';
|
||||
|
||||
export default BoardMembershipsStep;
|
||||
@@ -1,3 +0,0 @@
|
||||
import AddStep from './AddStep';
|
||||
|
||||
export default AddStep;
|
||||
@@ -1,145 +0,0 @@
|
||||
import pick from 'lodash/pick';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DragDropContext, Draggable, Droppable } from 'react-beautiful-dnd';
|
||||
import { Button, Icon } from 'semantic-ui-react';
|
||||
import { closePopup, usePopup } from '../../lib/popup';
|
||||
|
||||
import Paths from '../../constants/Paths';
|
||||
import DroppableTypes from '../../constants/DroppableTypes';
|
||||
import AddStep from './AddStep';
|
||||
import EditStep from './EditStep';
|
||||
|
||||
import styles from './Boards.module.scss';
|
||||
import globalStyles from '../../styles.module.scss';
|
||||
|
||||
const Boards = React.memo(({ items, currentId, canEdit, onCreate, onUpdate, onMove, onDelete }) => {
|
||||
const tabsWrapper = useRef(null);
|
||||
|
||||
const handleWheel = useCallback(({ deltaY }) => {
|
||||
tabsWrapper.current.scrollBy({
|
||||
left: deltaY,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
document.body.classList.add(globalStyles.dragging);
|
||||
closePopup();
|
||||
}, []);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
({ draggableId, source, destination }) => {
|
||||
document.body.classList.remove(globalStyles.dragging);
|
||||
|
||||
if (!destination || source.index === destination.index) {
|
||||
return;
|
||||
}
|
||||
|
||||
onMove(draggableId, destination.index);
|
||||
},
|
||||
[onMove],
|
||||
);
|
||||
|
||||
const handleUpdate = useCallback(
|
||||
(id, data) => {
|
||||
onUpdate(id, data);
|
||||
},
|
||||
[onUpdate],
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
(id) => {
|
||||
onDelete(id);
|
||||
},
|
||||
[onDelete],
|
||||
);
|
||||
|
||||
const AddPopup = usePopup(AddStep);
|
||||
const EditPopup = usePopup(EditStep);
|
||||
|
||||
const itemsNode = items.map((item, index) => (
|
||||
<Draggable
|
||||
key={item.id}
|
||||
draggableId={item.id}
|
||||
index={index}
|
||||
isDragDisabled={!item.isPersisted || !canEdit}
|
||||
>
|
||||
{({ innerRef, draggableProps, dragHandleProps }) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<div {...draggableProps} ref={innerRef} className={styles.tabWrapper}>
|
||||
<div className={classNames(styles.tab, item.id === currentId && styles.tabActive)}>
|
||||
{item.isPersisted ? (
|
||||
<>
|
||||
<Link
|
||||
{...dragHandleProps} // eslint-disable-line react/jsx-props-no-spreading
|
||||
to={Paths.BOARDS.replace(':id', item.id)}
|
||||
title={item.name}
|
||||
className={styles.link}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
{canEdit && (
|
||||
<EditPopup
|
||||
defaultData={pick(item, 'name')}
|
||||
onUpdate={(data) => handleUpdate(item.id, data)}
|
||||
onDelete={() => handleDelete(item.id)}
|
||||
>
|
||||
<Button className={classNames(styles.editButton, styles.target)}>
|
||||
<Icon fitted name="pencil" size="small" />
|
||||
</Button>
|
||||
</EditPopup>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<span {...dragHandleProps} className={styles.link}>
|
||||
{item.name}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper} onWheel={handleWheel}>
|
||||
<div ref={tabsWrapper} className={styles.tabsWrapper}>
|
||||
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
<Droppable droppableId="boards" type={DroppableTypes.BOARD} direction="horizontal">
|
||||
{({ innerRef, droppableProps, placeholder }) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<div {...droppableProps} ref={innerRef} className={styles.tabs}>
|
||||
{itemsNode}
|
||||
{placeholder}
|
||||
{canEdit && (
|
||||
<AddPopup onCreate={onCreate}>
|
||||
<Button icon="plus" className={styles.addButton} />
|
||||
</AddPopup>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Boards.propTypes = {
|
||||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
currentId: PropTypes.string,
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onMove: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Boards.defaultProps = {
|
||||
currentId: undefined,
|
||||
};
|
||||
|
||||
export default Boards;
|
||||
@@ -1,104 +0,0 @@
|
||||
import { dequal } from 'dequal';
|
||||
import React, { useCallback, useEffect, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Form } from 'semantic-ui-react';
|
||||
import { Input, Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { useForm, useSteps } from '../../hooks';
|
||||
import DeleteStep from '../DeleteStep';
|
||||
|
||||
import styles from './EditStep.module.scss';
|
||||
|
||||
const StepTypes = {
|
||||
DELETE: 'DELETE',
|
||||
};
|
||||
|
||||
const EditStep = React.memo(({ defaultData, onUpdate, onDelete, onClose }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const [data, handleFieldChange] = useForm(() => ({
|
||||
name: '',
|
||||
...defaultData,
|
||||
}));
|
||||
|
||||
const [step, openStep, handleBack] = useSteps();
|
||||
|
||||
const nameField = useRef(null);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
name: data.name.trim(),
|
||||
};
|
||||
|
||||
if (!cleanData.name) {
|
||||
nameField.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dequal(cleanData, defaultData)) {
|
||||
onUpdate(cleanData);
|
||||
}
|
||||
|
||||
onClose();
|
||||
}, [defaultData, onUpdate, onClose, data]);
|
||||
|
||||
const handleDeleteClick = useCallback(() => {
|
||||
openStep(StepTypes.DELETE);
|
||||
}, [openStep]);
|
||||
|
||||
useEffect(() => {
|
||||
nameField.current.select();
|
||||
}, []);
|
||||
|
||||
if (step && step.type === StepTypes.DELETE) {
|
||||
return (
|
||||
<DeleteStep
|
||||
title="common.deleteBoard"
|
||||
content="common.areYouSureYouWantToDeleteThisBoard"
|
||||
buttonContent="action.deleteBoard"
|
||||
onConfirm={onDelete}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popup.Header>
|
||||
{t('common.editBoard', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div className={styles.text}>{t('common.title')}</div>
|
||||
<Input
|
||||
fluid
|
||||
ref={nameField}
|
||||
name="name"
|
||||
value={data.name}
|
||||
className={styles.field}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
<Button positive content={t('action.save')} />
|
||||
</Form>
|
||||
<Button
|
||||
content={t('action.delete')}
|
||||
className={styles.deleteButton}
|
||||
onClick={handleDeleteClick}
|
||||
/>
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
EditStep.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default EditStep;
|
||||
@@ -1,3 +0,0 @@
|
||||
import Boards from './Boards';
|
||||
|
||||
export default Boards;
|
||||
@@ -1,260 +0,0 @@
|
||||
import pick from 'lodash/pick';
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Menu } from 'semantic-ui-react';
|
||||
import { Popup } from '../../lib/custom-ui';
|
||||
|
||||
import { useSteps } from '../../hooks';
|
||||
import BoardMembershipsStep from '../BoardMembershipsStep';
|
||||
import LabelsStep from '../LabelsStep';
|
||||
import DueDateEditStep from '../DueDateEditStep';
|
||||
import StopwatchEditStep from '../StopwatchEditStep';
|
||||
import CardMoveStep from '../CardMoveStep';
|
||||
import DeleteStep from '../DeleteStep';
|
||||
|
||||
import styles from './ActionsStep.module.scss';
|
||||
|
||||
const StepTypes = {
|
||||
USERS: 'USERS',
|
||||
LABELS: 'LABELS',
|
||||
EDIT_DUE_DATE: 'EDIT_DUE_DATE',
|
||||
EDIT_STOPWATCH: 'EDIT_STOPWATCH',
|
||||
MOVE: 'MOVE',
|
||||
DELETE: 'DELETE',
|
||||
};
|
||||
|
||||
const ActionsStep = React.memo(
|
||||
({
|
||||
card,
|
||||
projectsToLists,
|
||||
boardMemberships,
|
||||
currentUserIds,
|
||||
labels,
|
||||
currentLabelIds,
|
||||
onNameEdit,
|
||||
onUpdate,
|
||||
onMove,
|
||||
onTransfer,
|
||||
onDuplicate,
|
||||
onDelete,
|
||||
onUserAdd,
|
||||
onUserRemove,
|
||||
onBoardFetch,
|
||||
onLabelAdd,
|
||||
onLabelRemove,
|
||||
onLabelCreate,
|
||||
onLabelUpdate,
|
||||
onLabelMove,
|
||||
onLabelDelete,
|
||||
onClose,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const [step, openStep, handleBack] = useSteps();
|
||||
|
||||
const handleEditNameClick = useCallback(() => {
|
||||
onNameEdit();
|
||||
onClose();
|
||||
}, [onNameEdit, onClose]);
|
||||
|
||||
const handleUsersClick = useCallback(() => {
|
||||
openStep(StepTypes.USERS);
|
||||
}, [openStep]);
|
||||
|
||||
const handleLabelsClick = useCallback(() => {
|
||||
openStep(StepTypes.LABELS);
|
||||
}, [openStep]);
|
||||
|
||||
const handleEditDueDateClick = useCallback(() => {
|
||||
openStep(StepTypes.EDIT_DUE_DATE);
|
||||
}, [openStep]);
|
||||
|
||||
const handleEditStopwatchClick = useCallback(() => {
|
||||
openStep(StepTypes.EDIT_STOPWATCH);
|
||||
}, [openStep]);
|
||||
|
||||
const handleMoveClick = useCallback(() => {
|
||||
openStep(StepTypes.MOVE);
|
||||
}, [openStep]);
|
||||
|
||||
const handleDuplicateClick = useCallback(() => {
|
||||
onDuplicate();
|
||||
onClose();
|
||||
}, [onDuplicate, onClose]);
|
||||
|
||||
const handleDeleteClick = useCallback(() => {
|
||||
openStep(StepTypes.DELETE);
|
||||
}, [openStep]);
|
||||
|
||||
const handleDueDateUpdate = useCallback(
|
||||
(dueDate) => {
|
||||
onUpdate({
|
||||
dueDate,
|
||||
});
|
||||
},
|
||||
[onUpdate],
|
||||
);
|
||||
|
||||
const handleStopwatchUpdate = useCallback(
|
||||
(stopwatch) => {
|
||||
onUpdate({
|
||||
stopwatch,
|
||||
});
|
||||
},
|
||||
[onUpdate],
|
||||
);
|
||||
|
||||
if (step) {
|
||||
switch (step.type) {
|
||||
case StepTypes.USERS:
|
||||
return (
|
||||
<BoardMembershipsStep
|
||||
items={boardMemberships}
|
||||
currentUserIds={currentUserIds}
|
||||
onUserSelect={onUserAdd}
|
||||
onUserDeselect={onUserRemove}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
case StepTypes.LABELS:
|
||||
return (
|
||||
<LabelsStep
|
||||
items={labels}
|
||||
currentIds={currentLabelIds}
|
||||
onSelect={onLabelAdd}
|
||||
onDeselect={onLabelRemove}
|
||||
onCreate={onLabelCreate}
|
||||
onUpdate={onLabelUpdate}
|
||||
onMove={onLabelMove}
|
||||
onDelete={onLabelDelete}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
case StepTypes.EDIT_DUE_DATE:
|
||||
return (
|
||||
<DueDateEditStep
|
||||
defaultValue={card.dueDate}
|
||||
onUpdate={handleDueDateUpdate}
|
||||
onBack={handleBack}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
case StepTypes.EDIT_STOPWATCH:
|
||||
return (
|
||||
<StopwatchEditStep
|
||||
defaultValue={card.stopwatch}
|
||||
onUpdate={handleStopwatchUpdate}
|
||||
onBack={handleBack}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
case StepTypes.MOVE:
|
||||
return (
|
||||
<CardMoveStep
|
||||
projectsToLists={projectsToLists}
|
||||
defaultPath={pick(card, ['projectId', 'boardId', 'listId'])}
|
||||
onMove={onMove}
|
||||
onTransfer={onTransfer}
|
||||
onBoardFetch={onBoardFetch}
|
||||
onBack={handleBack}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
case StepTypes.DELETE:
|
||||
return (
|
||||
<DeleteStep
|
||||
title="common.deleteCard"
|
||||
content="common.areYouSureYouWantToDeleteThisCard"
|
||||
buttonContent="action.deleteCard"
|
||||
onConfirm={onDelete}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popup.Header>
|
||||
{t('common.cardActions', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Popup.Header>
|
||||
<Popup.Content>
|
||||
<Menu secondary vertical className={styles.menu}>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditNameClick}>
|
||||
{t('action.editTitle', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleUsersClick}>
|
||||
{t('common.members', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleLabelsClick}>
|
||||
{t('common.labels', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditDueDateClick}>
|
||||
{t('action.editDueDate', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleEditStopwatchClick}>
|
||||
{t('action.editStopwatch', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleMoveClick}>
|
||||
{t('action.moveCard', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleDuplicateClick}>
|
||||
{t('action.duplicateCard', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
<Menu.Item className={styles.menuItem} onClick={handleDeleteClick}>
|
||||
{t('action.deleteCard', {
|
||||
context: 'title',
|
||||
})}
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Popup.Content>
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ActionsStep.propTypes = {
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
card: PropTypes.object.isRequired,
|
||||
projectsToLists: PropTypes.array.isRequired,
|
||||
boardMemberships: PropTypes.array.isRequired,
|
||||
currentUserIds: PropTypes.array.isRequired,
|
||||
labels: PropTypes.array.isRequired,
|
||||
currentLabelIds: PropTypes.array.isRequired,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
onNameEdit: PropTypes.func.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onMove: PropTypes.func.isRequired,
|
||||
onTransfer: PropTypes.func.isRequired,
|
||||
onDuplicate: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onUserAdd: PropTypes.func.isRequired,
|
||||
onUserRemove: PropTypes.func.isRequired,
|
||||
onBoardFetch: PropTypes.func.isRequired,
|
||||
onLabelAdd: PropTypes.func.isRequired,
|
||||
onLabelRemove: PropTypes.func.isRequired,
|
||||
onLabelCreate: PropTypes.func.isRequired,
|
||||
onLabelUpdate: PropTypes.func.isRequired,
|
||||
onLabelMove: PropTypes.func.isRequired,
|
||||
onLabelDelete: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ActionsStep;
|
||||
@@ -1,11 +0,0 @@
|
||||
:global(#app) {
|
||||
.menu {
|
||||
margin: -7px -12px -5px;
|
||||
width: calc(100% + 24px);
|
||||
}
|
||||
|
||||
.menuItem {
|
||||
margin: 0;
|
||||
padding-left: 14px;
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Button, Icon } from 'semantic-ui-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { usePopup } from '../../lib/popup';
|
||||
|
||||
import { startStopwatch, stopStopwatch } from '../../utils/stopwatch';
|
||||
import Paths from '../../constants/Paths';
|
||||
import Tasks from './Tasks';
|
||||
import NameEdit from './NameEdit';
|
||||
import ActionsStep from './ActionsStep';
|
||||
import User from '../User';
|
||||
import Label from '../Label';
|
||||
import DueDate from '../DueDate';
|
||||
import Stopwatch from '../Stopwatch';
|
||||
|
||||
import styles from './Card.module.scss';
|
||||
|
||||
const Card = React.memo(
|
||||
({
|
||||
id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
dueDate,
|
||||
isDueDateCompleted,
|
||||
stopwatch,
|
||||
coverUrl,
|
||||
boardId,
|
||||
listId,
|
||||
projectId,
|
||||
isPersisted,
|
||||
attachmentsTotal,
|
||||
notificationsTotal,
|
||||
users,
|
||||
labels,
|
||||
tasks,
|
||||
allProjectsToLists,
|
||||
allBoardMemberships,
|
||||
allLabels,
|
||||
canEdit,
|
||||
onUpdate,
|
||||
onMove,
|
||||
onTransfer,
|
||||
onDuplicate,
|
||||
onDelete,
|
||||
onUserAdd,
|
||||
onUserRemove,
|
||||
onBoardFetch,
|
||||
onLabelAdd,
|
||||
onLabelRemove,
|
||||
onLabelCreate,
|
||||
onLabelUpdate,
|
||||
onLabelMove,
|
||||
onLabelDelete,
|
||||
}) => {
|
||||
const nameEdit = useRef(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
if (document.activeElement) {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleToggleStopwatchClick = useCallback(
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
onUpdate({
|
||||
stopwatch: stopwatch.startedAt ? stopStopwatch(stopwatch) : startStopwatch(stopwatch),
|
||||
});
|
||||
},
|
||||
[stopwatch, onUpdate],
|
||||
);
|
||||
|
||||
const handleNameUpdate = useCallback(
|
||||
(newName) => {
|
||||
onUpdate({
|
||||
name: newName,
|
||||
});
|
||||
},
|
||||
[onUpdate],
|
||||
);
|
||||
|
||||
const handleNameEdit = useCallback(() => {
|
||||
nameEdit.current.open();
|
||||
}, []);
|
||||
|
||||
const ActionsPopup = usePopup(ActionsStep);
|
||||
|
||||
const contentNode = (
|
||||
<>
|
||||
{coverUrl && <img src={coverUrl} alt="" className={styles.cover} />}
|
||||
<div className={styles.details}>
|
||||
{labels.length > 0 && (
|
||||
<span className={styles.labels}>
|
||||
{labels.map((label) => (
|
||||
<span
|
||||
key={label.id}
|
||||
className={classNames(styles.attachment, styles.attachmentLeft)}
|
||||
>
|
||||
<Label name={label.name} color={label.color} size="tiny" />
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
<div className={styles.name}>{name}</div>
|
||||
{tasks.length > 0 && <Tasks items={tasks} />}
|
||||
{(description ||
|
||||
dueDate ||
|
||||
stopwatch ||
|
||||
attachmentsTotal > 0 ||
|
||||
notificationsTotal > 0) && (
|
||||
<span className={styles.attachments}>
|
||||
{notificationsTotal > 0 && (
|
||||
<span
|
||||
className={classNames(
|
||||
styles.attachment,
|
||||
styles.attachmentLeft,
|
||||
styles.notification,
|
||||
)}
|
||||
>
|
||||
{notificationsTotal}
|
||||
</span>
|
||||
)}
|
||||
{dueDate && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<DueDate value={dueDate} isCompleted={isDueDateCompleted} size="tiny" />
|
||||
</span>
|
||||
)}
|
||||
{stopwatch && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<Stopwatch
|
||||
as="span"
|
||||
startedAt={stopwatch.startedAt}
|
||||
total={stopwatch.total}
|
||||
size="tiny"
|
||||
onClick={canEdit ? handleToggleStopwatchClick : undefined}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{description && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<span className={styles.attachmentContent}>
|
||||
<Icon name="align left" />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
{attachmentsTotal > 0 && (
|
||||
<span className={classNames(styles.attachment, styles.attachmentLeft)}>
|
||||
<span className={styles.attachmentContent}>
|
||||
<Icon name="attach" />
|
||||
{attachmentsTotal}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{users.length > 0 && (
|
||||
<span className={classNames(styles.attachments, styles.attachmentsRight)}>
|
||||
{users.map((user) => (
|
||||
<span
|
||||
key={user.id}
|
||||
className={classNames(styles.attachment, styles.attachmentRight)}
|
||||
>
|
||||
<User name={user.name} avatarUrl={user.avatarUrl} size="small" />
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Draggable draggableId={`card:${id}`} index={index} isDragDisabled={!isPersisted || !canEdit}>
|
||||
{({ innerRef, draggableProps, dragHandleProps }) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<div {...draggableProps} {...dragHandleProps} ref={innerRef} className={styles.wrapper}>
|
||||
<NameEdit ref={nameEdit} defaultValue={name} onUpdate={handleNameUpdate}>
|
||||
<div className={styles.card}>
|
||||
{isPersisted ? (
|
||||
<>
|
||||
<Link
|
||||
to={Paths.CARDS.replace(':id', id)}
|
||||
className={styles.content}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{contentNode}
|
||||
</Link>
|
||||
{canEdit && (
|
||||
<ActionsPopup
|
||||
card={{
|
||||
dueDate,
|
||||
stopwatch,
|
||||
boardId,
|
||||
listId,
|
||||
projectId,
|
||||
}}
|
||||
projectsToLists={allProjectsToLists}
|
||||
boardMemberships={allBoardMemberships}
|
||||
currentUserIds={users.map((user) => user.id)}
|
||||
labels={allLabels}
|
||||
currentLabelIds={labels.map((label) => label.id)}
|
||||
onNameEdit={handleNameEdit}
|
||||
onUpdate={onUpdate}
|
||||
onMove={onMove}
|
||||
onTransfer={onTransfer}
|
||||
onDuplicate={onDuplicate}
|
||||
onDelete={onDelete}
|
||||
onUserAdd={onUserAdd}
|
||||
onUserRemove={onUserRemove}
|
||||
onBoardFetch={onBoardFetch}
|
||||
onLabelAdd={onLabelAdd}
|
||||
onLabelRemove={onLabelRemove}
|
||||
onLabelCreate={onLabelCreate}
|
||||
onLabelUpdate={onLabelUpdate}
|
||||
onLabelMove={onLabelMove}
|
||||
onLabelDelete={onLabelDelete}
|
||||
>
|
||||
<Button className={classNames(styles.actionsButton, styles.target)}>
|
||||
<Icon fitted name="pencil" size="small" />
|
||||
</Button>
|
||||
</ActionsPopup>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<span className={styles.content}>{contentNode}</span>
|
||||
)}
|
||||
</div>
|
||||
</NameEdit>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Card.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
index: PropTypes.number.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
dueDate: PropTypes.instanceOf(Date),
|
||||
isDueDateCompleted: PropTypes.bool,
|
||||
stopwatch: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
coverUrl: PropTypes.string,
|
||||
boardId: PropTypes.string.isRequired,
|
||||
listId: PropTypes.string.isRequired,
|
||||
projectId: PropTypes.string.isRequired,
|
||||
isPersisted: PropTypes.bool.isRequired,
|
||||
attachmentsTotal: PropTypes.number.isRequired,
|
||||
notificationsTotal: PropTypes.number.isRequired,
|
||||
/* eslint-disable react/forbid-prop-types */
|
||||
users: PropTypes.array.isRequired,
|
||||
labels: PropTypes.array.isRequired,
|
||||
tasks: PropTypes.array.isRequired,
|
||||
allProjectsToLists: PropTypes.array.isRequired,
|
||||
allBoardMemberships: PropTypes.array.isRequired,
|
||||
allLabels: PropTypes.array.isRequired,
|
||||
/* eslint-enable react/forbid-prop-types */
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onMove: PropTypes.func.isRequired,
|
||||
onTransfer: PropTypes.func.isRequired,
|
||||
onDuplicate: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onUserAdd: PropTypes.func.isRequired,
|
||||
onUserRemove: PropTypes.func.isRequired,
|
||||
onBoardFetch: PropTypes.func.isRequired,
|
||||
onLabelAdd: PropTypes.func.isRequired,
|
||||
onLabelRemove: PropTypes.func.isRequired,
|
||||
onLabelCreate: PropTypes.func.isRequired,
|
||||
onLabelUpdate: PropTypes.func.isRequired,
|
||||
onLabelMove: PropTypes.func.isRequired,
|
||||
onLabelDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Card.defaultProps = {
|
||||
description: undefined,
|
||||
dueDate: undefined,
|
||||
isDueDateCompleted: undefined,
|
||||
stopwatch: undefined,
|
||||
coverUrl: undefined,
|
||||
};
|
||||
|
||||
export default Card;
|
||||
@@ -1,127 +0,0 @@
|
||||
import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useClosableForm, useField } from '../../hooks';
|
||||
import { focusEnd } from '../../utils/element-helpers';
|
||||
|
||||
import styles from './NameEdit.module.scss';
|
||||
|
||||
const NameEdit = React.forwardRef(({ children, defaultValue, onUpdate }, ref) => {
|
||||
const [t] = useTranslation();
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const [value, handleFieldChange, setValue] = useField(defaultValue);
|
||||
|
||||
const field = useRef(null);
|
||||
|
||||
const open = useCallback(() => {
|
||||
setIsOpened(true);
|
||||
setValue(defaultValue);
|
||||
}, [defaultValue, setValue]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpened(false);
|
||||
setValue(null);
|
||||
}, [setValue]);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const cleanValue = value.trim();
|
||||
|
||||
if (!cleanValue) {
|
||||
field.current.ref.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cleanValue !== defaultValue) {
|
||||
onUpdate(cleanValue);
|
||||
}
|
||||
|
||||
close();
|
||||
}, [defaultValue, onUpdate, value, close]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
open,
|
||||
close,
|
||||
}),
|
||||
[open, close],
|
||||
);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
event.preventDefault();
|
||||
|
||||
submit();
|
||||
|
||||
break;
|
||||
case 'Escape':
|
||||
close();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
[close, submit],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(
|
||||
close,
|
||||
isOpened,
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened) {
|
||||
focusEnd(field.current.ref.current);
|
||||
}
|
||||
}, [isOpened]);
|
||||
|
||||
if (!isOpened) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div className={styles.fieldWrapper}>
|
||||
<TextArea
|
||||
ref={field}
|
||||
as={TextareaAutosize}
|
||||
value={value}
|
||||
minRows={3}
|
||||
maxRows={8}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.controls}>
|
||||
{/* eslint-disable-next-line jsx-a11y/mouse-events-have-key-events */}
|
||||
<Button
|
||||
positive
|
||||
content={t('action.save')}
|
||||
className={styles.submitButton}
|
||||
onMouseOver={handleControlMouseOver}
|
||||
onMouseOut={handleControlMouseOut}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
NameEdit.propTypes = {
|
||||
children: PropTypes.element.isRequired,
|
||||
defaultValue: PropTypes.string.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(NameEdit);
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { Progress } from 'semantic-ui-react';
|
||||
import { useToggle } from '../../lib/hooks';
|
||||
|
||||
import Linkify from '../Linkify';
|
||||
|
||||
import styles from './Tasks.module.scss';
|
||||
|
||||
const Tasks = React.memo(({ items }) => {
|
||||
const [isOpened, toggleOpened] = useToggle();
|
||||
|
||||
const handleToggleClick = useCallback(
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
toggleOpened();
|
||||
},
|
||||
[toggleOpened],
|
||||
);
|
||||
|
||||
const completedItems = items.filter((item) => item.isCompleted);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events,
|
||||
jsx-a11y/no-static-element-interactions */}
|
||||
<div className={styles.button} onClick={handleToggleClick}>
|
||||
<span className={styles.progressWrapper}>
|
||||
<Progress
|
||||
autoSuccess
|
||||
value={completedItems.length}
|
||||
total={items.length}
|
||||
color="blue"
|
||||
size="tiny"
|
||||
className={styles.progress}
|
||||
/>
|
||||
</span>
|
||||
<span
|
||||
className={classNames(styles.count, isOpened ? styles.countOpened : styles.countClosed)}
|
||||
>
|
||||
{completedItems.length}/{items.length}
|
||||
</span>
|
||||
</div>
|
||||
{isOpened && (
|
||||
<ul className={styles.tasks}>
|
||||
{items.map((item) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className={classNames(styles.task, item.isCompleted && styles.taskCompleted)}
|
||||
>
|
||||
<Linkify linkStopPropagation>{item.name}</Linkify>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
Tasks.propTypes = {
|
||||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
export default Tasks;
|
||||
@@ -1,3 +0,0 @@
|
||||
import Card from './Card';
|
||||
|
||||
export default Card;
|
||||
@@ -1,112 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Comment, Icon, Loader, Visibility } from 'semantic-ui-react';
|
||||
|
||||
import { ActivityTypes } from '../../../constants/Enums';
|
||||
import CommentAdd from './CommentAdd';
|
||||
import Item from './Item';
|
||||
|
||||
import styles from './Activities.module.scss';
|
||||
|
||||
const Activities = React.memo(
|
||||
({
|
||||
items,
|
||||
isFetching,
|
||||
isAllFetched,
|
||||
isDetailsVisible,
|
||||
isDetailsFetching,
|
||||
canEdit,
|
||||
canEditAllComments,
|
||||
onFetch,
|
||||
onDetailsToggle,
|
||||
onCommentCreate,
|
||||
onCommentUpdate,
|
||||
onCommentDelete,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const handleToggleDetailsClick = useCallback(() => {
|
||||
onDetailsToggle(!isDetailsVisible);
|
||||
}, [isDetailsVisible, onDetailsToggle]);
|
||||
|
||||
const handleCommentUpdate = useCallback(
|
||||
(id, data) => {
|
||||
onCommentUpdate(id, data);
|
||||
},
|
||||
[onCommentUpdate],
|
||||
);
|
||||
|
||||
const handleCommentDelete = useCallback(
|
||||
(id) => {
|
||||
onCommentDelete(id);
|
||||
},
|
||||
[onCommentDelete],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.contentModule}>
|
||||
<div className={styles.moduleWrapper}>
|
||||
<Icon name="list ul" className={styles.moduleIcon} />
|
||||
<div className={styles.moduleHeader}>
|
||||
{t('common.actions')}
|
||||
<Button
|
||||
content={isDetailsVisible ? t('action.hideDetails') : t('action.showDetails')}
|
||||
className={styles.toggleButton}
|
||||
onClick={handleToggleDetailsClick}
|
||||
/>
|
||||
</div>
|
||||
{canEdit && <CommentAdd onCreate={onCommentCreate} />}
|
||||
<div className={styles.wrapper}>
|
||||
<Comment.Group>
|
||||
{items.map((item) =>
|
||||
item.type === ActivityTypes.COMMENT_CARD ? (
|
||||
<Item.Comment
|
||||
key={item.id}
|
||||
data={item.data}
|
||||
createdAt={item.createdAt}
|
||||
isPersisted={item.isPersisted}
|
||||
user={item.user}
|
||||
canEdit={(item.user.isCurrent && canEdit) || canEditAllComments}
|
||||
onUpdate={(data) => handleCommentUpdate(item.id, data)}
|
||||
onDelete={() => handleCommentDelete(item.id)}
|
||||
/>
|
||||
) : (
|
||||
<Item
|
||||
key={item.id}
|
||||
type={item.type}
|
||||
data={item.data}
|
||||
createdAt={item.createdAt}
|
||||
user={item.user}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
</Comment.Group>
|
||||
</div>
|
||||
{isFetching || isDetailsFetching ? (
|
||||
<Loader active inverted inline="centered" size="small" className={styles.loader} />
|
||||
) : (
|
||||
!isAllFetched && <Visibility fireOnMount onOnScreen={onFetch} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Activities.propTypes = {
|
||||
items: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isAllFetched: PropTypes.bool.isRequired,
|
||||
isDetailsVisible: PropTypes.bool.isRequired,
|
||||
isDetailsFetching: PropTypes.bool.isRequired,
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
canEditAllComments: PropTypes.bool.isRequired,
|
||||
onFetch: PropTypes.func.isRequired,
|
||||
onDetailsToggle: PropTypes.func.isRequired,
|
||||
onCommentCreate: PropTypes.func.isRequired,
|
||||
onCommentUpdate: PropTypes.func.isRequired,
|
||||
onCommentDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Activities;
|
||||
@@ -1,102 +0,0 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
import { useDidUpdate, useToggle } from '../../../lib/hooks';
|
||||
|
||||
import { useClosableForm, useForm } from '../../../hooks';
|
||||
|
||||
import styles from './CommentAdd.module.scss';
|
||||
|
||||
const DEFAULT_DATA = {
|
||||
text: '',
|
||||
};
|
||||
|
||||
const CommentAdd = React.memo(({ onCreate }) => {
|
||||
const [t] = useTranslation();
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const [data, handleFieldChange, setData] = useForm(DEFAULT_DATA);
|
||||
const [selectTextFieldState, selectTextField] = useToggle();
|
||||
|
||||
const textField = useRef(null);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpened(false);
|
||||
}, []);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
text: data.text.trim(),
|
||||
};
|
||||
|
||||
if (!cleanData.text) {
|
||||
textField.current.ref.current.select();
|
||||
return;
|
||||
}
|
||||
|
||||
onCreate(cleanData);
|
||||
|
||||
setData(DEFAULT_DATA);
|
||||
selectTextField();
|
||||
}, [onCreate, data, setData, selectTextField]);
|
||||
|
||||
const handleFieldFocus = useCallback(() => {
|
||||
setIsOpened(true);
|
||||
}, []);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
submit();
|
||||
}
|
||||
},
|
||||
[submit],
|
||||
);
|
||||
|
||||
const [handleFieldBlur, handleControlMouseOver, handleControlMouseOut] = useClosableForm(close);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
useDidUpdate(() => {
|
||||
textField.current.ref.current.focus();
|
||||
}, [selectTextFieldState]);
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<TextArea
|
||||
ref={textField}
|
||||
as={TextareaAutosize}
|
||||
name="text"
|
||||
value={data.text}
|
||||
placeholder={t('common.writeComment')}
|
||||
minRows={isOpened ? 3 : 1}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onFocus={handleFieldFocus}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
{isOpened && (
|
||||
<div className={styles.controls}>
|
||||
<Button
|
||||
positive
|
||||
content={t('action.addComment')}
|
||||
onMouseOver={handleControlMouseOver}
|
||||
onMouseOut={handleControlMouseOut}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
CommentAdd.propTypes = {
|
||||
onCreate: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default CommentAdd;
|
||||
@@ -1,115 +0,0 @@
|
||||
import { dequal } from 'dequal';
|
||||
import React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TextareaAutosize from 'react-textarea-autosize';
|
||||
import { Button, Form, TextArea } from 'semantic-ui-react';
|
||||
|
||||
import { useForm } from '../../../hooks';
|
||||
import { focusEnd } from '../../../utils/element-helpers';
|
||||
|
||||
import styles from './CommentEdit.module.scss';
|
||||
|
||||
const CommentEdit = React.forwardRef(({ defaultData, onUpdate, text, actions }, ref) => {
|
||||
const [t] = useTranslation();
|
||||
const [isOpened, setIsOpened] = useState(false);
|
||||
const [data, handleFieldChange, setData] = useForm(null);
|
||||
|
||||
const textField = useRef(null);
|
||||
|
||||
const open = useCallback(() => {
|
||||
setIsOpened(true);
|
||||
setData({
|
||||
text: '',
|
||||
...defaultData,
|
||||
});
|
||||
}, [defaultData, setData]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
setIsOpened(false);
|
||||
setData(null);
|
||||
}, [setData]);
|
||||
|
||||
const submit = useCallback(() => {
|
||||
const cleanData = {
|
||||
...data,
|
||||
text: data.text.trim(),
|
||||
};
|
||||
|
||||
if (cleanData.text && !dequal(cleanData, defaultData)) {
|
||||
onUpdate(cleanData);
|
||||
}
|
||||
|
||||
close();
|
||||
}, [defaultData, onUpdate, data, close]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
open,
|
||||
close,
|
||||
}),
|
||||
[open, close],
|
||||
);
|
||||
|
||||
const handleFieldKeyDown = useCallback(
|
||||
(event) => {
|
||||
if (event.ctrlKey && event.key === 'Enter') {
|
||||
submit();
|
||||
}
|
||||
},
|
||||
[submit],
|
||||
);
|
||||
|
||||
const handleFieldBlur = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
submit();
|
||||
}, [submit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpened) {
|
||||
focusEnd(textField.current.ref.current);
|
||||
}
|
||||
}, [isOpened]);
|
||||
|
||||
if (!isOpened) {
|
||||
return (
|
||||
<>
|
||||
{actions}
|
||||
{text}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<TextArea
|
||||
ref={textField}
|
||||
as={TextareaAutosize}
|
||||
name="text"
|
||||
value={data.text}
|
||||
minRows={3}
|
||||
spellCheck={false}
|
||||
className={styles.field}
|
||||
onKeyDown={handleFieldKeyDown}
|
||||
onChange={handleFieldChange}
|
||||
onBlur={handleFieldBlur}
|
||||
/>
|
||||
<div className={styles.controls}>
|
||||
<Button positive content={t('action.save')} />
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
});
|
||||
|
||||
CommentEdit.propTypes = {
|
||||
defaultData: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
text: PropTypes.element.isRequired,
|
||||
actions: PropTypes.element.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(CommentEdit);
|
||||
@@ -1,89 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { Comment } from 'semantic-ui-react';
|
||||
|
||||
import getDateFormat from '../../../utils/get-date-format';
|
||||
import { ActivityTypes } from '../../../constants/Enums';
|
||||
import ItemComment from './ItemComment';
|
||||
import User from '../../User';
|
||||
|
||||
import styles from './Item.module.scss';
|
||||
|
||||
const Item = React.memo(({ type, data, createdAt, user }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
let contentNode;
|
||||
switch (type) {
|
||||
case ActivityTypes.CREATE_CARD:
|
||||
contentNode = (
|
||||
<Trans
|
||||
i18nKey="common.userAddedThisCardToList"
|
||||
values={{
|
||||
user: user.name,
|
||||
list: data.list.name,
|
||||
}}
|
||||
>
|
||||
<span className={styles.author}>{user.name}</span>
|
||||
<span className={styles.text}>
|
||||
{' added this card to '}
|
||||
{data.list.name}
|
||||
</span>
|
||||
</Trans>
|
||||
);
|
||||
|
||||
break;
|
||||
case ActivityTypes.MOVE_CARD:
|
||||
contentNode = (
|
||||
<Trans
|
||||
i18nKey="common.userMovedThisCardFromListToList"
|
||||
values={{
|
||||
user: user.name,
|
||||
fromList: data.fromList.name,
|
||||
toList: data.toList.name,
|
||||
}}
|
||||
>
|
||||
<span className={styles.author}>{user.name}</span>
|
||||
<span className={styles.text}>
|
||||
{' moved this card from '}
|
||||
{data.fromList.name}
|
||||
{' to '}
|
||||
{data.toList.name}
|
||||
</span>
|
||||
</Trans>
|
||||
);
|
||||
|
||||
break;
|
||||
default:
|
||||
contentNode = null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Comment>
|
||||
<span className={styles.user}>
|
||||
<User name={user.name} avatarUrl={user.avatarUrl} />
|
||||
</span>
|
||||
<div className={classNames(styles.content)}>
|
||||
<div>{contentNode}</div>
|
||||
<span className={styles.date}>
|
||||
{t(`format:${getDateFormat(createdAt)}`, {
|
||||
postProcess: 'formatDate',
|
||||
value: createdAt,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</Comment>
|
||||
);
|
||||
});
|
||||
|
||||
Item.Comment = ItemComment;
|
||||
|
||||
Item.propTypes = {
|
||||
type: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
createdAt: PropTypes.instanceOf(Date).isRequired,
|
||||
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
export default Item;
|
||||
@@ -1,95 +0,0 @@
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Comment } from 'semantic-ui-react';
|
||||
import { usePopup } from '../../../lib/popup';
|
||||
import { Markdown } from '../../../lib/custom-ui';
|
||||
|
||||
import getDateFormat from '../../../utils/get-date-format';
|
||||
import CommentEdit from './CommentEdit';
|
||||
import User from '../../User';
|
||||
import DeleteStep from '../../DeleteStep';
|
||||
|
||||
import styles from './ItemComment.module.scss';
|
||||
|
||||
const ItemComment = React.memo(
|
||||
({ data, createdAt, isPersisted, user, canEdit, onUpdate, onDelete }) => {
|
||||
const [t] = useTranslation();
|
||||
|
||||
const commentEdit = useRef(null);
|
||||
|
||||
const handleEditClick = useCallback(() => {
|
||||
commentEdit.current.open();
|
||||
}, []);
|
||||
|
||||
const DeletePopup = usePopup(DeleteStep);
|
||||
|
||||
return (
|
||||
<Comment>
|
||||
<span className={styles.user}>
|
||||
<User name={user.name} avatarUrl={user.avatarUrl} />
|
||||
</span>
|
||||
<div className={classNames(styles.content)}>
|
||||
<CommentEdit
|
||||
ref={commentEdit}
|
||||
defaultData={data}
|
||||
onUpdate={onUpdate}
|
||||
text={
|
||||
<div className={styles.text}>
|
||||
<Markdown linkTarget="_blank">{data.text}</Markdown>
|
||||
</div>
|
||||
}
|
||||
actions={
|
||||
<div className={styles.title}>
|
||||
<span>
|
||||
<span className={styles.author}>{user.name}</span>
|
||||
<span className={styles.date}>
|
||||
{t(`format:${getDateFormat(createdAt)}`, {
|
||||
postProcess: 'formatDate',
|
||||
value: createdAt,
|
||||
})}
|
||||
</span>
|
||||
</span>
|
||||
{canEdit && (
|
||||
<Comment.Actions>
|
||||
<Comment.Action
|
||||
as="button"
|
||||
content={t('action.edit')}
|
||||
disabled={!isPersisted}
|
||||
onClick={handleEditClick}
|
||||
/>
|
||||
<DeletePopup
|
||||
title="common.deleteComment"
|
||||
content="common.areYouSureYouWantToDeleteThisComment"
|
||||
buttonContent="action.deleteComment"
|
||||
onConfirm={onDelete}
|
||||
>
|
||||
<Comment.Action
|
||||
as="button"
|
||||
content={t('action.delete')}
|
||||
disabled={!isPersisted}
|
||||
/>
|
||||
</DeletePopup>
|
||||
</Comment.Actions>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Comment>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ItemComment.propTypes = {
|
||||
data: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
createdAt: PropTypes.instanceOf(Date).isRequired,
|
||||
isPersisted: PropTypes.bool.isRequired,
|
||||
user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
canEdit: PropTypes.bool.isRequired,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ItemComment;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user