Table View for tasks #76
23
src/App.vue
23
src/App.vue
|
@ -104,18 +104,9 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<aside class="menu namespaces-lists">
|
<aside class="menu namespaces-lists">
|
||||||
<div class="fancycheckbox show-archived-check">
|
<fancycheckbox v-model="showArchived" @change="loadNamespaces()" class="show-archived-check">
|
||||||
<input type="checkbox" v-model="showArchived" @change="loadNamespaces()" style="display: none;" id="showArchivedCheckbox"/>
|
Show Archived
|
||||||
<label class="check" for="showArchivedCheckbox">
|
</fancycheckbox>
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18">
|
|
||||||
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
|
||||||
<polyline points="1 9 7 14 15 4"></polyline>
|
|
||||||
</svg>
|
|
||||||
<span>
|
|
||||||
Show Archived
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
|
<div class="spinner" :class="{ 'is-loading': namespaceService.loading}"></div>
|
||||||
<template v-for="n in namespaces">
|
<template v-for="n in namespaces">
|
||||||
<div :key="n.id">
|
<div :key="n.id">
|
||||||
|
@ -228,11 +219,15 @@
|
||||||
import authTypes from './models/authTypes'
|
import authTypes from './models/authTypes'
|
||||||
|
|
||||||
import swEvents from './ServiceWorker/events'
|
import swEvents from './ServiceWorker/events'
|
||||||
import Notification from "./components/global/notification";
|
import Notification from './components/global/notification'
|
||||||
|
import Fancycheckbox from './components/global/fancycheckbox'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: {Notification},
|
components: {
|
||||||
|
Fancycheckbox,
|
||||||
|
Notification,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: auth.user,
|
user: auth.user,
|
||||||
|
|
58
src/components/global/fancycheckbox.vue
Normal file
58
src/components/global/fancycheckbox.vue
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<template>
|
||||||
|
<div class="fancycheckbox" :class="{'is-disabled': disabled}">
|
||||||
|
<input @change="updateData" type="checkbox" :id="checkBoxId" :checked="checked" style="display: none;" :disabled="disabled">
|
||||||
|
<label :for="checkBoxId" class="check">
|
||||||
|
<svg width="18px" height="18px" viewBox="0 0 18 18">
|
||||||
|
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
||||||
|
<polyline points="1 9 7 14 15 4"></polyline>
|
||||||
|
</svg>
|
||||||
|
<span>
|
||||||
|
<slot></slot>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'fancycheckbox',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
checked: false,
|
||||||
|
checkBoxId: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newVal) {
|
||||||
|
this.checked = newVal
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.checked = this.value
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.checkBoxId = 'fancycheckbox' + Math.random()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateData(e) {
|
||||||
|
this.checked = e.target.checked
|
||||||
|
this.$emit('input', this.checked)
|
||||||
|
this.$emit('change', e.target.checked)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="user">
|
<div class="user" :class="{'is-inline': isInline}">
|
||||||
<img :src="user.getAvatarUrl(avatarSize)" class="avatar" alt="" v-tooltip="user.username" :width="avatarSize" :height="avatarSize"/>
|
<img :src="user.getAvatarUrl(avatarSize)" class="avatar" alt="" v-tooltip="user.username" :width="avatarSize" :height="avatarSize"/>
|
||||||
<span v-if="showUsername" class="username">{{ user.username }}</span>
|
<span v-if="showUsername" class="username">{{ user.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,11 @@
|
||||||
required: false,
|
required: false,
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 50,
|
default: 50,
|
||||||
}
|
},
|
||||||
|
isInline: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -31,6 +35,10 @@
|
||||||
.user {
|
.user {
|
||||||
margin: .5em;
|
margin: .5em;
|
||||||
|
|
||||||
|
&.is-inline {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
-webkit-border-radius: 100%;
|
-webkit-border-radius: 100%;
|
||||||
-moz-border-radius: 100%;
|
-moz-border-radius: 100%;
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
{{ l.title }}
|
{{ l.title }}
|
||||||
</a>
|
</a>
|
||||||
<a class="delete is-small" @click="deleteLabel(l)" v-if="user.infos.id === l.created_by.id"></a>
|
<a class="delete is-small" @click="deleteLabel(l)" v-if="user.infos.id === l.created_by.id"></a>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-4" v-if="isLabelEdit">
|
<div class="column is-4" v-if="isLabelEdit">
|
||||||
|
|
|
@ -28,10 +28,9 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="isArchivedCheck">Is Archived</label>
|
<label class="label" for="isArchivedCheck">Is Archived</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<label class="checkbox" v-tooltip="'If a list is archived, you cannot create new tasks or edit the list or existing tasks.'">
|
<fancycheckbox v-model="list.is_archived" v-tooltip="'If a list is archived, you cannot create new tasks or edit the list or existing tasks.'">
|
||||||
<input type="checkbox" id="isArchivedCheck" v-model="list.is_archived"/>
|
|
||||||
This list is archived
|
This list is archived
|
||||||
</label>
|
</fancycheckbox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -89,10 +88,11 @@
|
||||||
import auth from '../../auth'
|
import auth from '../../auth'
|
||||||
import router from '../../router'
|
import router from '../../router'
|
||||||
import manageSharing from '../sharing/userTeam'
|
import manageSharing from '../sharing/userTeam'
|
||||||
import LinkSharing from '../sharing/linkSharing';
|
import LinkSharing from '../sharing/linkSharing'
|
||||||
|
|
||||||
import ListModel from '../../models/list'
|
import ListModel from '../../models/list'
|
||||||
import ListService from '../../services/list'
|
import ListService from '../../services/list'
|
||||||
|
import Fancycheckbox from '../global/fancycheckbox'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "EditList",
|
name: "EditList",
|
||||||
|
@ -110,6 +110,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
Fancycheckbox,
|
||||||
LinkSharing,
|
LinkSharing,
|
||||||
manageSharing,
|
manageSharing,
|
||||||
verte,
|
verte,
|
||||||
|
|
|
@ -10,12 +10,14 @@
|
||||||
It is not possible to create new or edit tasks or it.
|
It is not possible to create new or edit tasks or it.
|
||||||
</div>
|
</div>
|
||||||
<div class="switch-view">
|
<div class="switch-view">
|
||||||
<router-link :to="{ name: 'showList', params: { id: list.id } }" :class="{'is-active': $route.params.type !== 'gantt'}">List</router-link>
|
<router-link :to="{ name: 'showList', params: { id: list.id } }" :class="{'is-active': $route.params.type !== 'gantt' && $route.params.type !== 'table'}">List</router-link>
|
||||||
<router-link :to="{ name: 'showListWithType', params: { id: list.id, type: 'gantt' } }" :class="{'is-active': $route.params.type === 'gantt'}">Gantt</router-link>
|
<router-link :to="{ name: 'showListWithType', params: { id: list.id, type: 'gantt' } }" :class="{'is-active': $route.params.type === 'gantt'}">Gantt</router-link>
|
||||||
|
<router-link :to="{ name: 'showListWithType', params: { id: list.id, type: 'table' } }" :class="{'is-active': $route.params.type === 'table'}">Table</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<gantt :list="list" v-if="$route.params.type === 'gantt'"/>
|
<gantt :list="list" v-if="$route.params.type === 'gantt'"/>
|
||||||
|
<table-view :list="list" v-else-if="$route.params.type === 'table'"/>
|
||||||
<show-list-task :the-list="list" v-else/>
|
<show-list-task :the-list="list" v-else/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
import ListModel from '../../models/list'
|
import ListModel from '../../models/list'
|
||||||
import ListService from '../../services/list'
|
import ListService from '../../services/list'
|
||||||
import authType from '../../models/authTypes'
|
import authType from '../../models/authTypes'
|
||||||
|
import TableView from '../tasks/TableView'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
@ -40,6 +43,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
TableView,
|
||||||
Gantt,
|
Gantt,
|
||||||
ShowListTask,
|
ShowListTask,
|
||||||
},
|
},
|
||||||
|
@ -50,7 +54,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the type is invalid, redirect the user
|
// If the type is invalid, redirect the user
|
||||||
if (auth.user.authenticated && auth.user.infos.type !== authType.LINK_SHARE && this.$route.params.type !== 'gantt' && this.$route.params.type !== '') {
|
if (
|
||||||
|
auth.user.authenticated &&
|
||||||
|
auth.user.infos.type !== authType.LINK_SHARE &&
|
||||||
|
this.$route.params.type !== 'gantt' &&
|
||||||
|
this.$route.params.type !== 'table' &&
|
||||||
|
this.$route.params.type !== ''
|
||||||
|
) {
|
||||||
router.push({name: 'showList', params: { id: this.$route.params.id }})
|
router.push({name: 'showList', params: { id: this.$route.params.id }})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,18 +28,9 @@
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="isArchivedCheck">Is Archived</label>
|
<label class="label" for="isArchivedCheck">Is Archived</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<div class="fancycheckbox" v-tooltip="'If a namespace is archived, you cannot create new lists or edit it.'">
|
<fancycheckbox v-model="namespace.is_archived" v-tooltip="'If a namespace is archived, you cannot create new lists or edit it.'">
|
||||||
<input type="checkbox" id="isArchivedCheck" v-model="namespace.is_archived"/>
|
This namespace is archived
|
||||||
<label class="check" for="isArchivedCheck">
|
</fancycheckbox>
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18">
|
|
||||||
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
|
||||||
<polyline points="1 9 7 14 15 4"></polyline>
|
|
||||||
</svg>
|
|
||||||
<span>
|
|
||||||
This namespace is archived
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -98,6 +89,7 @@
|
||||||
|
|
||||||
import NamespaceService from '../../services/namespace'
|
import NamespaceService from '../../services/namespace'
|
||||||
import NamespaceModel from '../../models/namespace'
|
import NamespaceModel from '../../models/namespace'
|
||||||
|
import Fancycheckbox from '../global/fancycheckbox'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "EditNamespace",
|
name: "EditNamespace",
|
||||||
|
@ -114,6 +106,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
Fancycheckbox,
|
||||||
manageSharing,
|
manageSharing,
|
||||||
verte,
|
verte,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="gantt-options">
|
<div class="gantt-options">
|
||||||
<div class="fancycheckbox is-block">
|
<fancycheckbox v-model="showTaskswithoutDates" class="is-block">
|
||||||
<input id="showTaskswithoutDates" type="checkbox" style="display: none;" v-model="showTaskswithoutDates">
|
Show tasks which don't have dates set
|
||||||
<label for="showTaskswithoutDates" class="check">
|
</fancycheckbox>
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18">
|
|
||||||
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
|
||||||
<polyline points="1 9 7 14 15 4"></polyline>
|
|
||||||
</svg>
|
|
||||||
<span>
|
|
||||||
Show tasks which don't have dates set
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="range-picker">
|
<div class="range-picker">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="dayWidth">Size</label>
|
<label class="label" for="dayWidth">Size</label>
|
||||||
|
@ -66,10 +57,12 @@
|
||||||
import GanttChart from './gantt-component'
|
import GanttChart from './gantt-component'
|
||||||
import flatPickr from 'vue-flatpickr-component'
|
import flatPickr from 'vue-flatpickr-component'
|
||||||
import ListModel from '../../models/list'
|
import ListModel from '../../models/list'
|
||||||
|
import Fancycheckbox from '../global/fancycheckbox'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Gantt',
|
name: 'Gantt',
|
||||||
components: {
|
components: {
|
||||||
|
Fancycheckbox,
|
||||||
flatPickr,
|
flatPickr,
|
||||||
GanttChart
|
GanttChart
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="loader-container" :class="{ 'is-loading': listService.loading || taskCollectionService.loading}">
|
<div class="loader-container" :class="{ 'is-loading': taskCollectionService.loading}">
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<div class="field has-addons" :class="{ 'hidden': !showTaskSearch }">
|
<div class="field has-addons" :class="{ 'hidden': !showTaskSearch }">
|
||||||
<div class="control has-icons-left has-icons-right">
|
<div class="control has-icons-left has-icons-right">
|
||||||
|
@ -87,13 +87,13 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="pagination is-centered" role="navigation" aria-label="pagination" v-if="taskCollectionService.totalPages > 1">
|
<nav class="pagination is-centered" role="navigation" aria-label="pagination" v-if="taskCollectionService.totalPages > 1">
|
||||||
<router-link class="pagination-previous" :to="{name: 'showList', query: { page: currentPage - 1 }}" tag="button" :disabled="currentPage === 1">Previous</router-link>
|
<router-link class="pagination-previous" :to="getRouteForPagination(currentPage - 1)" tag="button" :disabled="currentPage === 1">Previous</router-link>
|
||||||
<router-link class="pagination-next" :to="{name: 'showList', query: { page: currentPage + 1 }}" tag="button" :disabled="currentPage === taskCollectionService.totalPages">Next page</router-link>
|
<router-link class="pagination-next" :to="getRouteForPagination(currentPage + 1)" tag="button" :disabled="currentPage === taskCollectionService.totalPages">Next page</router-link>
|
||||||
<ul class="pagination-list">
|
<ul class="pagination-list">
|
||||||
<template v-for="(p, i) in pages">
|
<template v-for="(p, i) in pages">
|
||||||
<li :key="'page'+i" v-if="p.isEllipsis"><span class="pagination-ellipsis">…</span></li>
|
<li :key="'page'+i" v-if="p.isEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||||
<li :key="'page'+i" v-else>
|
<li :key="'page'+i" v-else>
|
||||||
<router-link :to="{name: 'showList', query: { page: p.number }}" :class="{'is-current': p.number === currentPage}" class="pagination-link" :aria-label="'Goto page ' + p.number">{{ p.number }}</router-link>
|
<router-link :to="getRouteForPagination(p.number)" :class="{'is-current': p.number === currentPage}" class="pagination-link" :aria-label="'Goto page ' + p.number">{{ p.number }}</router-link>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -102,35 +102,30 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListService from '../../services/list'
|
|
||||||
import TaskService from '../../services/task'
|
import TaskService from '../../services/task'
|
||||||
import ListModel from '../../models/list'
|
import ListModel from '../../models/list'
|
||||||
import EditTask from './edit-task'
|
import EditTask from './edit-task'
|
||||||
import TaskModel from '../../models/task'
|
import TaskModel from '../../models/task'
|
||||||
import TaskCollectionService from '../../services/taskCollection'
|
|
||||||
import SingleTaskInList from './reusable/singleTaskInList'
|
import SingleTaskInList from './reusable/singleTaskInList'
|
||||||
|
import taskList from './helpers/taskList'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
name: 'ListView',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listID: this.$route.params.id,
|
listID: this.$route.params.id,
|
||||||
listService: ListService,
|
|
||||||
taskService: TaskService,
|
taskService: TaskService,
|
||||||
taskCollectionService: TaskCollectionService,
|
|
||||||
pages: [],
|
|
||||||
currentPage: 0,
|
|
||||||
list: {},
|
list: {},
|
||||||
tasks: [],
|
|
||||||
isTaskEdit: false,
|
isTaskEdit: false,
|
||||||
taskEditTask: TaskModel,
|
taskEditTask: TaskModel,
|
||||||
newTaskText: '',
|
newTaskText: '',
|
||||||
|
|
||||||
showError: false,
|
showError: false,
|
||||||
|
|
||||||
showTaskSearch: false,
|
|
||||||
searchTerm: '',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mixins: [
|
||||||
|
taskList,
|
||||||
|
],
|
||||||
components: {
|
components: {
|
||||||
SingleTaskInList,
|
SingleTaskInList,
|
||||||
EditTask,
|
EditTask,
|
||||||
|
@ -145,12 +140,9 @@
|
||||||
theList() {
|
theList() {
|
||||||
this.list = this.theList
|
this.list = this.theList
|
||||||
},
|
},
|
||||||
'$route.query': 'loadTasksForPage', // Only listen for query path changes
|
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.listService = new ListService()
|
|
||||||
this.taskService = new TaskService()
|
this.taskService = new TaskService()
|
||||||
this.taskCollectionService = new TaskCollectionService()
|
|
||||||
this.initTasks(1)
|
this.initTasks(1)
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -179,61 +171,6 @@
|
||||||
this.error(e, this)
|
this.error(e, this)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
loadTasks(page, search = '') {
|
|
||||||
const params = {sort_by: ['done', 'id'], order_by: ['asc', 'desc']}
|
|
||||||
if (search !== '') {
|
|
||||||
params.s = search
|
|
||||||
}
|
|
||||||
this.taskCollectionService.getAll({listID: this.$route.params.id}, params, page)
|
|
||||||
.then(r => {
|
|
||||||
this.$set(this, 'tasks', r)
|
|
||||||
this.$set(this, 'pages', [])
|
|
||||||
this.currentPage = page
|
|
||||||
|
|
||||||
for (let i = 0; i < this.taskCollectionService.totalPages; i++) {
|
|
||||||
|
|
||||||
// Show ellipsis instead of all pages
|
|
||||||
if(
|
|
||||||
i > 0 && // Always at least the first page
|
|
||||||
(i + 1) < this.taskCollectionService.totalPages && // And the last page
|
|
||||||
(
|
|
||||||
// And the current with current + 1 and current - 1
|
|
||||||
(i + 1) > this.currentPage + 1 ||
|
|
||||||
(i + 1) < this.currentPage - 1
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// Only add an ellipsis if the last page isn't already one
|
|
||||||
if(this.pages[i - 1] && !this.pages[i - 1].isEllipsis) {
|
|
||||||
this.pages.push({
|
|
||||||
number: 0,
|
|
||||||
isEllipsis: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pages.push({
|
|
||||||
number: i + 1,
|
|
||||||
isEllipsis: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
this.error(e, this)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
loadTasksForPage(e) {
|
|
||||||
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
|
|
||||||
let page = e.page
|
|
||||||
if (typeof e.page === 'undefined') {
|
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
let search = e.search
|
|
||||||
if (typeof e.search === 'undefined') {
|
|
||||||
search = ''
|
|
||||||
}
|
|
||||||
this.initTasks(page, search)
|
|
||||||
},
|
|
||||||
editTask(id) {
|
editTask(id) {
|
||||||
// Find the selected task and set it to the current object
|
// Find the selected task and set it to the current object
|
||||||
let theTask = this.getTaskByID(id) // Somehow this does not work if we directly assign this to this.taskEditTask
|
let theTask = this.getTaskByID(id) // Somehow this does not work if we directly assign this to this.taskEditTask
|
||||||
|
@ -248,23 +185,6 @@
|
||||||
}
|
}
|
||||||
return {} // FIXME: This should probably throw something to make it clear to the user noting was found
|
return {} // FIXME: This should probably throw something to make it clear to the user noting was found
|
||||||
},
|
},
|
||||||
sortTasks() {
|
|
||||||
if (this.tasks === null || this.tasks === []) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return this.tasks.sort(function(a,b) {
|
|
||||||
if (a.done < b.done)
|
|
||||||
return -1
|
|
||||||
if (a.done > b.done)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if (a.id > b.id)
|
|
||||||
return -1
|
|
||||||
if (a.id < b.id)
|
|
||||||
return 1
|
|
||||||
return 0
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateTasks(updatedTask) {
|
updateTasks(updatedTask) {
|
||||||
for (const t in this.tasks) {
|
for (const t in this.tasks) {
|
||||||
if (this.tasks[t].id === updatedTask.id) {
|
if (this.tasks[t].id === updatedTask.id) {
|
||||||
|
@ -274,25 +194,6 @@
|
||||||
}
|
}
|
||||||
this.sortTasks()
|
this.sortTasks()
|
||||||
},
|
},
|
||||||
searchTasks() {
|
|
||||||
if (this.searchTerm === '') {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$router.push({
|
|
||||||
name: 'showList',
|
|
||||||
query: {search: this.searchTerm}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
hideSearchBar() {
|
|
||||||
// This is a workaround.
|
|
||||||
// When clicking on the search button, @blur from the input is fired. If we
|
|
||||||
// would then directly hide the whole search bar directly, no click event
|
|
||||||
// from the button gets fired. To prevent this, we wait 200ms until we hide
|
|
||||||
// everything so the button has a chance of firering the search event.
|
|
||||||
setTimeout(() => {
|
|
||||||
this.showTaskSearch = false
|
|
||||||
}, 200)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
234
src/components/tasks/TableView.vue
Normal file
234
src/components/tasks/TableView.vue
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
<template>
|
||||||
|
<div class="table-view loader-container" :class="{'is-loading': taskCollectionService.loading}">
|
||||||
|
<div class="column-filter">
|
||||||
|
<button class="button" @click="showActiveColumnsFilter = !showActiveColumnsFilter">
|
||||||
|
<span class="icon is-small">
|
||||||
|
<icon icon="th"/>
|
||||||
|
</span>
|
||||||
|
Columns
|
||||||
|
</button>
|
||||||
|
<transition name="fade">
|
||||||
|
<div class="card" v-if="showActiveColumnsFilter">
|
||||||
|
<div class="card-content">
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.id">#</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.done">Done</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.text">Name</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.priority">Priority</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.labels">Labels</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.assignees">Assignees</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.dueDate">Due Date</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.startDate">Start Date</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.endDate">End Date</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.percentDone">% Done</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.created">Created</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.updated">Updated</fancycheckbox>
|
||||||
|
<fancycheckbox @change="saveTaskColumns" v-model="activeColumns.createdBy">Created By</fancycheckbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table is-hoverable is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th v-if="activeColumns.id">
|
||||||
|
#
|
||||||
|
<sort :order="sortBy.id" @click="sort('id')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.done">
|
||||||
|
Done
|
||||||
|
<sort :order="sortBy.done" @click="sort('done')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.text">
|
||||||
|
Name
|
||||||
|
<sort :order="sortBy.text" @click="sort('text')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.priority">
|
||||||
|
Priority
|
||||||
|
<sort :order="sortBy.priority" @click="sort('priority')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.labels">
|
||||||
|
Labels
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.assignees">
|
||||||
|
Assignees
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.dueDate">
|
||||||
|
Due Date
|
||||||
|
<sort :order="sortBy.due_date_unix" @click="sort('due_date_unix')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.startDate">
|
||||||
|
Start Date
|
||||||
|
<sort :order="sortBy.start_date_unix" @click="sort('start_date_unix')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.endDate">
|
||||||
|
End Date
|
||||||
|
<sort :order="sortBy.end_date_unix" @click="sort('end_date_unix')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.percentDone">
|
||||||
|
% Done
|
||||||
|
<sort :order="sortBy.percent_done" @click="sort('percent_done')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.created">
|
||||||
|
Created
|
||||||
|
<sort :order="sortBy.created" @click="sort('created')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.updated">
|
||||||
|
Updated
|
||||||
|
<sort :order="sortBy.updated" @click="sort('updated')"/>
|
||||||
|
</th>
|
||||||
|
<th v-if="activeColumns.createdBy">
|
||||||
|
Created By
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="t in tasks" :key="t.id">
|
||||||
|
<td v-if="activeColumns.id">
|
||||||
|
<router-link :to="{name: 'taskDetailView', params: { id: t.id }}">{{ t.id }}</router-link>
|
||||||
|
</td>
|
||||||
|
<td v-if="activeColumns.done">
|
||||||
|
<div class="is-done" v-if="t.done">Done</div>
|
||||||
|
</td>
|
||||||
|
<td v-if="activeColumns.text">
|
||||||
|
<router-link :to="{name: 'taskDetailView', params: { id: t.id }}">{{ t.text }}</router-link>
|
||||||
|
</td>
|
||||||
|
<td v-if="activeColumns.priority">
|
||||||
|
<priority-label :priority="t.priority" :show-all="true"/>
|
||||||
|
</td>
|
||||||
|
<td v-if="activeColumns.labels">
|
||||||
|
<labels :labels="t.labels"/>
|
||||||
|
</td>
|
||||||
|
<td v-if="activeColumns.assignees">
|
||||||
|
<user
|
||||||
|
:user="a"
|
||||||
|
:avatar-size="27"
|
||||||
|
:show-username="false"
|
||||||
|
:is-inline="true"
|
||||||
|
v-for="(a, i) in t.assignees"
|
||||||
|
:key="t.id + 'assignee' + a.id + i"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<date-table-cell :date="t.dueDate" v-if="activeColumns.dueDate"/>
|
||||||
|
<date-table-cell :date="t.startDate" v-if="activeColumns.startDate"/>
|
||||||
|
<date-table-cell :date="t.endDate" v-if="activeColumns.endDate"/>
|
||||||
|
<td v-if="activeColumns.percentDone">{{ t.percentDone }}%</td>
|
||||||
|
<date-table-cell :date="t.created" v-if="activeColumns.created"/>
|
||||||
|
<date-table-cell :date="t.updated" v-if="activeColumns.updated"/>
|
||||||
|
<td v-if="activeColumns.createdBy">
|
||||||
|
<user
|
||||||
|
:user="t.createdBy"
|
||||||
|
:show-username="false"
|
||||||
|
:avatar-size="27"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<nav class="pagination is-centered" role="navigation" aria-label="pagination" v-if="taskCollectionService.totalPages > 1">
|
||||||
|
<router-link class="pagination-previous" :to="getRouteForPagination(currentPage - 1, 'table')" tag="button" :disabled="currentPage === 1">Previous</router-link>
|
||||||
|
<router-link class="pagination-next" :to="getRouteForPagination(currentPage + 1, 'table')" tag="button" :disabled="currentPage === taskCollectionService.totalPages">Next page</router-link>
|
||||||
|
<ul class="pagination-list">
|
||||||
|
<template v-for="(p, i) in pages">
|
||||||
|
<li :key="'page'+i" v-if="p.isEllipsis"><span class="pagination-ellipsis">…</span></li>
|
||||||
|
<li :key="'page'+i" v-else>
|
||||||
|
<router-link :to="getRouteForPagination(p.number, 'table')" :class="{'is-current': p.number === currentPage}" class="pagination-link" :aria-label="'Goto page ' + p.number">{{ p.number }}</router-link>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ListModel from '../../models/list'
|
||||||
|
import taskList from './helpers/taskList'
|
||||||
|
import User from '../global/user'
|
||||||
|
import PriorityLabel from './reusable/priorityLabel'
|
||||||
|
import Labels from './reusable/labels'
|
||||||
|
import DateTableCell from './reusable/date-table-cell'
|
||||||
|
import Fancycheckbox from '../global/fancycheckbox'
|
||||||
|
import Sort from './reusable/sort'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TableView',
|
||||||
|
components: {
|
||||||
|
Sort,
|
||||||
|
Fancycheckbox,
|
||||||
|
DateTableCell,
|
||||||
|
Labels,
|
||||||
|
PriorityLabel,
|
||||||
|
User,
|
||||||
|
},
|
||||||
|
mixins: [
|
||||||
|
taskList,
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showActiveColumnsFilter: false,
|
||||||
|
activeColumns: {
|
||||||
|
id: true,
|
||||||
|
done: true,
|
||||||
|
text: true,
|
||||||
|
priority: false,
|
||||||
|
labels: true,
|
||||||
|
assignees: true,
|
||||||
|
dueDate: true,
|
||||||
|
startDate: false,
|
||||||
|
endDate: false,
|
||||||
|
percentDone: false,
|
||||||
|
created: false,
|
||||||
|
updated: false,
|
||||||
|
createdBy: false,
|
||||||
|
},
|
||||||
|
sortBy: {
|
||||||
|
id: 'desc',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
list: {
|
||||||
|
type: ListModel,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const savedShowColumns = localStorage.getItem('tableViewColumns')
|
||||||
|
if (savedShowColumns !== null) {
|
||||||
|
this.$set(this, 'activeColumns', JSON.parse(savedShowColumns))
|
||||||
|
}
|
||||||
|
const savedSortBy = localStorage.getItem('tableViewSortBy')
|
||||||
|
if (savedSortBy !== null) {
|
||||||
|
this.$set(this, 'sortBy', JSON.parse(savedSortBy))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initTasks(1)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initTasks(page, search = '') {
|
||||||
|
let params = {sort_by: [], order_by: []}
|
||||||
|
Object.keys(this.sortBy).map(s => {
|
||||||
|
params.sort_by.push(s)
|
||||||
|
params.order_by.push(this.sortBy[s])
|
||||||
|
})
|
||||||
|
this.loadTasks(page, search, params)
|
||||||
|
},
|
||||||
|
sort(property) {
|
||||||
|
const order = this.sortBy[property]
|
||||||
|
if (typeof order === 'undefined' || order === 'none') {
|
||||||
|
this.$set(this.sortBy, property, 'desc')
|
||||||
|
} else if (order === 'desc') {
|
||||||
|
this.$set(this.sortBy, property, 'asc')
|
||||||
|
} else {
|
||||||
|
this.$delete(this.sortBy, property)
|
||||||
|
}
|
||||||
|
this.initTasks(this.currentPage, this.searchTerm)
|
||||||
|
// Save the order to be able to retrieve them later
|
||||||
|
localStorage.setItem('tableViewSortBy', JSON.stringify(this.sortBy))
|
||||||
|
},
|
||||||
|
saveTaskColumns() {
|
||||||
|
localStorage.setItem('tableViewColumns', JSON.stringify(this.activeColumns))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
128
src/components/tasks/helpers/taskList.js
Normal file
128
src/components/tasks/helpers/taskList.js
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import TaskCollectionService from '../../../services/taskCollection'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This mixin provides a base set of methods and properties to get tasks on a list.
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
taskCollectionService: TaskCollectionService,
|
||||||
|
tasks: [],
|
||||||
|
|
||||||
|
pages: [],
|
||||||
|
currentPage: 0,
|
||||||
|
|
||||||
|
showTaskSearch: false,
|
||||||
|
searchTerm: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.query': 'loadTasksForPage', // Only listen for query path changes
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.taskCollectionService = new TaskCollectionService()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loadTasks(page, search = '', params = {sort_by: ['done', 'id'], order_by: ['asc', 'desc']}) {
|
||||||
|
if (search !== '') {
|
||||||
|
params.s = search
|
||||||
|
}
|
||||||
|
this.taskCollectionService.getAll({listID: this.$route.params.id}, params, page)
|
||||||
|
.then(r => {
|
||||||
|
this.$set(this, 'tasks', r)
|
||||||
|
this.$set(this, 'pages', [])
|
||||||
|
this.currentPage = page
|
||||||
|
|
||||||
|
for (let i = 0; i < this.taskCollectionService.totalPages; i++) {
|
||||||
|
|
||||||
|
// Show ellipsis instead of all pages
|
||||||
|
if(
|
||||||
|
i > 0 && // Always at least the first page
|
||||||
|
(i + 1) < this.taskCollectionService.totalPages && // And the last page
|
||||||
|
(
|
||||||
|
// And the current with current + 1 and current - 1
|
||||||
|
(i + 1) > this.currentPage + 1 ||
|
||||||
|
(i + 1) < this.currentPage - 1
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Only add an ellipsis if the last page isn't already one
|
||||||
|
if(this.pages[i - 1] && !this.pages[i - 1].isEllipsis) {
|
||||||
|
this.pages.push({
|
||||||
|
number: 0,
|
||||||
|
isEllipsis: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pages.push({
|
||||||
|
number: i + 1,
|
||||||
|
isEllipsis: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.error(e, this)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadTasksForPage(e) {
|
||||||
|
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
|
||||||
|
let page = e.page
|
||||||
|
if (typeof e.page === 'undefined') {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
let search = e.search
|
||||||
|
if (typeof e.search === 'undefined') {
|
||||||
|
search = ''
|
||||||
|
}
|
||||||
|
this.initTasks(page, search)
|
||||||
|
},
|
||||||
|
sortTasks() {
|
||||||
|
if (this.tasks === null || this.tasks === []) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return this.tasks.sort(function(a,b) {
|
||||||
|
if (a.done < b.done)
|
||||||
|
return -1
|
||||||
|
if (a.done > b.done)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if (a.id > b.id)
|
||||||
|
return -1
|
||||||
|
if (a.id < b.id)
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchTasks() {
|
||||||
|
if (this.searchTerm === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
name: 'showList',
|
||||||
|
query: {search: this.searchTerm}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
hideSearchBar() {
|
||||||
|
// This is a workaround.
|
||||||
|
// When clicking on the search button, @blur from the input is fired. If we
|
||||||
|
// would then directly hide the whole search bar directly, no click event
|
||||||
|
// from the button gets fired. To prevent this, we wait 200ms until we hide
|
||||||
|
// everything so the button has a chance of firering the search event.
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showTaskSearch = false
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
getRouteForPagination(page = 1, type = 'list') {
|
||||||
|
return {
|
||||||
|
name: 'showListWithType',
|
||||||
|
params: {
|
||||||
|
type: type
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
page: page,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/components/tasks/reusable/date-table-cell.vue
Normal file
17
src/components/tasks/reusable/date-table-cell.vue
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<td v-tooltip="+date === 0 ? '' : formatDate(date)">
|
||||||
|
{{ +date === 0 ? '-' : formatDateSince(date) }}
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'date-table-cell',
|
||||||
|
props: {
|
||||||
|
date: {
|
||||||
|
type: Date,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
25
src/components/tasks/reusable/labels.vue
Normal file
25
src/components/tasks/reusable/labels.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<div class="label-wrapper">
|
||||||
|
<span class="tag" v-for="label in labels" :style="{'background': label.hex_color, 'color': label.textColor}" :key="label.id">
|
||||||
|
<span>{{ label.title }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'labels',
|
||||||
|
props: {
|
||||||
|
labels: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.label-wrapper {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,8 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<span v-if="priority >= priorities.HIGH" class="high-priority" :class="{'not-so-high': priority === priorities.HIGH}">
|
<span v-if="showAll || priority >= priorities.HIGH" :class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}">
|
||||||
<span class="icon">
|
<span class="icon" v-if="priority >= priorities.HIGH">
|
||||||
<icon icon="exclamation"/>
|
<icon icon="exclamation"/>
|
||||||
</span>
|
</span>
|
||||||
|
<template v-if="priority === priorities.UNSET">Unset</template>
|
||||||
|
<template v-if="priority === priorities.LOW">Low</template>
|
||||||
|
<template v-if="priority === priorities.MEDIUM">Medium</template>
|
||||||
<template v-if="priority === priorities.HIGH">High</template>
|
<template v-if="priority === priorities.HIGH">High</template>
|
||||||
<template v-if="priority === priorities.URGENT">Urgent</template>
|
<template v-if="priority === priorities.URGENT">Urgent</template>
|
||||||
<template v-if="priority === priorities.DO_NOW">DO NOW</template>
|
<template v-if="priority === priorities.DO_NOW">DO NOW</template>
|
||||||
|
@ -26,7 +29,11 @@
|
||||||
priority: {
|
priority: {
|
||||||
default: 0,
|
default: 0,
|
||||||
type: Number,
|
type: Number,
|
||||||
}
|
},
|
||||||
|
showAll: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,15 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<div class="fancycheckbox" :class="{'is-disabled': isArchived}">
|
<fancycheckbox v-model="task.done" @change="markAsDone" :disabled="isArchived"/>
|
||||||
<input @change="markAsDone" type="checkbox" :id="task.id" :checked="task.done"
|
|
||||||
style="display: none;" :disabled="isArchived">
|
|
||||||
<label :for="task.id" class="check">
|
|
||||||
<svg width="18px" height="18px" viewBox="0 0 18 18">
|
|
||||||
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
|
|
||||||
<polyline points="1 9 7 14 15 4"></polyline>
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<router-link :to="{ name: 'taskDetailView', params: { id: task.id } }" class="tasktext" :class="{ 'done': task.done}">
|
<router-link :to="{ name: 'taskDetailView', params: { id: task.id } }" class="tasktext" :class="{ 'done': task.done}">
|
||||||
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
<!-- Show any parent tasks to make it clear this task is a sub task of something -->
|
||||||
<span class="parent-tasks" v-if="typeof task.related_tasks.parenttask !== 'undefined'">
|
<span class="parent-tasks" v-if="typeof task.related_tasks.parenttask !== 'undefined'">
|
||||||
|
@ -19,18 +10,15 @@
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
{{ task.text }}
|
{{ task.text }}
|
||||||
<span class="tag" v-for="label in task.labels" :style="{'background': label.hex_color, 'color': label.textColor}"
|
<labels :labels="task.labels"/>
|
||||||
:key="label.id">
|
<user
|
||||||
<span>{{ label.title }}</span>
|
:user="a"
|
||||||
</span>
|
:avatar-size="27"
|
||||||
<img
|
:show-username="false"
|
||||||
:src="a.getAvatarUrl(27)"
|
:is-inline="true"
|
||||||
:alt="a.username"
|
|
||||||
class="avatar"
|
|
||||||
width="27"
|
|
||||||
height="27"
|
|
||||||
v-for="(a, i) in task.assignees"
|
v-for="(a, i) in task.assignees"
|
||||||
:key="task.id + 'assignee' + a.id + i"/>
|
:key="task.id + 'assignee' + a.id + i"
|
||||||
|
/>
|
||||||
<i v-if="task.dueDate > 0"
|
<i v-if="task.dueDate > 0"
|
||||||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||||
v-tooltip="formatDate(task.dueDate)"> - Due {{formatDateSince(task.dueDate)}}</i>
|
v-tooltip="formatDate(task.dueDate)"> - Due {{formatDateSince(task.dueDate)}}</i>
|
||||||
|
@ -43,6 +31,9 @@
|
||||||
import TaskModel from '../../../models/task'
|
import TaskModel from '../../../models/task'
|
||||||
import PriorityLabel from './priorityLabel'
|
import PriorityLabel from './priorityLabel'
|
||||||
import TaskService from '../../../services/task'
|
import TaskService from '../../../services/task'
|
||||||
|
import Labels from './labels'
|
||||||
|
import User from '../../global/user'
|
||||||
|
import Fancycheckbox from '../../global/fancycheckbox'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'singleTaskInList',
|
name: 'singleTaskInList',
|
||||||
|
@ -53,6 +44,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
Fancycheckbox,
|
||||||
|
User,
|
||||||
|
Labels,
|
||||||
PriorityLabel,
|
PriorityLabel,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -78,10 +72,8 @@
|
||||||
this.taskService = new TaskService()
|
this.taskService = new TaskService()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
markAsDone(e) {
|
markAsDone(checked) {
|
||||||
let updateFunc = () => {
|
const updateFunc = () => {
|
||||||
// We get the task, update the 'done' property and then push it to the api.
|
|
||||||
this.task.done = e.target.checked
|
|
||||||
this.taskService.update(this.task)
|
this.taskService.update(this.task)
|
||||||
.then(t => {
|
.then(t => {
|
||||||
this.task = t
|
this.task = t
|
||||||
|
@ -93,8 +85,7 @@
|
||||||
title: 'Undo',
|
title: 'Undo',
|
||||||
callback: () => this.markAsDone({
|
callback: () => this.markAsDone({
|
||||||
target: {
|
target: {
|
||||||
id: e.target.id,
|
checked: !checked
|
||||||
checked: !e.target.checked
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}]
|
}]
|
||||||
|
@ -105,8 +96,8 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.target.checked) {
|
if (checked) {
|
||||||
setTimeout(updateFunc(), 300); // Delay it to show the animation when marking a task as done
|
setTimeout(updateFunc, 300); // Delay it to show the animation when marking a task as done
|
||||||
} else {
|
} else {
|
||||||
updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
updateFunc() // Don't delay it when un-marking it as it doesn't have an animation the other way around
|
||||||
}
|
}
|
||||||
|
|
24
src/components/tasks/reusable/sort.vue
Normal file
24
src/components/tasks/reusable/sort.vue
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<template>
|
||||||
|
<a @click="click">
|
||||||
|
<icon icon="sort-up" v-if="order === 'asc'"/>
|
||||||
|
<icon icon="sort-up" v-else-if="order === 'desc'" rotation="180"/>
|
||||||
|
<icon icon="sort" v-else/>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'sort',
|
||||||
|
props: {
|
||||||
|
order: {
|
||||||
|
type: String,
|
||||||
|
default: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
click() {
|
||||||
|
this.$emit('click')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -64,6 +64,9 @@ import { faClock } from '@fortawesome/free-regular-svg-icons'
|
||||||
import { faHistory } from '@fortawesome/free-solid-svg-icons'
|
import { faHistory } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faCheckDouble } from '@fortawesome/free-solid-svg-icons'
|
import { faCheckDouble } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { faTh } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { faSort } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { faSortUp } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { faComments } from '@fortawesome/free-regular-svg-icons'
|
import { faComments } from '@fortawesome/free-regular-svg-icons'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
|
@ -104,6 +107,9 @@ library.add(faHistory)
|
||||||
library.add(faSearch)
|
library.add(faSearch)
|
||||||
library.add(faCheckDouble)
|
library.add(faCheckDouble)
|
||||||
library.add(faComments)
|
library.add(faComments)
|
||||||
|
library.add(faTh)
|
||||||
|
library.add(faSort)
|
||||||
|
library.add(faSortUp)
|
||||||
|
|
||||||
Vue.component('icon', FontAwesomeIcon)
|
Vue.component('icon', FontAwesomeIcon)
|
||||||
|
|
||||||
|
|
|
@ -13,3 +13,4 @@
|
||||||
@import 'teams';
|
@import 'teams';
|
||||||
@import 'migrator';
|
@import 'migrator';
|
||||||
@import 'comments';
|
@import 'comments';
|
||||||
|
@import 'table-view';
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
span {
|
span {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
|
padding-left: .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
|
32
src/styles/components/table-view.scss
Normal file
32
src/styles/components/table-view.scss
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
.table-view {
|
||||||
|
.table {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.user {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-done {
|
||||||
|
font-size: .9em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-filter {
|
||||||
|
text-align: right;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 180px;
|
||||||
|
position: absolute;
|
||||||
|
right: 3em;
|
||||||
|
margin-top: -80px;
|
||||||
|
|
||||||
|
.card {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancycheckbox {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,16 +55,6 @@
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is-done {
|
|
||||||
background: $green;
|
|
||||||
color: $white;
|
|
||||||
padding: .5em;
|
|
||||||
font-size: 1.5em;
|
|
||||||
margin-left: .5em;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-input {
|
.date-input {
|
||||||
|
@ -181,3 +171,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is-done {
|
||||||
|
background: $green;
|
||||||
|
color: $white;
|
||||||
|
padding: .5em;
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-left: .5em;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user