diff --git a/frontend/cypress/e2e/task/task.spec.ts b/frontend/cypress/e2e/task/task.spec.ts
index f36fa4814..ac778f1ed 100644
--- a/frontend/cypress/e2e/task/task.spec.ts
+++ b/frontend/cypress/e2e/task/task.spec.ts
@@ -194,6 +194,68 @@ describe('Task', () => {
LabelTaskFactory.truncate()
TaskAttachmentFactory.truncate()
})
+
+ it('provides back navigation to the project in the list view', () => {
+ const tasks = TaskFactory.create(1)
+ cy.intercept('**/projects/1/views/*/tasks**').as('loadTasks')
+ cy.visit('/projects/1/1')
+ cy.wait('@loadTasks')
+ cy.get('.list-view .task')
+ .first()
+ .find('a.task-link')
+ .click()
+ cy.get('.task-view .back-button')
+ .should('be.visible')
+ .click()
+ cy.location('pathname').should('match', /\/projects\/1\/\d+/)
+ })
+
+ it('provides back navigation to the project in the table view', () => {
+ const tasks = TaskFactory.create(1)
+ cy.intercept('**/projects/1/views/*/tasks**').as('loadTasks')
+ cy.visit('/projects/1/3')
+ cy.wait('@loadTasks')
+ cy.get('tbody tr')
+ .first()
+ .find('a')
+ .first()
+ .click()
+ cy.get('.task-view .back-button')
+ .should('be.visible')
+ .click()
+ cy.location('pathname').should('match', /\/projects\/1\/\d+/)
+ })
+
+ it('provides back navigation to the project in the kanban view on mobile', () => {
+ cy.viewport('iphone-8')
+
+ const tasks = TaskFactory.create(1)
+ cy.intercept('**/projects/1/views/*/tasks**').as('loadTasks')
+ cy.visit('/projects/1/4')
+ cy.wait('@loadTasks')
+ cy.get('.kanban-view .tasks .task')
+ .first()
+ .click()
+ cy.get('.task-view .back-button')
+ .should('be.visible')
+ .click()
+ cy.location('pathname').should('match', /\/projects\/1\/\d+/)
+ })
+
+ it('does not provide back navigation to the project in the kanban view on desktop', () => {
+ cy.viewport('macbook-15')
+
+ const tasks = TaskFactory.create(1)
+ cy.intercept('**/projects/1/views/*/tasks**').as('loadTasks')
+ cy.visit('/projects/1/4')
+ cy.wait('@loadTasks')
+ cy.get('.kanban-view .tasks .task')
+ .first()
+ .click()
+ cy.get('.task-view .back-button')
+ .should('not.exist')
+ })
+
it('Shows a 404 page for nonexisting tasks', () => {
cy.visit('/tasks/9999')
@@ -201,6 +263,7 @@ describe('Task', () => {
cy.contains('Not found')
.should('be.visible')
})
+
it('Shows all task details', () => {
const tasks = TaskFactory.create(1, {
id: 1,
diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json
index 6810c4a64..400942c70 100644
--- a/frontend/src/i18n/lang/en.json
+++ b/frontend/src/i18n/lang/en.json
@@ -813,6 +813,7 @@
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToProject": "This task belongs to project '{project}'",
+ "back": "Back to project",
"due": "Due {at}",
"closePopup": "Close popup",
"organization": "Organization",
diff --git a/frontend/src/views/tasks/TaskDetailView.vue b/frontend/src/views/tasks/TaskDetailView.vue
index 42373c4e0..59658eacb 100644
--- a/frontend/src/views/tasks/TaskDetailView.vue
+++ b/frontend/src/views/tasks/TaskDetailView.vue
@@ -11,6 +11,14 @@
v-if="visible"
class="task-view"
>
+
+
+ {{ $t('task.detail.back') }}
+
projectStore.projects[task.value.projectId])
+const projectRoute = computed(() => ({
+ name: 'project.index',
+ params: {projectId: task.value.projectId},
+}))
+
const canWrite = computed(() => (
task.value.maxPermission !== null &&
task.value.maxPermission > PERMISSIONS.READ
@@ -744,6 +757,7 @@ const color = computed(() => {
})
const isModal = computed(() => Boolean(props.backdropView))
+const isMobile = useMediaQuery('(max-width: 1024px)')
function attachmentUpload(file: File, onSuccess?: (url: string) => void) {
return uploadFile(props.taskId, file, onSuccess)