diff --git a/src/client/components/Dashboard/my-tasks/MyTasksTable.jsx b/src/client/components/Dashboard/my-tasks/MyTasksTable.jsx index 83ae73e8c0e..d425cb6aa56 100644 --- a/src/client/components/Dashboard/my-tasks/MyTasksTable.jsx +++ b/src/client/components/Dashboard/my-tasks/MyTasksTable.jsx @@ -6,6 +6,7 @@ import styled from 'styled-components' import { formatMediumDate } from '../../../utils/date' import urls from '../../../../lib/urls' +import { STATUS } from '../../../modules/Tasks/TaskForm/constants' export const transformAdvisersListItem = (advisers) => { return advisers.map((adviser, index) => ( @@ -46,7 +47,11 @@ const rows = ({ results }) => { - {task.archived ? 'Completed' : 'Active'} + {task.archived + ? 'Deleted' + : task.status == STATUS.COMPLETED + ? 'Completed' + : 'Active'} )) diff --git a/src/client/components/Dashboard/my-tasks/state.js b/src/client/components/Dashboard/my-tasks/state.js index ca8fc912ba5..2eeb537e32d 100644 --- a/src/client/components/Dashboard/my-tasks/state.js +++ b/src/client/components/Dashboard/my-tasks/state.js @@ -9,6 +9,7 @@ import { ME_OTHERS_LIST_OPTIONS, SHOW_ALL_OPTION, } from './constants' +import { STATUS } from '../../../modules/Tasks/TaskForm/constants' export const ID = 'getMyTasks' export const TASK_GET_MY_TASKS = 'TASK_GET_MY_TASKS' @@ -30,9 +31,10 @@ const sortbyMapping = { company_ascending: 'company.name:asc', project_ascending: 'investment_project.name:asc', } + const statusMapping = { - active: { archived: false }, - completed: { archived: true }, + active: { status: STATUS.ACTIVE }, + completed: { status: STATUS.COMPLETED }, } export const state2props = ({ router, ...state }) => { @@ -50,6 +52,7 @@ export const state2props = ({ router, ...state }) => { sortby: 'due_date:asc', company: undefined, project: undefined, + status: undefined, } const assignedToMapping = { diff --git a/src/client/components/Dashboard/my-tasks/tasks.js b/src/client/components/Dashboard/my-tasks/tasks.js index 934964dab15..3f2008948b4 100644 --- a/src/client/components/Dashboard/my-tasks/tasks.js +++ b/src/client/components/Dashboard/my-tasks/tasks.js @@ -6,10 +6,11 @@ export const getMyTasks = ({ not_created_by, advisers, not_advisers, - archived, + archived = false, sortby = 'due_date:asc', company, project, + status, }) => apiProxyAxios .post('/v4/search/task', { @@ -24,5 +25,6 @@ export const getMyTasks = ({ sortby, company, investment_project: project, + status, }) .then(({ data }) => data) diff --git a/src/client/modules/Tasks/TaskDetails/TaskButtons.jsx b/src/client/modules/Tasks/TaskDetails/TaskButtons.jsx index 7a8297bc47e..3874c6f993c 100644 --- a/src/client/modules/Tasks/TaskDetails/TaskButtons.jsx +++ b/src/client/modules/Tasks/TaskDetails/TaskButtons.jsx @@ -8,8 +8,13 @@ import styled from 'styled-components' import { Form } from '../../../components' import urls from '../../../../lib/urls' -import { TASK_ARCHIVE_TASK, buttonState2props } from './state' -import { GREY_3, TEXT_COLOUR } from '../../../utils/colours' +import { + TASK_DELETE, + TASK_SAVE_STATUS_COMPLETE, + buttonState2props, +} from './state' +import { GREY_3, RED, TEXT_COLOUR } from '../../../utils/colours' +import { STATUS } from '../TaskForm/constants' const ButtonWrapper = styled.div` min-height: 71px; @@ -21,28 +26,33 @@ const ButtonWrapper = styled.div` } ` +const DeleteTaskWrapper = styled.div` + * { + margin-left: ${SPACING.SCALE_1}; + } +` + export const TaskButtons = ({ task, returnUrl }) => ( <> - {!task.archived && ( + {!task.archived && task.status == STATUS.ACTIVE && (
urls.dashboard.myTasks()} cancelRedirectTo={false} - submissionTaskName={TASK_ARCHIVE_TASK} + submissionTaskName={TASK_SAVE_STATUS_COMPLETE} transformPayload={() => ({ taskId: task.id, })} flashMessage={() => 'Task marked as complete'} submitButtonLabel="Mark as complete" initialValues={task} - > - {() => <>} -
+ /> )} - {!task.archived && ( + {!task.archived && task.status == STATUS.ACTIVE && ( + + {!task.archived && ( + +
urls.dashboard.myTasks()} + cancelRedirectTo={false} + submissionTaskName={TASK_DELETE} + transformPayload={() => ({ + taskId: task.id, + })} + submitButtonColour={RED} + flashMessage={() => 'Task deleted'} + submitButtonLabel="Delete task" + initialValues={task} + /> + + )} + { const { taskId } = useParams() - const taskTitle = task ? task.title : '' - const archivedTag = task?.archived && ( + const statusTag = task?.status == STATUS.COMPLETED && ( COMPLETED ) + const archivedTag = task?.archived && ( + + DELETED + + ) + const subheading = + archivedTag || statusTag ? ( + <> + {archivedTag} {statusTag} + + ) : ( + '' + ) return ( { diff --git a/src/client/modules/Tasks/TaskDetails/tasks.js b/src/client/modules/Tasks/TaskDetails/tasks.js index bdc42df5337..cec59dddd00 100644 --- a/src/client/modules/Tasks/TaskDetails/tasks.js +++ b/src/client/modules/Tasks/TaskDetails/tasks.js @@ -3,5 +3,11 @@ import { apiProxyAxios } from '../../../components/Task/utils' export const getTaskDetail = (id) => apiProxyAxios.get(`v4/task/${id}`).then(({ data }) => data) -export const archiveTask = ({ taskId }) => - apiProxyAxios.post(`/v4/task/${taskId}/archive`, { reason: 'completed' }) +export const saveTaskStatusComplete = ({ taskId }) => + apiProxyAxios.post(`/v4/task/${taskId}/status-complete`) + +export const saveTaskStatusActive = ({ taskId }) => + apiProxyAxios.post(`/v4/task/${taskId}/status-active`) + +export const deleteTask = ({ taskId }) => + apiProxyAxios.post(`/v4/task/${taskId}/archive`, { reason: 'deleted' }) diff --git a/src/client/modules/Tasks/TaskForm/constants.js b/src/client/modules/Tasks/TaskForm/constants.js index 7d29c5543d9..353540de4ad 100644 --- a/src/client/modules/Tasks/TaskForm/constants.js +++ b/src/client/modules/Tasks/TaskForm/constants.js @@ -9,3 +9,10 @@ export const OPTIONS = { { label: 'Someone else', value: OPTION_SOMEONE_ELSE }, ], } + +const STATUS_ACTIVE = 'active' +const STATUS_COMPLETED = 'complete' +export const STATUS = { + ACTIVE: STATUS_ACTIVE, + COMPLETED: STATUS_COMPLETED, +} diff --git a/src/client/tasks.js b/src/client/tasks.js index b0fa0fae350..0e8d2db07b6 100644 --- a/src/client/tasks.js +++ b/src/client/tasks.js @@ -418,10 +418,17 @@ import { getOmisCompanies } from './modules/Omis/CreateOrder/CompanySelect/tasks import { TASK_CREATE_ORDER } from './modules/Omis/CreateOrder/state' import { createOrder } from './modules/Omis/CreateOrder/tasks' -import { archiveTask, getTaskDetail } from './modules/Tasks/TaskDetails/tasks' import { - TASK_ARCHIVE_TASK, + saveTaskStatusActive, + saveTaskStatusComplete, + getTaskDetail, + deleteTask, +} from './modules/Tasks/TaskDetails/tasks' +import { + TASK_SAVE_STATUS_ACTIVE, + TASK_SAVE_STATUS_COMPLETE, TASK_GET_TASK_DETAILS, + TASK_DELETE, } from './modules/Tasks/TaskDetails/state' import { saveTaskDetail } from './modules/Tasks/TaskForm/tasks' import { TASK_SAVE_TASK_DETAILS } from './modules/Tasks/TaskForm/state' @@ -647,7 +654,9 @@ export const tasks = { [TASK_EDIT_OMIS_INTERNAL_INFORMATION]: updateOrder, [TASK_GET_TASK_DETAILS]: getTaskDetail, [TASK_GET_INVESTMENT_PROJECT]: investmentProjectTasks.getInvestmentProject, - [TASK_ARCHIVE_TASK]: archiveTask, + [TASK_SAVE_STATUS_COMPLETE]: saveTaskStatusComplete, + [TASK_SAVE_STATUS_ACTIVE]: saveTaskStatusActive, + [TASK_DELETE]: deleteTask, [TASK_RECONCILE_OMIS_PAYMENT]: savePayment, [TASK_SAVE_TASK_DETAILS]: saveTaskDetail, [TASK_EDIT_INVOICE_DETAILS]: updateOrder, diff --git a/src/lib/urls.js b/src/lib/urls.js index aee9c57c254..96a2104fe5a 100644 --- a/src/lib/urls.js +++ b/src/lib/urls.js @@ -730,5 +730,9 @@ module.exports = { createInteraction: url('/tasks/create?interactionId=', ':interactionId'), createCopyTask: url('/tasks/create?copyTaskId=', ':copyTaskId'), edit: url('/tasks', '/:taskId/edit'), + statusComplete: url('/tasks', '/:taskId/status-complete'), + statusActive: url('/tasks', '/:taskId/status-active'), + archive: url('/tasks', '/:taskId/archive'), + unarchive: url('/tasks', '/:taskId/unarchive'), }, } diff --git a/test/a11y/cypress/config/urlTestExclusions.js b/test/a11y/cypress/config/urlTestExclusions.js index 325877de390..6e7f370df02 100644 --- a/test/a11y/cypress/config/urlTestExclusions.js +++ b/test/a11y/cypress/config/urlTestExclusions.js @@ -131,6 +131,11 @@ export const urlTestExclusions = [ { url: '/investments/projects/:investmentId/propositions/:propositionId' }, { url: '/companies/:companyId/hierarchies/ghq/:globalHqId/add' }, { url: '/companies/:companyId/hierarchies/ghq/remove' }, + // API calls with redirect + { url: '/tasks/:taskId/status-complete' }, + { url: '/tasks/:taskId/status-active' }, + { url: '/tasks/:taskId/archive' }, + { url: '/tasks/:taskId/unarchive' }, // 501 errors { url: '/api-proxy/v4/company/:companyId/export-win' }, { url: '/investments/projects/:projectId/edit-ukcompany/:companyId' }, diff --git a/test/component/cypress/specs/Dashboard/MyTasks/MyTasksTable.cy.jsx b/test/component/cypress/specs/Dashboard/MyTasks/MyTasksTable.cy.jsx index 0cbe2ee1723..da1ffee57b6 100644 --- a/test/component/cypress/specs/Dashboard/MyTasks/MyTasksTable.cy.jsx +++ b/test/component/cypress/specs/Dashboard/MyTasks/MyTasksTable.cy.jsx @@ -13,6 +13,7 @@ import urls from '../../../../../../src/lib/urls' import { keysToSnakeCase } from '../../../../../functional/cypress/fakers/utils' import Provider from '../../provider' +import { STATUS } from '../../../../../../src/client/modules/Tasks/TaskForm/constants' describe('My Tasks on the Dashboard', () => { const Component = (props) => ( @@ -24,7 +25,7 @@ describe('My Tasks on the Dashboard', () => { ) // Create 3 tasks of which one is Archived const myTasksList = taskWithInvestmentProjectListFaker() - myTasksList[1].archived = true + myTasksList[1].status = STATUS.COMPLETED const myTaskResults = myTasksList.map((task) => keysToSnakeCase(task)) const myTasks = { diff --git a/test/component/cypress/specs/Tasks/TaskDetails/TaskButtons.cy.jsx b/test/component/cypress/specs/Tasks/TaskDetails/TaskButtons.cy.jsx index 54081835a3d..c102ae07c2c 100644 --- a/test/component/cypress/specs/Tasks/TaskDetails/TaskButtons.cy.jsx +++ b/test/component/cypress/specs/Tasks/TaskDetails/TaskButtons.cy.jsx @@ -44,18 +44,75 @@ describe('Task buttons', () => { }) context('When a task is completed', () => { - const task = taskWithInvestmentProjectFaker({ archived: true }) + const task = taskWithInvestmentProjectFaker({ status: 'complete' }) beforeEach(() => { cy.mount() }) it('should not show the Mark as complete button', () => { + cy.get('[data-test="submit-button"]').should( + 'not.contain.text', + 'Mark as complete' + ) + }) + + it('should not show the Edit link', () => { + cy.get('[data-test="edit-form-button"]').should('not.exist') + }) + + it('should show the Create similar task link', () => { + cy.get('[data-test="create-similar-task-button"]').should('exist') + }) + + it('should show the Back link to dashboard when no return url exists', () => { + assertLink('task-back-link', urls.dashboard.myTasks()) + }) + }) + + context('When a task is not archived/deleted', () => { + const task = taskWithInvestmentProjectFaker() + + beforeEach(() => { + cy.mount() + }) + + it('should show the Delete task button', () => { + cy.get('[data-test="submit-button"]').should( + 'contain.text', + 'Delete task' + ) + }) + + it('should show the Edit link with expected url', () => { + assertLink('edit-form-button', urls.tasks.edit(task.id)) + }) + + it('should show the Create similar task button with expected url', () => { + assertLink( + 'create-similar-task-button', + urls.tasks.createCopyTask(task.id) + ) + }) + + it('should show the Back link to dashboard when no return url exists', () => { + assertLink('task-back-link', urls.dashboard.myTasks()) + }) + }) + + context('When a task is archived/deleted', () => { + const task = taskWithInvestmentProjectFaker({ archived: true }) + + beforeEach(() => { + cy.mount() + }) + + it('should not show the Delete task nor Mark as complete buttons', () => { cy.get('[data-test="edit-form-button"]').should('not.exist') }) it('should not show the Edit link', () => { - cy.get('[data-test="submit-button"]').should('not.exist') + cy.get('[data-test="edit-form-button"]').should('not.exist') }) it('should show the Create similar task link', () => { diff --git a/test/functional/cypress/fakers/task.js b/test/functional/cypress/fakers/task.js index c7e5ad75c99..a006931708e 100644 --- a/test/functional/cypress/fakers/task.js +++ b/test/functional/cypress/fakers/task.js @@ -4,6 +4,7 @@ import { pick } from 'lodash' import { listFaker } from './utils' import { companyFaker } from './companies' import { interactionFaker } from './interactions' +import { STATUS } from '../../../../src/client/modules/Tasks/TaskForm/constants' const basicAdviserFaker = (overrides = {}) => ({ name: faker.person.fullName(), @@ -29,6 +30,7 @@ const taskFaker = (overrides = {}) => ({ modifiedBy: basicAdviserFaker(), modifiedOn: faker.date.past().toISOString(), createdOn: faker.date.past().toISOString(), + status: STATUS.ACTIVE, ...overrides, }) diff --git a/test/functional/cypress/specs/dashboard/filter-spec.js b/test/functional/cypress/specs/dashboard/filter-spec.js index b4bb74d1ac7..5e3c203fba0 100644 --- a/test/functional/cypress/specs/dashboard/filter-spec.js +++ b/test/functional/cypress/specs/dashboard/filter-spec.js @@ -23,6 +23,7 @@ describe('Task filters', () => { offset: 0, adviser: [myAdviserId], sortby: 'due_date:asc', + archived: false, } //Remap investment_project property to investmentProject so it displays in the Task List UI properly TaskList[0].investment_project = TaskList[0].investmentProject @@ -234,6 +235,7 @@ describe('Task filters', () => { offset: 0, adviser: [myAdviserId], sortby: sortBy, + archived: false, }) assertListItems({ length: 1 }) }) @@ -300,24 +302,29 @@ describe('Task filters', () => { }) it('should filter active status from the url', () => { - testFilterFromUrl(element, 'status=active', { archived: false }, 'Active') + testFilterFromUrl( + element, + 'status=active', + { status: 'active' }, + 'Active' + ) }) it('should filter completed status from the url', () => { testFilterFromUrl( element, 'status=completed', - { archived: true }, + { status: 'complete' }, 'Completed' ) }) it('should filter active status from user input', () => { - testFilterFromUserInput(element, { archived: false }, 'Active') + testFilterFromUserInput(element, { status: 'active' }, 'Active') }) it('should filter completed status from user input', () => { - testFilterFromUserInput(element, { archived: true }, 'Completed') + testFilterFromUserInput(element, { status: 'complete' }, 'Completed') }) }) diff --git a/test/functional/cypress/specs/tasks/details-spec.js b/test/functional/cypress/specs/tasks/details-spec.js index 654ed75d0bf..a718214d6b9 100644 --- a/test/functional/cypress/specs/tasks/details-spec.js +++ b/test/functional/cypress/specs/tasks/details-spec.js @@ -12,10 +12,12 @@ import { } from '../../support/assertions' import { clickButton } from '../../support/actions' import interactionsListFaker from '../../fakers/interactions' +import { STATUS } from '../../../../../src/client/modules/Tasks/TaskForm/constants' describe('View details for a generic task', () => { - const genericTaskCompleted = taskFaker({ archived: true }) - const genericTaskNotCompleteTask = taskFaker({ archived: false }) + const genericTaskCompleted = taskFaker({ status: STATUS.COMPLETED }) + const genericTaskNotCompleteTask = taskFaker({ status: STATUS.ACTIVE }) + const generticTaskArchived = taskFaker({ archived: true }) context('When visiting a completed task details', () => { beforeEach(() => { @@ -43,6 +45,49 @@ describe('View details for a generic task', () => { ) cy.get('[data-test="activity-kind-label"]').should('contain', 'COMPLETED') + cy.get('[data-test="activity-kind-label"]').should( + 'not.contain', + 'DELETED' + ) + }) + + it('should display the summary table', () => { + assertSummaryTable({ + dataTest: 'task-details-table', + }) + }) + }) + + context('When visiting an archived task details', () => { + beforeEach(() => { + cy.intercept( + 'GET', + `/api-proxy/v4/task/${generticTaskArchived.id}`, + generticTaskArchived + ).as('apiRequest') + cy.visit(tasks.details(generticTaskArchived.id)) + cy.wait('@apiRequest') + }) + + it('should display the task title in the breadcrumbs', () => { + assertBreadcrumbs({ + Home: dashboard.myTasks(), + [generticTaskArchived.title]: tasks.details(generticTaskArchived.id), + Task: null, + }) + }) + + it('should display the title of the task and deleted tag', () => { + cy.get('[data-test="heading"]').should( + 'contain', + generticTaskArchived.title + ) + + cy.get('[data-test="activity-kind-label"]').should('contain', 'DELETED') + cy.get('[data-test="activity-kind-label"]').should( + 'not.contain', + 'COMPLETED' + ) }) it('should display the summary table', () => { @@ -52,7 +97,7 @@ describe('View details for a generic task', () => { }) }) - context('When visiting a not complete task details', () => { + context('When visiting a not complete not archived task details', () => { beforeEach(() => { cy.intercept( 'GET', @@ -72,14 +117,25 @@ describe('View details for a generic task', () => { cy.get('[data-test="activity-kind-label"]').should('not.exist') }) - it('should redirect to the dashboard and show the Flash message after marking as complete', () => { + it('should redirect to the dashboard and show the Flash message after deleting', () => { cy.intercept( 'POST', `/api-proxy/v4/task/${genericTaskNotCompleteTask.id}/archive`, {} ).as('postTaskArchiveApiRequest') + clickButton('Delete task') + assertPayload('@postTaskArchiveApiRequest', { reason: 'deleted' }) + assertUrl(dashboard.myTasks()) + }) + + it('should redirect to the dashboard and show the Flash message after marking as complete', () => { + cy.intercept( + 'POST', + `/api-proxy/v4/task/${genericTaskNotCompleteTask.id}/status-complete`, + {} + ).as('postTaskStatusCompleteApiRequest') clickButton('Mark as complete') - assertPayload('@postTaskArchiveApiRequest', { reason: 'completed' }) + cy.wait('@postTaskStatusCompleteApiRequest') assertUrl(dashboard.myTasks()) }) }) @@ -90,8 +146,8 @@ describe('View details for task that is assigned to an investment project', () = archived: false, }) - context('When visiting a not complete task details', () => { - before(() => { + context('When visiting a not complete not archived task details', () => { + beforeEach(() => { cy.intercept( 'GET', `/api-proxy/v4/task/${investmentProjectTask.id}`, @@ -102,13 +158,24 @@ describe('View details for task that is assigned to an investment project', () = }) it('should redirect to the investment project and show the Flash message after marking as complete', () => { + cy.intercept( + 'POST', + `/api-proxy/v4/task/${investmentProjectTask.id}/status-complete`, + {} + ).as('postTaskStatusCompleteApiRequest') + clickButton('Mark as complete') + cy.wait('@postTaskStatusCompleteApiRequest') + assertUrl(dashboard.myTasks()) + }) + + it('should redirect to the investment project and show the Flash message after deleting', () => { cy.intercept( 'POST', `/api-proxy/v4/task/${investmentProjectTask.id}/archive`, {} ).as('postTaskArchiveApiRequest') - clickButton('Mark as complete') - assertPayload('@postTaskArchiveApiRequest', { reason: 'completed' }) + clickButton('Delete task') + assertPayload('@postTaskArchiveApiRequest', { reason: 'deleted' }) assertUrl(dashboard.myTasks()) }) }) @@ -117,8 +184,8 @@ describe('View details for task that is assigned to an investment project', () = describe('View details for task that is assigned to a company', () => { const companyTask = taskWithCompanyFaker({ archived: false }) - context('When visiting a not complete task details', () => { - before(() => { + context('When visiting a not complete not archived task details', () => { + beforeEach(() => { cy.intercept( 'GET', `/api-proxy/v4/task/${companyTask.id}`, @@ -129,13 +196,24 @@ describe('View details for task that is assigned to a company', () => { }) it('should redirect to the investment project and show the Flash message after marking as complete', () => { + cy.intercept( + 'POST', + `/api-proxy/v4/task/${companyTask.id}/status-complete`, + {} + ).as('postTaskStatusCompleteApiRequest') + clickButton('Mark as complete') + cy.wait('@postTaskStatusCompleteApiRequest') + assertUrl(dashboard.myTasks()) + }) + + it('should redirect to the investment project and show the Flash message after deleting', () => { cy.intercept( 'POST', `/api-proxy/v4/task/${companyTask.id}/archive`, {} ).as('postTaskArchiveApiRequest') - clickButton('Mark as complete') - assertPayload('@postTaskArchiveApiRequest', { reason: 'completed' }) + clickButton('Delete task') + assertPayload('@postTaskArchiveApiRequest', { reason: 'deleted' }) assertUrl(dashboard.myTasks()) }) })