diff --git a/src/components/lists/views/Kanban.vue b/src/components/lists/views/Kanban.vue
index 8dd4ae81..f0234751 100644
--- a/src/components/lists/views/Kanban.vue
+++ b/src/components/lists/views/Kanban.vue
@@ -198,6 +198,7 @@
import {filterObject} from '../../../helpers/filterObject'
import {applyDrag} from '../../../helpers/applyDrag'
+ import {mapState} from 'vuex'
export default {
name: 'Kanban',
@@ -211,7 +212,6 @@
data() {
return {
bucketService: BucketService,
- buckets: [],
taskService: TaskService,
dropPlaceholderOptions: {
@@ -240,12 +240,12 @@
this.loadBuckets()
setTimeout(() => document.addEventListener('click', this.closeBucketDropdowns), 0)
},
+ computed: mapState({
+ buckets: state => state.kanban.buckets,
+ }),
methods: {
loadBuckets() {
- this.bucketService.getAll({listId: this.$route.params.listId})
- .then(r => {
- this.buckets = r
- })
+ this.$store.dispatch('kanban/loadBucketsForList', this.$route.params.listId)
.catch(e => {
this.error(e, this)
})
@@ -271,7 +271,9 @@
delete buckets[bucketIndex]
buckets[bucketIndex] = bucket
// Set the buckets, triggering a state update in vue
- this.buckets = buckets
+ // FIXME: This seems to set some task attributes (like due date) wrong. Commented out, but seems to still work?
+ // Not sure what to do about this.
+ // this.$store.commit('kanban/setBuckets', buckets)
}
if (dropResult.addedIndex !== null) {
@@ -297,10 +299,10 @@
task.bucketId = bucketId
- this.taskService.update(task)
- .then(t => {
+ this.$store.dispatch('tasks/update', task)
+ .then(() => {
// Update the block with the new task details
- this.$set(this.buckets[bucketIndex].tasks, taskIndex, t)
+ // this.$store.commit('kanban/setTaskInBucketByIndex', {bucketIndex, taskIndex, task: t})
this.success({message: 'The task was moved successfully!'}, this)
})
.catch(e => {
@@ -317,14 +319,6 @@
return bucket.tasks[index]
}
},
- getBlockFromTask(task) {
- return {
- id: task.id,
- status: 'bucket' + task.bucketId,
- // We're putting the task in an extra property so we won't have to maintin this whole thing because of basically recreating the task model.
- task: task,
- }
- },
toggleShowNewTaskInput(bucket) {
this.$set(this.showNewTaskInput, bucket, !this.showNewTaskInput[bucket])
},
@@ -360,7 +354,7 @@
this.taskService.create(task)
.then(r => {
this.newTaskText = ''
- this.buckets[bi].tasks.push(r)
+ this.$store.commit('kanban/addTaskToBucket', r)
this.success({message: 'The task was created successfully!'}, this)
})
.catch(e => {
@@ -374,15 +368,10 @@
const newBucket = new BucketModel({title: this.newBucketTitle, listId: parseInt(this.$route.params.listId)})
- this.bucketService.create(newBucket)
- .then(r => {
+ this.$store.dispatch('kanban/createBucket', newBucket)
+ .then(() => {
this.newBucketTitle = ''
this.showNewBucketInput = false
- if (Array.isArray(this.buckets)) {
- this.buckets.push(r)
- } else {
- this.buckets[r.id] = r
- }
this.success({message: 'The bucket was created successfully!'}, this)
})
.catch(e => {
@@ -402,9 +391,9 @@
id: this.bucketToDelete,
listId: this.$route.params.listId,
})
- this.bucketService.delete(bucket)
+
+ this.$store.dispatch('kanban/deleteBucket', bucket)
.then(r => {
- this.loadBuckets()
this.success(r, this)
})
.catch(e => {
@@ -430,7 +419,7 @@
return
}
- this.bucketService.update(bucket)
+ this.$store.dispatch('kanban/updateBucket', bucket)
.then(r => {
this.success({message: 'The bucket title was updated successfully!'}, this)
realBucket.title = r.title
diff --git a/src/components/tasks/TaskDetailView.vue b/src/components/tasks/TaskDetailView.vue
index 5feebc14..7a798335 100644
--- a/src/components/tasks/TaskDetailView.vue
+++ b/src/components/tasks/TaskDetailView.vue
@@ -54,14 +54,14 @@
:class="{ 'disabled': taskService.loading}"
class="input"
:disabled="taskService.loading"
- v-model="task.dueDate"
+ v-model="dueDate"
:config="flatPickerConfig"
@on-close="saveTask"
placeholder="Click here to set a due date"
ref="dueDate"
>
- {task.dueDate = null;saveTask()}">
+ {dueDate = task.dueDate = null;saveTask()}">
@@ -345,6 +345,9 @@
taskService: TaskService,
task: TaskModel,
relationKinds: relationKinds,
+ // The due date is a seperate property in the task to prevent flatpickr from modifying the task model
+ // in store right after updating it from the api resulting in the wrong due date format being saved in the task.
+ dueDate: null,
namespace: NamespaceModel,
showDeleteModal: false,
@@ -401,7 +404,7 @@
},
setActiveFields() {
- this.task.dueDate = +new Date(this.task.dueDate) === 0 ? null : this.task.dueDate
+ this.dueDate = +new Date(this.task.dueDate) === 0 ? null : this.task.dueDate
this.task.startDate = +new Date(this.task.startDate) === 0 ? null : this.task.startDate
this.task.endDate = +new Date(this.task.endDate) === 0 ? null : this.task.endDate
@@ -439,13 +442,15 @@
},
saveTask(undoCallback = null) {
+ this.task.dueDate = this.dueDate
+
// If no end date is being set, but a start date and due date,
// use the due date as the end date
if (this.task.endDate === null && this.task.startDate !== null && this.task.dueDate !== null) {
this.task.endDate = this.task.dueDate
}
- this.taskService.update(this.task)
+ this.$store.dispatch('tasks/update', this.task)
.then(r => {
this.$set(this, 'task', r)
let actions = []
@@ -455,6 +460,7 @@
callback: undoCallback,
}]
}
+ this.dueDate = this.task.dueDate
this.success({message: 'The task was saved successfully.'}, this, actions)
this.setActiveFields()
})
diff --git a/src/components/tasks/reusable/attachments.vue b/src/components/tasks/reusable/attachments.vue
index e32cc762..48ed4f12 100644
--- a/src/components/tasks/reusable/attachments.vue
+++ b/src/components/tasks/reusable/attachments.vue
@@ -160,6 +160,7 @@
r.success.forEach(a => {
this.success({message: 'Successfully uploaded ' + a.file.name}, this)
this.attachments.push(a)
+ this.$store.dispatch('tasks/addTaskAttachment', {taskId: this.taskId, attachment: a})
})
}
if(r.errors !== null) {
diff --git a/src/components/tasks/reusable/editAssignees.vue b/src/components/tasks/reusable/editAssignees.vue
index 02646277..8db4b183 100644
--- a/src/components/tasks/reusable/editAssignees.vue
+++ b/src/components/tasks/reusable/editAssignees.vue
@@ -39,7 +39,6 @@
import UserModel from '../../../models/user'
import ListUserService from '../../../services/listUsers'
import TaskAssigneeService from '../../../services/taskAssignee'
- import TaskAssigneeModel from '../../../models/taskAssignee'
import User from '../../global/user'
export default {
@@ -84,8 +83,7 @@
},
methods: {
addAssignee(user) {
- const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: this.taskId})
- this.taskAssigneeService.create(taskAssignee)
+ this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
.then(() => {
this.success({message: 'The user was successfully assigned.'}, this)
})
@@ -94,8 +92,7 @@
})
},
removeAssignee(user) {
- const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: this.taskId})
- this.taskAssigneeService.delete(taskAssignee)
+ this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
.then(() => {
// Remove the assignee from the list
for (const a in this.assignees) {
diff --git a/src/components/tasks/reusable/editLabels.vue b/src/components/tasks/reusable/editLabels.vue
index ebb37d79..3ac6d7b2 100644
--- a/src/components/tasks/reusable/editLabels.vue
+++ b/src/components/tasks/reusable/editLabels.vue
@@ -41,7 +41,6 @@
import LabelService from '../../../services/label'
import LabelModel from '../../../models/label'
import LabelTaskService from '../../../services/labelTask'
- import LabelTaskModel from '../../../models/labelTask'
export default {
name: 'edit-labels',
@@ -108,8 +107,7 @@
this.$set(this, 'foundLabels', [])
},
addLabel(label) {
- let labelTask = new LabelTaskModel({taskId: this.taskId, labelId: label.id})
- this.labelTaskService.create(labelTask)
+ this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
.then(() => {
this.success({message: 'The label was successfully added.'}, this)
this.$emit('input', this.labels)
@@ -119,8 +117,7 @@
})
},
removeLabel(label) {
- let labelTask = new LabelTaskModel({taskId: this.taskId, labelId: label.id})
- this.labelTaskService.delete(labelTask)
+ this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
.then(() => {
// Remove the label from the list
for (const l in this.labels) {
diff --git a/src/store/index.js b/src/store/index.js
index 9cd30c08..b291d465 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -5,6 +5,8 @@ Vue.use(Vuex)
import config from './modules/config'
import auth from './modules/auth'
import namespaces from './modules/namespaces'
+import kanban from './modules/kanban'
+import tasks from './modules/tasks'
import {CURRENT_LIST, ERROR_MESSAGE, IS_FULLPAGE, LOADING, ONLINE} from './mutation-types'
export const store = new Vuex.Store({
@@ -12,6 +14,8 @@ export const store = new Vuex.Store({
config,
auth,
namespaces,
+ kanban,
+ tasks,
},
state: {
loading: false,
diff --git a/src/store/modules/kanban.js b/src/store/modules/kanban.js
new file mode 100644
index 00000000..ebec1a05
--- /dev/null
+++ b/src/store/modules/kanban.js
@@ -0,0 +1,141 @@
+import Vue from 'vue'
+
+import BucketService from '../../services/bucket'
+import {filterObject} from '../../helpers/filterObject'
+
+/**
+ * This store is intended to hold the currently active kanban view.
+ * It should hold only the current buckets.
+ */
+export default {
+ namespaced: true,
+ state: () => ({
+ buckets: [],
+ }),
+ mutations: {
+ setBuckets(state, buckets) {
+ state.buckets = buckets
+ },
+ addBucket(state, bucket) {
+ state.buckets.push(bucket)
+ },
+ removeBucket(state, bucket) {
+ for (const b in state.buckets) {
+ if (state.buckets[b].id === bucket.id) {
+ state.buckets.splice(b, 1)
+ }
+ }
+ },
+ setBucketById(state, bucket) {
+ for (const b in state.buckets) {
+ if (state.buckets[b].id === bucket.id) {
+ Vue.set(state.buckets, b, bucket)
+ return
+ }
+ }
+ },
+ setBucketByIndex(state, {bucketIndex, bucket}) {
+ Vue.set(state.buckets, bucketIndex, bucket)
+ },
+ setTaskInBucketByIndex(state, {bucketIndex, taskIndex, task}) {
+ const bucket = state.buckets[bucketIndex]
+ bucket.tasks[taskIndex] = task
+ Vue.set(state.buckets, bucketIndex, bucket)
+ },
+ setTaskInBucket(state, task) {
+ // If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
+ if (state.buckets.length === 0) {
+ return
+ }
+
+ for (const b in state.buckets) {
+ if (state.buckets[b].id === task.bucketId) {
+ for (const t in state.buckets[b].tasks) {
+ if (state.buckets[b].tasks[t].id === task.id) {
+ const bucket = state.buckets[b]
+ bucket.tasks[t] = task
+ Vue.set(state.buckets, b, bucket)
+ return
+ }
+ }
+ return
+ }
+ }
+ },
+ addTaskToBucket(state, task) {
+ const bi = filterObject(state.buckets, b => b.id === task.bucketId)
+ state.buckets[bi].tasks.push(task)
+ },
+ },
+ getters: {
+ getTaskById: state => id => {
+ for (const b in state.buckets) {
+ for (const t in state.buckets[b].tasks) {
+ if (state.buckets[b].tasks[t].id === id) {
+ return {
+ bucketIndex: b,
+ taskIndex: t,
+ task: state.buckets[b].tasks[t],
+ }
+ }
+ }
+ }
+ return {
+ bucketIndex: null,
+ taskIndex: null,
+ task: null,
+ }
+ },
+ },
+ actions: {
+ loadBucketsForList(ctx, listId) {
+ const bucketService = new BucketService()
+ return bucketService.getAll({listId: listId})
+ .then(r => {
+ ctx.commit('setBuckets', r)
+ return Promise.resolve()
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ createBucket(ctx, bucket) {
+ const bucketService = new BucketService()
+ return bucketService.create(bucket)
+ .then(r => {
+ ctx.commit('addBucket', r)
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ deleteBucket(ctx, bucket) {
+ const bucketService = new BucketService()
+ return bucketService.delete(bucket)
+ .then(r => {
+ ctx.commit('removeBucket', bucket)
+ // We reload all buckets because tasks are being moved from the deleted bucket
+ ctx.dispatch('loadBucketsForList', bucket.listId)
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ updateBucket(ctx, bucket) {
+ const bucketService = new BucketService()
+ return bucketService.update(bucket)
+ .then(r => {
+ const bi = filterObject(ctx.state.buckets, b => b.id === r.id)
+ const bucket = r
+ bucket.tasks = ctx.state.buckets[bi].tasks
+ ctx.commit('setBucketByIndex', {bucketIndex: bi, bucket})
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ },
+}
\ No newline at end of file
diff --git a/src/store/modules/tasks.js b/src/store/modules/tasks.js
new file mode 100644
index 00000000..a8b7207a
--- /dev/null
+++ b/src/store/modules/tasks.js
@@ -0,0 +1,127 @@
+import TaskService from '../../services/task'
+import TaskAssigneeService from '../../services/taskAssignee'
+import TaskAssigneeModel from '../../models/taskAssignee'
+import LabelTaskModel from '../../models/labelTask'
+import LabelTaskService from '../../services/labelTask'
+
+export default {
+ namespaced: true,
+ state: () => ({}),
+ actions: {
+ update(ctx, task) {
+ const taskService = new TaskService()
+ return taskService.update(task)
+ .then(t => {
+ ctx.commit('kanban/setTaskInBucket', t, {root: true})
+ return Promise.resolve(t)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ // Adds a task attachment in store.
+ // This is an action to be able to commit other mutations
+ addTaskAttachment(ctx, {taskId, attachment}) {
+ const t = ctx.rootGetters['kanban/getTaskById'](taskId)
+ if (t.task === null) {
+ return
+ }
+ t.task.attachments.push(attachment)
+ ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
+ },
+ addAssignee(ctx, {user, taskId}) {
+
+ const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
+ const taskAssigneeService = new TaskAssigneeService()
+
+ return taskAssigneeService.create(taskAssignee)
+ .then(r => {
+ const t = ctx.rootGetters['kanban/getTaskById'](taskId)
+ if (t.task === null) {
+ return Promise.reject('Task not found.')
+ }
+ t.task.assignees.push(user)
+ ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ removeAssignee(ctx, {user, taskId}) {
+
+ const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
+ const taskAssigneeService = new TaskAssigneeService()
+
+ return taskAssigneeService.delete(taskAssignee)
+ .then(r => {
+ const t = ctx.rootGetters['kanban/getTaskById'](taskId)
+ if (t.task === null) {
+ return Promise.reject('Task not found.')
+ }
+
+ for (const a in t.task.assignees) {
+ if (t.task.assignees[a].id === user.id) {
+ t.task.assignees.splice(a, 1)
+ break
+ }
+ }
+
+ ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+
+ },
+ addLabel(ctx, {label, taskId}) {
+
+ const labelTaskService = new LabelTaskService()
+ const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
+
+ return labelTaskService.create(labelTask)
+ .then(r => {
+ const t = ctx.rootGetters['kanban/getTaskById'](taskId)
+ if (t.task === null) {
+ return Promise.reject('Task not found.')
+ }
+ t.task.labels.push(label)
+ ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
+
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ removeLabel(ctx, {label, taskId}) {
+
+ const labelTaskService = new LabelTaskService()
+ const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
+
+ return labelTaskService.delete(labelTask)
+ .then(r => {
+ const t = ctx.rootGetters['kanban/getTaskById'](taskId)
+ if (t.task === null) {
+ return Promise.reject('Task not found.')
+ }
+
+ // Remove the label from the list
+ for (const l in t.task.labels) {
+ if (t.task.labels[l].id === label.id) {
+ t.task.labels.splice(l, 1)
+ break
+ }
+ }
+
+ ctx.commit('kanban/setTaskInBucketByIndex', t, {root: true})
+
+ return Promise.resolve(r)
+ })
+ .catch(e => {
+ return Promise.reject(e)
+ })
+ },
+ },
+}
\ No newline at end of file