Merge branch 'main' of bitbucket.org:studiotem/jandl-taskmanager-app

This commit is contained in:
StudioTem committed 2023-02-15 18:49:34 +01:00
commit 718432b82a
44 files changed
+1033 -753

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{
"data": {
"user": {
"id": 1,
"id": 4,
"name": "Rastislav",
"surname": "Kuttner",
"email": "rastislav.kuttner@jandl.com",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"data": {
"user": {
"id": 1,
"id": 4,
"name": "Rastislav",
"surname": "Kuttner",
"email": "rastislav.kuttner@jandl.com",
+1 -1
View File
@@ -2,7 +2,7 @@
"data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZ2FtaXNmZXJhLndlemVvLmRldlwvY21zXC9hcGlcL3YxXC9hdXRoXC9sb2dpbiIsImlhdCI6MTYxNjQzNTEzMCwiZXhwIjoxNjE2NTIxNTMwLCJuYmYiOjE2MTY0MzUxMzAsImp0aSI6IlJtQmRQNnpDWXJsYWM5d3kiLCJzdWIiOjIsInBydiI6IjFkMDE2ZjgxZmMwNzljMzJkMzY2MDZkYThjOGY4Njk1NzQwYjg0OWQifQ.-a7YeLzozzliAP3UMvqgQEkuHDYG4L04Zdo_sezU9L4",
"user": {
"id": 1,
"id": 4,
"name": "Rastislav",
"surname": "Kuttner",
"email": "rastislav.kuttner@jandl.com",
-62
View File
@@ -1,62 +0,0 @@
<template>
<input type="file" name="file" id="fileInput" class="hidden-input" accept="*"
@change="onChange" ref="fileUpload"
/>
<br>
<br>
<v-btn @click="createWithoutFile()">CREATE without file</v-btn>
<br>
<br>
<v-btn @click="createWithFile()">CREATE with file</v-btn>
</template>
<script>
import moment from 'moment'
import axios from 'axios'
export default {
data() {
return {
files: null
}
},
methods: {
onChange($event) {
this.files = $event.target.files
},
async createWithoutFile() {
const masterTask = this._getMastertaskObject()
await axios.post('https://taskmanager.internal.studiotem.com/api/v1/mastertasks', this._objectToFormData(masterTask))
console.log(await this.$axios.get_data('v1/mastertasks'))
},
async createWithFile() {
if (!this.files) return alert('No files uploaded')
const masterTask = this._getMastertaskObject()
masterTask.files = this.files
console.log(await this.$axios.post_auth_data('v1/mastertasks', masterTask))
},
_getMastertaskObject() {
return {
name: `name-${3}`,
description: `description`,
duration: 1231,
deadline: moment().format('YYYY-MM-DD HH:MM:SS'),
project_id: 1,
team_id: 1,
employees: [{
id: 1,
duration: 1000
}]
}
}
}
}
</script>
+37 -7
View File
@@ -5,13 +5,13 @@
</v-main>
</v-app>
<Z-team-modal ref="z-team-modal" @update="onTeamUpdate" />
<Z-delete-team-modal ref="z-delete-team-modal" @update="onTeamUpdate" />
<Z-team-modal ref="z-team-modal" @update="onTeamUpdate" />
<Z-delete-team-modal ref="z-delete-team-modal" @update="onTeamUpdate" />
<Z-task-modal ref="z-task-modal" @on-task-edited="onTaskEdited" />
<Z-delete-task-modal ref="z-delete-task-modal" @on-task-deleted="onTaskDeleted" />
<Z-delete-master-task-modal ref="z-delete-master-task-modal" @on-master-task-deleted="onMasterTaskDeleted" />
<Z-complete-task-modal ref="z-complete-task-modal" @task-completed="onTaskCompleted" />
<Z-task-modal ref="z-task-modal" @on-task-edited="onTaskEdited" />
<Z-delete-task-modal ref="z-delete-task-modal" @on-task-deleted="onTaskDeleted" />
<Z-delete-master-task-modal ref="z-delete-master-task-modal" @on-master-task-deleted="onMasterTaskDeleted" />
<Z-complete-task-modal ref="z-complete-task-modal" @task-completed="onTaskCompleted" />
<Z-task-add-time-modal ref="z-task-add-time-modal" />
<Z-blocker-modal ref="z-blocker-modal" />
@@ -42,7 +42,21 @@ export default {
ZDeleteMasterTaskModal
},
data() {
return {
loader: null
}
},
async created() {
await this.$store.dispatch('tm/initialize')
this.loader?.hide()
},
mounted() {
this.loader = this.$loading.show()
this.$events.on('show-delete-team-modal', ({team}) => {
this.$refs['z-delete-team-modal'].showModal(team)
})
@@ -60,10 +74,18 @@ export default {
})
this.$events.on('show-task-modal', ({task, editMode, masterTask}) => {
if(task && task.id) {
const id = task.id
this.$router.push({ query: { 'task': id }})
}
this.$refs['z-task-modal'].showModal(task, editMode, masterTask)
})
this.$events.on('show-master-task-modal', ({masterTask, editMode}) => {
this.$events.on('show-master-task-modal', ({ masterTask, editMode }) => {
if (masterTask && masterTask.id) {
const id = masterTask.id
this.$router.push({ query: { 'master-task': id } })
}
this.$refs['z-master-task-modal'].showModal(masterTask, editMode)
})
@@ -104,6 +126,14 @@ export default {
onMasterTaskEdited(masterTask){
this.$events.emit('on-master-task-edited', masterTask)
}
},
watch: {
'$route'() {
if (this.$route.query['master-task']) {
this.$events.emit('show-master-task-modal', { masterTask: { id: this.$route.query['master-task'] }, editMode: false })
}
}
}
}
</script>
+3 -25
View File
@@ -9,6 +9,7 @@ import router from './router'
import store from '@/store'
import w from '@/plugins/w/w'
import axiosCustom from '@/plugins/w@custom/axios'
import tmStore from '@/plugins/tm@_store'
import vueCal from '@/plugins/app@vendor/vue-cal'
import { Options } from '@/plugins/w/w/types'
@@ -51,33 +52,9 @@ const options: Options = {
'POST v1/auth/login': true,
'POST v1/auth/post': true,
// 'GET v1/master-tasks/(id)': true,
// 'GET v1/master-tasks': true,
'POST v1/master-tasks/(id)': true,
// 'DELETE v1/master-tasks/(id)': true,
// 'POST v1/master-tasks': true,
// 'GET v1/overview/projects': true,
'GET v1/overview/tasks': true,
'GET v1/overview/teams': true,
'GET v1/overview/ongoing-tasks': true,
'GET v1/overview/unscheduled-tasks': true,
'GET v1/tasks/(id)': true,
// 'POST v1/tasks/(id)': true,
'DELETE v1/tasks/(id)': true,
// 'GET v1/teams/(id)': true,
// 'POST v1/teams/(id)': true,
// 'DELETE v1/teams/(id)': true,
// 'GET v1/teams': true,
// 'GET v1/teamsx': true,
// 'POST v1/teams': true,
// 'GET v1/employees': true,
'GET v1/overview/tasks': true,
'GET v1/search': true,
'POST v1/tasks': true,
'POST v1/blockers': true
}
}
@@ -91,6 +68,7 @@ const app = createApp(App)
.use(IonicVue)
.use(vuetify)
.use(vueCal)
.use(tmStore)
.use(loader, {
color: '#6200EE'
})
-20
View File
@@ -90,12 +90,6 @@ const routes: Array<RouteRecordRaw> = [
name: 'Search',
meta: { title: 'Search Results' },
component: () => import('@/plugins/lib@search/search/search.vue')
},
{
path: '/app/testing',
name: 'Testing',
meta: { title: 'Testing' },
component: () => import('@/plugins/_testing/testing.vue')
}
]
}
@@ -113,18 +107,4 @@ const router = createRouter({
routes
})
// router.afterEach((to) => {
// if (to.fullPath.includes('?master-task')) {
// const searchParams = new URLSearchParams(to.fullPath.split('?')[1])
// const id = searchParams.get('id')
// this.$events.emit('show-master-task-modal', { id })
// } else if (to.fullPath.includes('?task')) {
// const searchParams = new URLSearchParams(to.fullPath.split('?')[1])
// const id = searchParams.get('id')
// this.$events.emit('show-task-modal', { id })
// }
// })
export default router
@@ -15,8 +15,8 @@
:class="{ active: isActive(page.pageName) }"
@click="$router.push({ name: page.pageName }); closeMenu()"> {{ page.title }}
<span
v-if="page.pageName == 'Overview - Unscheduled tasks'"
class="mb-4 text-center rounded-circle badge">{{ unscheduledTasksLength }}</span>
v-if="page.pageName == 'Overview - Unscheduled tasks' && unscheduledSubtasks"
class="mb-4 text-center rounded-circle badge">{{ unscheduledSubtasks?.length }}</span>
</ion-item>
</ion-list>
</ion-content>
@@ -24,10 +24,11 @@
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data() {
return {
unscheduledTasksLength: null,
pages: [{
title: 'Tasks',
pageName: 'Dashboard - Tasks'
@@ -55,18 +56,16 @@ export default {
}
},
computed: {
...mapGetters('tm', ['unscheduledSubtasks'])
},
created() {
this.$events.on("close-menu", () => this.closeMenu())
this.$events.on("toggle-menu", () => this.toggleMenu())
this.unscheduledTasks()
},
methods: {
async unscheduledTasks() {
let x = await this.$axios.get_auth_data('v1/overview/unscheduled-tasks')
this.unscheduledTasksLength = x.length
},
isActive(pageName) {
return this.$route.name == pageName
},
+2
View File
@@ -19,6 +19,8 @@
.task-badge
background: rgba(0, 0, 0, 0.04)
width: fit-content
padding: 5px 20px
min-width: 50px
.description-detail
background: rgba(0, 0, 0, 0.04)
@@ -43,7 +43,6 @@ export default {
created() {
this.uploadedFiles = this.files
console.log(this.uploadedFiles)
},
methods: {
+8
View File
@@ -0,0 +1,8 @@
import { App } from 'vue'
import tmStore from './store'
export default {
install: (app: App) => {
app.config.globalProperties.$store.registerModule('tm', tmStore)
}
}
+205
View File
@@ -0,0 +1,205 @@
import axios from '@/plugins/w@custom/axios/models/axios'
import store from '@/store'
export default {
namespaced: true,
state: {
mastertasks: null,
employees: null,
teams: null,
projects: null,
unscheduledSubtasks: null,
ongoingSubtasks: null,
initialized: false
},
mutations: {
set_mastertasks(state: any, mastertasks: any) {
state.mastertasks = mastertasks
},
set_employees(state: any, employees: any) {
state.employees = employees
},
set_teams(state: any, teams: any) {
state.teams = teams
},
set_projects(state: any, projects: any) {
state.projects = projects
},
set_unscheduled_subtasks(state: any, subtasks: any) {
state.unscheduledSubtasks = subtasks
},
set_ongoing_subtasks(state: any, subtasks: any) {
state.ongoingSubtasks = subtasks
},
update_team(state: any, team: any) {
state.teams = state.teams.map(t => {
if (t.id == team.id)
return team
return t
})
},
save_team(state: any, team: any) {
state.teams.push(team)
},
delete_team(state: any, team: any) {
state.teams = state.teams.filter(t => t.id != team.id)
},
delete_subtask(state: any, subtask: any) {
state.unscheduledSubtasks = state.unscheduledSubtasks.filter(st => st.id != subtask.id)
state.ongoingSubtasks = state.ongoingSubtasks.filter(st => st.id != subtask.id)
state.mastertasks = state.mastertasks.map(mastertask => mastertask.subtasks.filter(st => st.id != subtask.id))
},
initialized(state: any) {
state.initialized = true
}
},
actions: {
initialize({dispatch, commit}: {dispatch: any, commit: any}) {
return Promise.all([
dispatch('loadMastertasks'),
dispatch('loadEmployees'),
dispatch('loadTeams'),
dispatch('loadProjects'),
dispatch('loadUnscheduledSubtasks'),
dispatch('loadOngoingSubtasks')
]).then(() => commit('initialized'))
},
loadMastertasks({commit}: {commit: any}) {
return new Promise((resolve, reject) => {
axios.get_auth_data('v1/mastertasks')
.then(mastertasks => {
commit('set_mastertasks', mastertasks)
resolve(mastertasks)
})
.catch(error => {
commit('set_mastertasks', {error: error})
reject(error)
})
})
},
loadEmployees({commit}: {commit: any}) {
return new Promise((resolve, reject) => {
axios.get_auth_data('v1/employees')
.then(employees => {
commit('set_employees', employees)
resolve(employees)
})
.catch(error => {
commit('set_employees', {error: error})
reject(error)
})
})
},
loadTeams({commit}: {commit: any}) {
return new Promise((resolve, reject) => {
axios.get_auth_data('v1/teams')
.then(teams => {
commit('set_teams', teams)
resolve(teams)
})
.catch(error => {
commit('set_teams', {error: error})
reject(error)
})
})
},
loadProjects({commit}: {commit: any}) {
return new Promise((resolve, reject) => {
axios.get_auth_data('v1/projects')
.then(project => {
commit('set_projects', project)
resolve(project)
})
.catch(error => {
commit('set_projects', {error: error})
reject(error)
})
})
},
loadUnscheduledSubtasks({commit}: {commit: any}, employeeId = store.getters['auth/user']?.id) {
return new Promise((resolve, reject) => {
axios.get_auth(`v1/subtasks?type=unscheduled&employee_id=${employeeId}`)
.then(({subtasks}) => {
commit('set_unscheduled_subtasks', subtasks)
resolve(subtasks)
})
.catch(error => {
commit('set_unscheduled_subtasks', {error: error})
reject(error)
})
})
},
loadOngoingSubtasks({commit}: {commit: any}, employeeId = store.getters['auth/user']?.id) {
return new Promise((resolve, reject) => {
axios.get_auth(`v1/subtasks?type=ongoing&employee_id=${employeeId}`)
.then(({subtasks}) => {
commit('set_ongoing_subtasks', subtasks)
resolve(subtasks)
})
.catch(error => {
commit('set_ongoing_subtasks', {error: error})
reject(error)
})
})
}
},
getters: {
mastertasks: (state: any) => {
return state.mastertasks
},
employees: (state: any) => {
return state.employees
},
enrichedEmployees: (state: any) => {
if (!state.employees) return []
return state.employees.map(employee => ({...employee, _fullName: `${ employee.name } ${ employee.surname }`}))
},
teams: (state: any) => {
return state.teams
},
projects: (state: any) => {
return state.projects
},
unscheduledSubtasks: (state: any) => {
return state.unscheduledSubtasks
},
ongoingSubtasks: (state: any) => {
return state.ongoingSubtasks
},
isInitialized: (state: any) => {
return state.initialized
}
}
}
+6
View File
@@ -6,9 +6,15 @@ export default {
},
formatDateTime(date) {
moment(date)
return moment(date).format('DD.MM.yyyy, HH:mm')
},
formatDateTimeForBackend(date) {
return moment(date).format('YYYY-MM-DD HH:mm:ss')
},
isBefore(date, compareTo = null) {
return moment(date).isBefore(moment(compareTo))
},
@@ -1,13 +1,12 @@
<template>
<div>
<W-loadable :content="masterTasks" empty-message="Nepodarilo sa nacitat data!">
<A-project-filter :projects="projects" :teams="teams" :employees="employees" @filterChanged="onFilterChanged" />
<A-project-calendar :events="filteredEvents" :month="filter.selectedMonth" @open-detail="openMasterTaskModal" />
</W-loadable>
</div>
<W-loadable :content="filteredEvents" empty-message="Nepodarilo sa nacitat data!">
<A-project-filter :projects="projects" :teams="teams" :employees="employees" @filterChanged="onFilterChanged" />
<A-project-calendar :events="filteredEvents" :month="filter.selectedMonth" @open-detail="openMasterTaskModal" />
</W-loadable>
</template>
<script>
import { mapGetters } from 'vuex'
import AProjectCalendar from './_components/a-project-calendar.vue'
import AProjectFilter from './_components/a-project-filter.vue'
@@ -18,10 +17,6 @@ export default {
},
data() {
return {
projects: null,
masterTasks: null,
teams: null,
employees: null,
selectedProject: null,
filter: {
selectedMonth: new Date().getMonth()
@@ -30,12 +25,25 @@ export default {
},
computed: {
...mapGetters('tm', [
'employees',
'mastertasks',
'teams',
'projects'
]),
filteredEvents() {
if (!this.masterTasks || this.masterTasks.error)
if (!this.mastertasks)
return null
if (this.mastertasks.error)
return this.mastertasks
if (!this.mastertasks.length)
return []
// duplicate all tasks
let events = this.masterTasks
let events = this.mastertasks
// show/hide finished tasks
if (!this.filter.showFinishedTasks)
@@ -55,8 +63,6 @@ export default {
// format masterTasks to event format for calendar
events = events.map(event => {
const color = this._stringToHexColor(event.color)
return {
id: event.id,
title: `${event.project_code} - ${event.name}`,
@@ -65,8 +71,8 @@ export default {
end: new Date(event.deadline),
is_finished: event.is_finished,
project_code: event.project_code,
backgroundColor: `${color}CC`,
borderColor: color
backgroundColor: `${event.color}`,
borderColor: event.color
}
})
@@ -74,56 +80,25 @@ export default {
}
},
created() {
this._getData()
},
mounted() {
this.$events.on('on-master-task-deleted', (masterTask) => {
this.masterTasks = this.masterTasks.filter(mt => mt.id != masterTask.id)
})
// this.$events.on('on-master-task-deleted', (masterTask) => {
// this.mastertasks = this.mastertasks.filter(mt => mt.id != masterTask.id)
// })
this.$events.on('on-master-task-edited', (masterTask) => {
this.masterTasks = this.masterTasks.map(mTask => mTask.id == masterTask.id ? masterTask : mTask)
this._getData()
})
// this.$events.on('on-master-task-edited', (masterTask) => {
// this.mastertasks = this.mastertasks.map(mTask => mTask.id == masterTask.id ? masterTask : mTask)
// this._getData()
// })
},
methods: {
async _getData() {
try {
this.teams = await this.$axios.get_auth_data('v1/teams')
this.projects = await this.$axios.get_auth_data('v1/projects')
this.employees = await this.$axios.get_auth_data('v1/employees')
this.masterTasks = await this.$axios.get_auth_data('v1/mastertasks')
} catch (error) {
this.masterTasks = {error}
}
},
onFilterChanged(filterValues) {
this.filter = filterValues
},
openMasterTaskModal(masterTaskId) {
const masterTask = this.masterTasks.find(masterTask => masterTask.id == masterTaskId)
console.log(masterTask)
const masterTask = this.mastertasks.find(masterTask => masterTask.id == masterTaskId)
this.$events.emit('show-master-task-modal', {masterTask, editMode: false})
},
_stringToHexColor(string) {
let hash = 0
for (let i = 0; i < string.length; i++) {
hash = string.charCodeAt(i) + ((hash << 5) - hash)
}
let color = '#'
for (let i = 0; i < 3; i++) {
let value = (hash >> (i * 8)) & 0xFF
color += ('00' + value.toString(16)).substr(-2)
}
return color
}
}
}
@@ -0,0 +1,24 @@
<template>
<p class="mt-5">Prílohy</p>
<div class="w-100 d-flex align-center">
<div v-for="(file, index) in files" :key="file.id" class="d-flex align-center flex-wrap" :class="[index === 0 ? 'ml-0' : 'ml-2']">
<v-chip class="mt-2">
<p @click="downloadFile(index)" class="cursor-pointer">
{{ file.name }}
</p>
<v-icon class="mx-1" icon="mdi-paperclip"></v-icon>
</v-chip>
</div>
</div>
</template>
<script>
export default {
props: {
files: {
type: Array,
default: () => []
}
}
}
</script>
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650" max-height="90%" height="700px">
<v-dialog v-model="visible" max-width="750" max-height="90%" height="700px">
<W-loadable :content="blocker" class="h-100">
<v-card height="100%" class="d-flex flex-column">
<div class="d-flex justify-space-between align-center modal-header">
@@ -1,7 +1,7 @@
<template>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-drag-vertical" size="large"></v-icon>
<p class="mx-3">Status</p>
@@ -9,7 +9,7 @@
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<v-sheet class="py-3">
<p v-if="masterTask.is_finished" class="status-badge px-4 py-2 rounded-pill">Ukončený</p>
<p v-if="!masterTask.is_finished" class="status-badge px-4 py-2 rounded-pill">Neukončený</p>
</v-sheet>
@@ -17,7 +17,7 @@
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-calendar-week" size="large"></v-icon>
<p class="mx-3">Deadline</p>
@@ -25,14 +25,14 @@
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<v-sheet class="py-3">
<p :class="`${tmUtils.isAfter(masterTask.deadline) ? 'text-black' : 'text-red-darken-4'}`">{{ tmUtils.formatDateTime(masterTask.deadline) }}</p>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="5">
<v-sheet class="pa-2 ma-2 pt-0">
<v-sheet class="pa-2 pt-0">
<div class="d-flex align-center">
<v-icon icon="mdi-account-multiple-outline" size="large"></v-icon>
<p class="mx-3">Tím</p>
@@ -53,7 +53,7 @@
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Naplánovaný čas</p>
@@ -61,14 +61,14 @@
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<v-sheet class="py-3">
<p>{{ masterTask?.duration?.toFixed(2) }} h</p>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-timer-outline" size="large"></v-icon>
<p class="mx-3">Odpracovaný čas</p>
@@ -76,38 +76,43 @@
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<v-sheet class="py-3">
<p :class="masterTask.time_worked > masterTask.duration ? 'text-red-darken-4' : 'text-black'">{{ masterTask.time_worked?.toFixed(2) }} h</p>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center border-bottom">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Zostávajúci čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<v-sheet class="py-3">
<p :class="masterTask.time_worked > masterTask.duration ? 'text-red-darken-4' : 'text-black'">{{ (masterTask.duration - masterTask.time_worked)?.toFixed(2) }} h</p>
</v-sheet>
</v-col>
</v-row>
<div class="px-5 py-5 border-bottom">
<p>Popis zadania</p>
<section v-html="masterTask.description" class="description-detail px-2 py-2 w-100 rounded mt-2" />
<p class="mt-5">Prílohy</p>
<div class="w-100 d-flex align-center">
<div v-for="(file, index) in masterTask.files" :key="file.id" class="d-flex align-center flex-wrap" :class="[index === 0 ? 'ml-0' : 'ml-2']">
<v-chip class="mt-2">
<p @click="downloadFile(index)" class="cursor-pointer">{{ file.name }}</p>
<v-icon class="mx-1" icon="mdi-paperclip"></v-icon>
</v-chip>
<template v-if="masterTask.files?.length">
<p class="mt-5">Prílohy</p>
<div class="w-100 d-flex align-center">
<div v-for="(file, index) in masterTask.files" :key="file.id" class="d-flex align-center flex-wrap" :class="[index === 0 ? 'ml-0' : 'ml-2']">
<v-chip class="mt-2">
<p @click="downloadFile(index)" class="cursor-pointer">{{ file.name }}</p>
<v-icon class="mx-1" icon="mdi-paperclip"></v-icon>
</v-chip>
</div>
</div>
</div>
</template>
</div>
</template>
@@ -1,8 +1,7 @@
export default {
computed: {
employeesOptions() {
const selectedEmployees = [...this.masterTask.employees, ...this.masterTask.team?.employees]
.map(employee => employee.id)
const selectedEmployees = [...this.masterTask.employees].map(employee => employee.id)
const employees = this.employees.filter(
(employee) => !selectedEmployees.includes(employee.id)
@@ -25,10 +24,10 @@ export default {
},
onMasterTaskSelected({ key }) {
const masterTask = this.masterTasks.find(masterTask => masterTask.id == key)
this.task.master_task.name = masterTask.name
this.task.master_task.id = masterTask.id
this.task.project_code = masterTask.project_code
const mastertask = this.mastertasks.find(masterTask => masterTask.id == key)
this.task.master_task.name = mastertask.name
this.task.master_task.id = mastertask.id
this.task.project_code = mastertask.project_code
},
onEmployeeSelected({ key }) {
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650">
<v-dialog v-model="visible" max-width="750">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Ste si istý, že chcete zmazať <b>{{ masterTask.name }}</b>?</h1>
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650" max-height="90%" height="700px">
<v-dialog v-model="visible" max-width="750" max-height="90%" height="700px">
<W-loadable :content="masterTask" class="h-100">
<v-card v-if="editMode" height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center modal-header">
@@ -18,17 +18,13 @@
<v-select v-model="this.masterTask.team_id" @focus="onInputFocus" class="modal-input border-select" :class="{ error: v$.masterTask.team_id.$errors.length }" label="Tím" variant="solo" :items="teams" item-value="id" item-title="name"></v-select>
<v-text-field v-for="(employee, index) in masterTask.employees" :key="employee.id" @focus="onInputFocus" :class="{ error: !masterTask.employees[index].duration }" class="modal-input" :label="`${employee.name} ${employee.surname}`" variant="solo" placeholder="h" v-model="masterTask.employees[index].duration" type="number" min="1" @input="onEmployeeDurationChanged">
<p class="employee-select-avatar d-flex justify-center align-center">
{{ employee.name.charAt(0) + employee.surname.charAt(0) }}
</p>
</v-text-field>
<!-- <v-text-field v-for="(employee, index) in masterTask.team?.employees" :key="employee.id" @focus="onInputFocus" :class="{ error: !masterTask.team.employees[index].duration }" class="modal-input" :label="`${employee.name} ${employee.surname}`" variant="solo" placeholder="h" v-model="masterTask.team.employees[index].duration" type="number" min="1" @input="onEmployeeDurationChanged">
<p class="employee-select-avatar d-flex justify-center align-center">
{{ employee.name.charAt(0) + employee.surname.charAt(0) }}
</p>
</v-text-field> -->
<template v-if="!masterTask.id">
<v-text-field v-for="(employee, index) in masterTask.employees" :key="employee.id" @focus="onInputFocus" :class="{ error: !masterTask.employees[index].duration }" class="modal-input" :label="`${employee.name} ${employee.surname}`" variant="solo" placeholder="h" v-model="masterTask.employees[index].duration" type="number" min="1" @input="onEmployeeDurationChanged">
<p class="employee-select-avatar d-flex justify-center align-center">
{{ employee.name.charAt(0) + employee.surname.charAt(0) }}
</p>
</v-text-field>
</template>
<Z-autocomplete label="Vyhľadať zamestnanca" v-if="showAddEmployeeInput" :options="employeesOptions" class="modal-input" @option-selected="onEmployeeSelected" />
@@ -59,7 +55,8 @@
<v-card-actions class="d-flex flex-column modal-content">
<div class="w-100">
<A-task-description :master-task="masterTask" />
<A-mastertask-description :master-task="masterTask" />
<div class="d-flex justify-start align-center mt-8">
<v-btn class="button primary" @click="save()">Ukončiť mastertask</v-btn>
</div>
@@ -92,7 +89,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css'
import SelectsMixin from './_mixins/selects.mixin'
import ATasksTable from './_components/a-tasks-table.vue'
import ATaskDescription from './_components/a-task-description.vue'
import AMastertaskDescription from './_components/a-mastertask-description.vue'
export default {
// MIXINS
@@ -104,7 +101,7 @@ export default {
mixins: [VuetifyFixFocusMixin, SelectsMixin],
components: {
ATaskDescription,
AMastertaskDescription,
ATasksTable,
Datepicker,
ZFilesUpload,
@@ -149,7 +146,7 @@ export default {
})
this.$events.on('on-task-edited', (task) => {
if (!this.masterTask.subtasks) return
if (!this.masterTask?.subtasks) return
if (this.masterTask.subtasks.find(subtask => subtask.id == task.id)) {
this.masterTask.subtasks = this.masterTask.subtasks.map(t => t.id == task.id ? task : t)
@@ -172,10 +169,10 @@ export default {
this.masterTask = null
try {
this.teams = await this.$axios.get_auth_data('v1/teams')
this.projects = await this.$axios.get_auth_data('v1/projects')
const employees = await this.$axios.get_auth_data('v1/employees')
this.employees = tmUtils.enrichEmployeesByFullName(employees)
this.teams = await this.$axios.get_auth_data('v1/teams')
this.projects = await this.$axios.get_auth_data('v1/projects')
const employees = await this.$axios.get_auth_data('v1/employees')
this.employees = tmUtils.enrichEmployeesByFullName(employees)
if (masterTask?.id) {
this.masterTask = await this.$axios.get_auth_data(`v1/mastertasks/${masterTask.id}`)
@@ -185,16 +182,6 @@ export default {
this.masterTask.team_id = this.masterTask.team.id
this.masterTask.project_id = project.id
this.masterTask.project_code = project.code
// TODO refactor
this.masterTask.employees = this.masterTask.employees.map(employee => {
const subtask = this.masterTask?.subtasks.find(subtask => subtask.employee_id == employee.id)
employee.duration = subtask.duration
return employee
})
console.log(this.masterTask.employees)
} else {
this.masterTask = { employees: [], duration: 0 }
}
@@ -0,0 +1,30 @@
<template>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-link" size="large"></v-icon>
<p class="mx-3">Mastertask</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3">
<p class="task-badge px-4 py-2 rounded-pill">
{{ name }}
</p>
</v-sheet>
</v-col>
</v-row>
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
}
}
}
</script>
@@ -0,0 +1,29 @@
<template>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-drag-vertical" size="large"></v-icon>
<p class="mx-3">Status</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3">
<p v-if="isFinished" class="status-badge px-4 py-2 rounded-pill">Ukončený</p>
<p v-else class="status-badge px-4 py-2 rounded-pill">Neukončený</p>
</v-sheet>
</v-col>
</v-row>
</template>
<script>
export default {
props: {
isFinished: {
type: Number,
default: () => 0
}
}
}
</script>
@@ -0,0 +1,32 @@
<template>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-account-outline" size="large"></v-icon>
<p class="mx-3">Priradený</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3 d-flex align-center">
<p class="employee-selected-avatar d-flex justify-center align-center">
{{ employee.name.charAt(0) + employee.surname.charAt(0) }}
</p>
<p class="ml-2" v-html="`${employee.name} ${employee.surname}`" />
</v-sheet>
</v-col>
</v-row>
</template>
<script>
export default {
props: {
employee: {
type: Object,
required: true
}
}
}
</script>
@@ -0,0 +1,17 @@
<template>
<div class="px-5 py-5 border-bottom">
<p>Popis zadania</p>
<section v-html="description" class="description-detail px-2 py-2 w-100 rounded mt-2" />
</div>
</template>
<script>
export default {
props: {
description: {
type: String,
default: () => ''
}
}
}
</script>
@@ -0,0 +1,28 @@
<template>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Naplánovaný čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3">
<p>{{ hours }} h</p>
</v-sheet>
</v-col>
</v-row>
</template>
<script>
export default {
props: {
hours: {
type: Number,
required: true
}
}
}
</script>
@@ -0,0 +1,29 @@
<template>
<v-row no-gutters class="align-center border-bottom">
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Zostávajúci čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3">
<p>{{ hours }} h</p>
</v-sheet>
</v-col>
</v-row>
</template>
<script>
export default {
props: {
hours: {
type: Number,
required: true
}
}
}
</script>
@@ -0,0 +1,129 @@
<template>
<v-row no-gutters>
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Odpracovaný čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3">
<p>{{ hours }} h</p>
</v-sheet>
<v-sheet class="py-3">
<div class="d-flex align-center mb-3">
<v-icon @click="showModal = true" class="add-date cursor-pointer mr-2" icon="mdi-plus" size="medium" />
<p class="task-badge rounded-pill">Pridať záznam</p>
</div>
<template v-if="task.timesheets.length">
<div v-for="timesheet in task.timesheets" :key="timesheet.id" class="d-flex align-center mb-3">
<v-icon @click.stop="deleteTimesheet(timesheet)" class="remove-date cursor-pointer mr-2" icon="mdi-minus" size="medium" />
<p class="task-badge rounded-pill">{{ timesheet.time }}</p>
</div>
</template>
</v-sheet>
</v-col>
</v-row>
<v-dialog max-width="750" v-model="showModal">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Pridať odpracovaný čas</h1>
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
</div>
<v-card-actions class="mt-10 d-flex justify-start align-center">
<v-text-field v-model="form.hours" variant="solo" bg-color="white" type="number" :hint="v$.form.hours.$errors ? errorUtils.parseErrors(v$.form.hours.$errors) : null" :class="{ error: v$.form.hours.$errors.length }" class="mb-2" label="Odpracovaný čas" />
</v-card-actions>
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
</v-card>
</v-dialog>
</template>
<script>
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import useVuelidate from '@vuelidate/core'
import { required, numeric, minValue } from '@vuelidate/validators'
export default {
props: {
hours: {
type: Number,
required: true
},
task: {
type: Object,
required: true
}
},
data() {
return {
v$: useVuelidate(),
errorUtils,
showModal: false,
form: {
hours: null
}
}
},
validations() {
return {
form: {
hours: {required, numeric, minValue: minValue(1)}
}
}
},
methods: {
async submit() {
if (!await this.v$.$validate())
return
const task = JSON.parse(JSON.stringify(this.task))
task.timesheets.push({
id: null,
employee_id: task.employee_id,
time: typeof this.form.hours == 'string' ? parseInt(this.form.hours) : this.form.hours
})
this._update(task)
},
async deleteTimesheet({ id }) {
const task = JSON.parse(JSON.stringify(this.task))
task.timesheets = task.timesheets.filter(timesheet => timesheet.id != id)
this._update(task)
},
closeModal() {
this.form = {
hours: null
}
this.showModal = false
this.$nextTick(() => this.v$.$reset())
},
async _update(task) {
try {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${task.id}`, task)
this.closeModal()
this.$events.emit('on-task-edited', subtask)
} catch (error) {
this.$toast.error(error.message || error)
}
}
}
}
</script>
@@ -0,0 +1,148 @@
<template>
<v-row no-gutters>
<v-col cols="5">
<v-sheet class="py-3">
<div class="d-flex align-center">
<v-icon icon="mdi-calendar-week" size="large"></v-icon>
<p class="mx-3">Trvanie</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="py-3">
<div class="d-flex align-center mb-3">
<v-icon @click="showModal = true" class="add-date cursor-pointer mr-2" icon="mdi-plus" size="medium" />
<p class="task-badge rounded-pill">Pridať záznam</p>
</div>
<div v-for="timeblock in timeblocks" :key="timeblock.id" class="d-flex align-center mb-3">
<v-icon @click.stop="deleteTimeblock(timeblock)" class="remove-date cursor-pointer mr-2" icon="mdi-minus" size="medium" />
<p class="task-badge rounded-pill">{{ tmUtils.formatDateTime(timeblock.start) }} - {{ tmUtils.formatDateTime(timeblock.end) }}</p>
</div>
</v-sheet>
</v-col>
</v-row>
<v-dialog max-width="750" v-model="showModal">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Pridať dátum</h1>
<v-icon @click.stop="clsoeModal()" icon="mdi-close" size="large"></v-icon>
</div>
<v-card-actions class="mt-10 d-flex flex-column modal-content">
<Datepicker v-model="form.date" format="dd.MM.yyyy" locale="sk" select-text="Vybrať" cancel-text="Zrušiť" placeholder="Dátum" :enable-time-picker="false" class="mb-5 modal-input" />
<v-text-field v-model="form.starts_at_time" type="time" variant="solo" bg-color="white" :hint="v$.form.starts_at_time.$errors ? errorUtils.parseErrors(v$.form.starts_at_time.$errors) : null" :class="{ error: v$.form.starts_at_time.$errors.length }" class="mb-1 modal-input" label="Začiatok" />
<v-text-field v-model="form.ends_at_time" type="time" variant="solo" bg-color="white" :hint="v$.form.ends_at_time.$errors ? errorUtils.parseErrors(v$.form.ends_at_time.$errors) : null" :class="{ error: v$.form.ends_at_time.$errors.length }" class="modal-input" label="Koniec" />
</v-card-actions>
<v-btn @click="submit()" class="button primary mt-5">Potvrdiť</v-btn>
</v-card>
</v-dialog>
</template>
<script>
import tmUtils from '@/plugins/tm@_utils/tm.utils'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import Datepicker from '@vuepic/vue-datepicker'
import moment from 'moment'
export default {
components: {
Datepicker
},
props: {
timeblocks: {
type: Array,
required: true
},
task: {
type: Object,
required: true
}
},
data() {
return {
v$: useVuelidate(),
errorUtils,
tmUtils,
showModal: false,
form: {
date: null,
starts_at_time: '09:00',
ends_at_time: '16:00'
}
}
},
validations() {
return {
form: {
date: { required },
starts_at_time: { required },
ends_at_time: { required }
}
}
},
methods: {
async submit() {
if (!await this.v$.$validate())
return
const task = JSON.parse(JSON.stringify(this.task))
const splittedStartsAtTime = this.form.starts_at_time.split(':')
const splittedEndsAtTime = this.form.ends_at_time.split(':')
const start = moment(this.form.date).hours(parseInt(splittedStartsAtTime[0])).minutes(parseInt(splittedStartsAtTime[1])).toISOString()
const end = moment(this.form.date).hours(parseInt(splittedEndsAtTime[0])).minutes(parseInt(splittedEndsAtTime[1])).toISOString()
task.timeblocks.push({
id: null,
start: tmUtils.formatDateTimeForBackend(start),
end: tmUtils.formatDateTimeForBackend(end)
})
this._update(task)
},
async deleteTimeblock({ id }) {
const task = JSON.parse(JSON.stringify(this.task))
task.timeblocks = task.timeblocks.filter(timeblock => timeblock.id != id)
console.log(task.timeblocks)
console.log(id)
this._update(task)
},
async _update(task) {
try {
const { subtask } = await this.$axios.put_auth(`v1/subtasks/${task.id}`, task)
this.closeModal()
this.$events.emit('on-task-edited', subtask)
} catch (error) {
this.$toast.error(error.message || error)
}
},
closeModal() {
this.form = {
date: null,
starts_at_time: '09:00',
ends_at_time: '16:00'
}
this.showModal = false
this.$nextTick(() => this.v$.$reset())
}
}
}
</script>
@@ -0,0 +1,44 @@
<template>
<A-mastertask-name :name="task.master_task.name" />
<A-mastertask-status :status="task.is_finished" />
<A-task-timeblocks :timeblocks="task.timeblocks" :task="task" />
<A-task-assigned-employee :employee="task.employee" />
<A-task-planned-time :hours="task.duration" />
<A-task-remaining-time :hours="task.duration - task.spent_time" />
<A-task-spent-time :hours="task.spent_time" :task="task" />
<A-task-description :description="task.description" />
<Z-files :files="task.files" />
</template>
<script>
import AMastertaskName from './_components/a-mastertask-name.vue'
import AMastertaskStatus from './_components/a-mastertask-status.vue'
import ATaskTimeblocks from './_components/a-task-timeblocks.vue'
import ATaskAssignedEmployee from './_components/a-task-assigned-employee.vue'
import ATaskPlannedTime from './_components/a-task-planned-time.vue'
import ATaskSpentTime from './_components/a-task-spent-time.vue'
import ATaskRemainingTime from './_components/a-task-remaining-time.vue'
import ATaskDescription from './_components/a-task-description.vue'
import ZFiles from '@/plugins/tm@modal/_components/z-files.vue'
export default {
components: {
ZFiles,
AMastertaskName,
AMastertaskStatus,
ATaskTimeblocks,
ATaskAssignedEmployee,
ATaskPlannedTime,
ATaskSpentTime,
ATaskRemainingTime,
ATaskDescription
},
props: {
task: {
type: Object,
required: true
}
}
}
</script>
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650">
<v-dialog v-model="visible" max-width="750">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Ste si istý, že chcete {{ this.task?.id ? 'ukončiť' : 'obnoviť' }} task, z masterTasku <b>{{ task.master_task }}</b>?</h1>
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650">
<v-dialog v-model="visible" max-width="750">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Ste si istý, že chcete zmazať task z masterTasku <b>{{ task.project_code }}</b>?</h1>
@@ -43,7 +43,9 @@ export default {
async deleteTask() {
try {
await this.$axios.delete(`v1/subtasks/${this.task.id}`)
this.$emit('on-task-deleted', this.task)
// this.$emit('on-task-deleted', this.task)
this.$store.commit('tm/delete_subtask', this.task)
this.closeModal()
} catch (error) {
this.$toast.error(error)
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="show" max-width="650">
<v-dialog v-model="show" max-width="750">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Prosím zadajte odpracovaný čas</h1>
+76 -374
View File
@@ -1,359 +1,51 @@
<template>
<v-dialog v-model="visible" max-width="650" max-height="90%" height="700px">
<v-dialog v-model="visible" max-width="750" max-height="90%" height="700px">
<W-loadable :content="task" class="h-100">
<v-card v-if="editMode" height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center modal-header">
<h1>{{ task.id ? 'Upraviť task' : 'Vytvoriť task' }}</h1>
<h1>{{ task.id ? "Upraviť task" : "Vytvoriť task" }}</h1>
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
</div>
<v-card-actions class="d-flex flex-column modal-content">
<v-text-field
v-model="task.project_code"
label="Kód projektu"
:value="task.project_code"
@focus="onInputFocus"
class="modal-input"
variant="solo"
disabled
></v-text-field>
<v-text-field v-model="task.project_code" label="Kód projektu" :value="task.project_code" @focus="onInputFocus" class="modal-input" variant="solo" disabled></v-text-field>
<Z-autocomplete
label="Master task"
:options="masterTasks.map((masterTask) => ({ key: masterTask.id, value: masterTask.name }))"
:validations="v$.task.master_task.id"
:clear-on-submit="false"
:value="task.master_task.masterTask_name"
:class="{ error: v$.task.master_task.id.$errors.length }"
class="modal-input"
@option-selected="onMasterTaskSelected"
/>
<Z-autocomplete label="Master task" :options="mastertasks.map((masterTask) => ({ key: masterTask.id, value: masterTask.name }))" :validations="v$.task.master_task.id" :clear-on-submit="false" :value="task?.master_task?.name" :class="{ error: v$.task.master_task.id.$errors.length }" class="modal-input" @option-selected="onMasterTaskSelected" />
<v-select
v-model="task.employee_id"
@focus="onInputFocus"
class="modal-input border-select"
label="Priradený"
:class="{ error: v$.task.employee_id.$errors.length }"
variant="solo"
:items="filteredEmployees"
item-value="id"
item-title="_fullName"
></v-select>
<v-select v-model="task.employee_id" @focus="onInputFocus" class="modal-input border-select" label="Priradený" :class="{ error: v$.task.employee_id.$errors.length }" variant="solo" :items="filteredEmployees" item-value="id" item-title="_fullName"></v-select>
<!-- <Datepicker
class="modal-input mb-5"
v-model="task.timeblocks"
format="dd.MM.yyyy, HH:mm"
locale="sk"
:time-picker-position="'right'"
@focus="onDateFocus"
select-text="Vybrať"
cancel-text="Zrušiť"
placeholder="Trvanie od - do"
range
/> -->
<v-text-field
v-model="task.duration"
@focus="onInputFocus"
class="modal-input"
variant="solo"
type="number"
label="Časový odhad"
:class="{ error: v$.task.duration.$errors.length }"
:hint="
v$.task.duration.$errors
? errorUtils.parseErrors(v$.task.duration.$errors)
: null
"
append-inner-icon="mdi-clock-outline"
></v-text-field>
<v-text-field v-model="task.duration" @focus="onInputFocus" class="modal-input" variant="solo" type="number" label="Časový odhad" :class="{ error: v$.task.duration.$errors.length }" :hint="v$.task.duration.$errors ? errorUtils.parseErrors(v$.task.duration.$errors) : null" append-inner-icon="mdi-clock-outline"></v-text-field>
<div class="w-100 modal-input" :class="{ error: v$.task.description.$errors.length }">
<QuillEditor
ref="description"
v-model="task.description"
placeholder="Popis"
theme="snow"
class="rich-text-editor"
@input="task.description = $event.srcElement.innerHTML"
/>
<QuillEditor ref="description" v-model="task.description" placeholder="Popis" theme="snow" class="rich-text-editor" @input="task.description = $event.srcElement.innerHTML" />
</div>
<Z-files-upload class="mt-15 files" :files="task.files" @files-uploaded="filesUploaded" />
</v-card-actions>
<v-btn class="button primary" @click="save()">{{
task.id ? 'UPRAVIŤ' : 'VYTVORIŤ'
}}</v-btn>
<v-btn class="button primary" @click="save()">{{ task.id ? "UPRAVIŤ" : "VYTVORIŤ" }}</v-btn>
</v-card>
<v-card v-else height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center">
<p>{{ task.project_code }}</p>
<v-icon
@click.stop="closeModal()"
icon="mdi-close"
size="large"
class="pb-5"
></v-icon>
<v-icon @click.stop="closeModal()" icon="mdi-close" size="large" class="pb-5"></v-icon>
</div>
<div
class="d-flex justify-space-between align-center modal-header border-bottom pb-4"
>
<div class="d-flex justify-space-between align-center modal-header border-bottom pb-4">
<h1>{{ task.title }}</h1>
<div class="d-flex justify-start">
<img
class="icon"
:src="require('@/plugins/tm@overview/_img/pencil-icon.svg')"
alt="pencil-icon"
@click.stop="editMode = !editMode"
/>
<img
class="icon trash"
:src="require('@/plugins/tm@overview/_img/trash-icon.svg')"
alt="trash-icon"
@click.stop="openDeleteTaskModal(task)"
/>
<img class="icon" :src="require('@/plugins/tm@overview/_img/pencil-icon.svg')" alt="pencil-icon" @click.stop="editMode = !editMode" />
<img class="icon trash" :src="require('@/plugins/tm@overview/_img/trash-icon.svg')" alt="trash-icon" @click.stop="openDeleteTaskModal(task)" />
</div>
</div>
<v-card-actions class="d-flex flex-column modal-content">
<div class="w-100">
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<div class="d-flex align-center">
<v-icon icon="mdi-link" size="large"></v-icon>
<p class="mx-3">Mastertask</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<p class="task-badge px-4 py-2 rounded-pill">
{{ task.master_task.name }}
</p>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<div class="d-flex align-center">
<v-icon
icon="mdi-drag-vertical"
size="large"
></v-icon>
<p class="mx-3">Status</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<p
v-if="task.is_finished"
class="status-badge px-4 py-2 rounded-pill"
>
Ukončený
</p>
<p
v-if="!task.is_finished"
class="status-badge px-4 py-2 rounded-pill"
>
Neukončený
</p>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters>
<v-col cols="5">
<v-sheet class="pa-2 ma-2 pt-5">
<div class="d-flex align-center">
<v-icon
icon="mdi-calendar-week"
size="large"
></v-icon>
<p class="mx-3">Trvanie</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<div v-for="(date, index) in task.timeblocks" :key="date.id">
<div class="d-flex align-center m-5">
<v-icon
@click.stop="removeDate(index)"
class="remove-date cursor-pointer mr-2" icon="mdi-minus" size="medium"
/>
<p
class="task-badge px-4 py-2 rounded-pill mt-2"
v-html="
`${tmUtils.formatDateTime(date.start)} - ${tmUtils.formatDateTime(date.end)}`
"
/>
</div>
</div>
<v-icon
@click="showDatePicker = true"
class="add-date cursor-pointer mt-5" icon="mdi-plus" size="medium"
/>
<v-dialog max-width="650" v-model="showDatePicker">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>Pridať dátum</h1>
<Z-task-readonly-info :task="task" />
<v-icon
@click.stop="showDatePicker = false"
icon="mdi-close"
size="large"
></v-icon>
</div>
<v-card-actions class="mt-10 d-flex justify-start align-center">
<Datepicker
v-model="selectedNewDate"
class="modal-input"
format="dd.MM.yyyy, HH:mm"
locale="sk"
select-text="Vybrať"
cancel-text="Zrušiť"
placeholder="Trvanie od - do"
range
/>
</v-card-actions>
<v-btn @click="addNewDate()" class="button primary mt-5">Potvrdiť</v-btn>
</v-card>
</v-dialog>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<div class="d-flex align-center">
<v-icon
icon="mdi-account-outline"
size="large"
></v-icon>
<p class="mx-3">Priradený</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2 d-flex align-center">
<p
class="employee-selected-avatar d-flex justify-center align-center"
>
{{
task.employee.name.charAt(0) +
task.employee.surname.charAt(0)
}}
</p>
<p
class="ml-2"
v-html="
`${task.employee.name} ${task.employee.surname}`
"
/>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<div class="d-flex align-center">
<v-icon
icon="mdi-clock-time-three-outline"
size="large"
></v-icon>
<p class="mx-3">Naplánovaný čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<p>{{ (task.duration / 3600000).toFixed(2) }} h</p>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<div class="d-flex align-center">
<v-icon
icon="mdi-timer-outline"
size="large"
></v-icon>
<p class="mx-3">Odpracovaný čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<v-text-field
v-model="task.spent_time"
class="modal-input"
variant="solo"
type="number"
placeholder="h"
></v-text-field>
</v-sheet>
</v-col>
</v-row>
<v-row no-gutters class="align-center border-bottom">
<v-col cols="5">
<v-sheet class="pa-2 ma-2">
<div class="d-flex align-center">
<v-icon
icon="mdi-clock-time-three-outline"
size="large"
></v-icon>
<p class="mx-3">Zostávajúci čas</p>
</div>
</v-sheet>
</v-col>
<v-col cols="7">
<v-sheet class="ma-2">
<p>
{{ ((task.duration / 3600000) - (task.spent_time)).toFixed(2) }} h
</p>
</v-sheet>
</v-col>
</v-row>
<div class="px-5 py-5 border-bottom">
<p>Popis zadania</p>
<section
v-html="task.description"
class="description-detail px-2 py-2 w-100 rounded mt-2"
/>
<p class="mt-5">Prílohy</p>
<div class="w-100 d-flex align-center">
<div
v-for="(file, index) in task.files"
:key="file.id"
class="d-flex align-center flex-wrap"
:class="[index === 0 ? 'ml-0' : 'ml-2']"
>
<v-chip class="mt-2">
<p @click="downloadFile(index)" class="cursor-pointer">
{{ file.name }}
</p>
<v-icon
class="mx-1"
icon="mdi-paperclip"
></v-icon>
</v-chip>
</div>
</div>
</div>
<div class="d-flex justify-start align-center mt-8">
<v-btn class="done-btn" @click.stop="task.is_finished = 1; save()"
>Dokončené</v-btn
>
<v-btn class="button secondary" @click="save()"
>Uložiť zmeny</v-btn
>
<v-btn class="done-btn" @click.stop="task.is_finished = 1; save()">Dokončené</v-btn>
<v-btn class="button secondary" @click="save()">Uložiť zmeny</v-btn>
</div>
</div>
</v-card-actions>
@@ -363,26 +55,27 @@
</template>
<script>
import Datepicker from '@vuepic/vue-datepicker'
import ZFilesUpload from '@/plugins/tm@_components/z-files-upload.vue'
import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import moment from 'moment'
import { QuillEditor } from '@vueup/vue-quill'
import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus'
import SelectsMixin from '../master-task/_mixins/selects.mixin'
import * as FileSaver from 'file-saver'
import tmUtils from '@/plugins/tm@_utils/tm.utils'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
import ZAutocomplete from '@/plugins/tm@_components/z-autocomplete.vue'
import ZFilesUpload from "@/plugins/tm@_components/z-files-upload.vue"
import useVuelidate from "@vuelidate/core"
import { required } from "@vuelidate/validators"
import errorUtils from "@/plugins/lib@auth/_utils/error.utils"
import { QuillEditor } from "@vueup/vue-quill"
import VuetifyFixFocusMixin from "@/plugins/tm@modal/_mixins/vuetify-fix-focus"
import SelectsMixin from "../master-task/_mixins/selects.mixin"
import * as FileSaver from "file-saver"
import tmUtils from "@/plugins/tm@_utils/tm.utils"
import "@vueup/vue-quill/dist/vue-quill.snow.css"
import ZAutocomplete from "@/plugins/tm@_components/z-autocomplete.vue"
import ZTaskReadonlyInfo from "./_components/z-task-readonly-info.vue"
import { mapGetters } from "vuex"
export default {
emits: ['close', 'refresh'],
emits: ["close", "refresh"],
mixins: [VuetifyFixFocusMixin, SelectsMixin],
components: {
Datepicker,
ZTaskReadonlyInfo,
ZFilesUpload,
QuillEditor,
ZAutocomplete
@@ -397,8 +90,6 @@ export default {
errorUtils,
v$: useVuelidate(),
preventInputFocus: false,
masterTasks: [],
employees: null,
masterTaskIdSelect: null,
showDatePicker: false,
selectedNewDate: []
@@ -406,14 +97,16 @@ export default {
},
computed: {
...mapGetters('tm', [
'mastertasks',
'enrichedEmployees'
]),
filteredEmployees() {
if (!this.employees || !this.task?.master_task)
return []
if (!this.enrichedEmployees || !this.task?.master_task) return []
const selectedEmployees = this.task.master_task.employees?.map(employee => employee.id) || []
const filteredEmployees = this.employees.filter(employee => !selectedEmployees.includes(employee.id))
return tmUtils.enrichEmployeesByFullName(filteredEmployees)
const selectedEmployees = this.task.master_task.employees?.map((employee) => employee.id) || []
return this.enrichedEmployees.filter((employee) => !selectedEmployees.includes(employee.id))
}
},
@@ -431,35 +124,36 @@ export default {
},
mounted() {
this.$events.on('on-task-deleted', (task) => {
this.$events.on("on-task-edited", (task) => {
if (task?.id == this.task?.id)
this.task = this._enrichTask(task)
// TODO: Remove? riesil som neodstranovanie jedineho zanamu v trvani a odpracovanom case
this.$forceUpdate()
})
this.$events.on("on-task-deleted", (task) => {
if (task?.id == this.task?.id) this.closeModal()
})
},
methods: {
async showModal(task, editMode = false, masterTask = {}) {
this.v$.$reset()
this.visible = true
this.task = null
this.masterTaskIdSelect = null
this.task = null
this.visible = true
this.$nextTick(() => this.v$.$reset())
try {
this.masterTasks = await this.$axios.get_auth_data('v1/mastertasks')
this.employees = await this.$axios.get_auth_data('v1/employees')
if (task?.id) {
const t = await this.$axios.get_auth_data(`v1/subtasks/${task.id}`)
t.master_task = this.masterTasks.find(mt => mt.id == t.master_task_id)
t.employee = this.employees.find(employee => employee.id == t.employee_id)
this.masterTaskIdSelect = t.master_task.id
this.task = t
const fetchedTask = await this.$axios.get_auth_data(`v1/subtasks/${task.id}`)
this.task = this._enrichTask(fetchedTask)
} else {
this.task = {master_task: masterTask}
this.task = { master_task: masterTask }
this.masterTaskIdSelect = masterTask.id
}
this.editMode = (editMode || !this.task.id)
this.editMode = editMode || !this.task.id
} catch (error) {
this.$toast.error(error)
}
@@ -470,21 +164,20 @@ export default {
},
async save() {
console.log(this.task)
if (!(await this.v$.$validate()))
return console.log(this.v$);
if (!(await this.v$.$validate())) return console.log(this.v$)
this.task.description = this.$refs['description'].getHTML()
this.task.description = this.$refs["description"].getHTML()
try {
if (this.task?.id) {
await this.$axios.put_auth_data(`v1/subtasks/${this.task.id}`, this.task)
} else {
const { subtask } = await this.$axios.post_auth(`v1/mastertasks/${this.task.master_task.id}/subtasks`, this.task)
console.log(subtask)
this.task.id = subtask.id
}
this.$events.emit('on-task-edited', this.task)
this.$events.emit("on-task-edited", this.task)
this.closeModal()
} catch (error) {
this.$toast.error(error)
@@ -504,9 +197,9 @@ export default {
async downloadFile(id) {
try {
const file = await this.$axios.get_auth_data(`v1/subtasks/${this.task.id}`, { responseType: 'arraybuffer' })
const file = await this.$axios.get_auth_data(`v1/subtasks/${this.task.id}`, { responseType: "arraybuffer" })
FileSaver.saveAs(file.files[id].path, file.files[id].name)
} catch(error) {
} catch (error) {
this.$toast.error(error)
}
},
@@ -516,30 +209,39 @@ export default {
},
openDeleteTaskModal(task) {
this.$events.emit('show-delete-task-modal', { task })
this.$events.emit("show-delete-task-modal", { task })
},
filesUploaded(files) {
this.task.files = files
},
_enrichTask(task) {
task.master_task = this.mastertasks.find((masterTask) => masterTask.id == task.master_task_id)
task.employee = this.enrichedEmployees.find((employee) => employee.id == task.employee_id)
this.masterTaskIdSelect = task.master_task.id
return task
}
},
watch: {
masterTaskIdSelect(value) {
if (!value || !this.task) return
this.task.master_task = this.masterTasks.find(masterTask => masterTask.id == value)
this.task.project_code = this.task.master_task.project_code
this.task.master_task = this.mastertasks.find((masterTask) => masterTask.id == value)
this.task.project_code = this.task.master_task.project_code
},
'task.employee_id'(value) {
"task.employee_id"(value) {
if (!value) return
this.task.employee = this.employees.find(employee => employee.id == value)
this.task.employee = this.enrichedEmployees.find((employee) => employee.id == value)
},
editMode() {
this.$nextTick(() => {
this.$refs['description']?.setHTML(this.task.description || '')
this.$refs["description"]?.setHTML(this.task.description || "")
})
}
}
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650">
<v-dialog v-model="visible" max-width="750">
<v-card>
<div class="d-flex justify-space-between align-center">
<h1>
@@ -42,6 +42,7 @@ export default {
await this.$axios.delete_auth(`v1/teams/${this.team.id}`)
this.visible = false
this.$emit('update', this.team)
this.$store.commit('tm/delete_team', this.team)
} catch (error) {
this.$toast.error(error)
}
+13 -9
View File
@@ -1,5 +1,5 @@
<template>
<v-dialog v-model="visible" max-width="650" max-height="90%" height="700px">
<v-dialog v-model="visible" max-width="750" max-height="90%" height="700px">
<W-loadable :content="team && allEmployees" class="h-100">
<v-card height="100%" class="d-flex flex-column">
<div class="d-flex justify-space-between align-center modal-header">
@@ -68,7 +68,6 @@ import useVuelidate from '@vuelidate/core'
import { required } from '@vuelidate/validators'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import tmUtils from '@/plugins/tm@_utils/tm.utils'
import axios from 'axios'
const atLeastOne = (value) => value.length
@@ -109,7 +108,7 @@ export default {
if (team?.id) {
const t = await this.$axios.get_auth_data(`v1/teams/${team.id}`)
this.team = {...t, employees: tmUtils.enrichEmployeesByFullName(t.employees)}
this.team = this._enrichTeam(t)
} else {
this.team = {employees: []}
}
@@ -126,16 +125,15 @@ export default {
async save() {
if (!(await this.v$.$validate())) return
try {
if (this.team.id) {
// await this.$axios.put_auth_data(`v1/teams/${this.team.id}`, this.team)
await axios.put(`v1/teams/${this.team.id}`, {data: this.team})
const { team } = await this.$axios.put_auth(`v1/teams/${this.team.id}`, this.team)
this.$store.commit('tm/update_team', team)
} else {
const { team } = await this.$axios.post_auth(`v1/teams`, this.team)
this.$store.commit('tm/save_team', team)
}
else
await this.$axios.post_auth_data(`v1/teams`, this.team)
this.$emit('update', this.team)
this.closeModal()
} catch (error) {
this.$toast.error(error)
@@ -145,6 +143,11 @@ export default {
employeeSelected(selectedOption) {
const employee = this.allEmployees.find((empl) => empl.id == selectedOption.key)
this.team.employees.push(employee)
},
_enrichTeam(team) {
team.employees = tmUtils.enrichEmployeesByFullName(team.employees)
return team
}
},
@@ -153,6 +156,7 @@ export default {
const selectedEmployeesIds = this.team.employees.map(
(employee) => employee.id
)
const employees = this.allEmployees.filter(
(employee) => !selectedEmployeesIds.includes(employee.id)
)
@@ -1,14 +1,15 @@
<template>
<v-row class="px-10 py-8 subheader">
<v-select
v-model="selectedEmployee"
v-model="selectedEmployeeId"
class="subheader-input"
variant="outlined"
label="Zamestanec"
:items="[null, ...allEmployees]"
item-title="fullName"
:items="[null, ...enrichedEmployees]"
item-title="_fullName"
item-value="id"
></v-select>
<v-text-field
v-model="searchQuery"
class="subheader-input"
@@ -22,47 +23,37 @@
</template>
<script>
import { mapGetters } from 'vuex'
export default {
emits: ['seach', 'employee-selected'],
data() {
return {
selectedEmployee: null,
employees: [],
selectedEmployeeId: null,
searchQuery: ''
}
},
computed: {
allEmployees() {
const employees = this.employees.map((employee) => {
return `${employee.name} ${employee.surname}`
})
return employees
}
mounted() {
this.selectedEmployeeId = this.$store.getters['auth/user'].id
},
async created() {
try {
this.employees = await this.$axios.get_auth_data('v1/employees')
this.employees.filter((employee) => {
employee.fullName = `${employee.name} ${employee.surname}`
})
} catch (error) {
this.$toast.error(error)
}
computed: {
...mapGetters('tm', ['enrichedEmployees']),
},
methods: {
onInput() {
this.$emit('queryChange', this.searchQuery)
this.$emit('query-changed', this.searchQuery)
}
},
// watch: {
// selectedEmployee() {
// this.search()
// }
// }
watch: {
selectedEmployeeId() {
this.$emit('employee-selected', this.selectedEmployeeId)
}
}
}
</script>
@@ -13,9 +13,9 @@
<tbody>
<tr class="cursor-pointer" v-for="task in tasks" :key="task.id" @click.stop="editTask(task, false)">
<td>{{ task.project_code }}</td>
<td>{{ task.title }}</td>
<td>{{ task.duration / 1000 / 60 / 60 }} h</td>
<td>{{ (task.remaining_time / 1000 / 60 / 60).toFixed(2) }} h</td>
<td>{{ task.master_task }}</td>
<td>{{ task.duration }} h</td>
<td>{{ task.duration - task.spent_time }} h</td>
<td class="task-action">
<div class="d-flex justify-start">
<img class="pencil-icon" :src="require('@/plugins/tm@overview/_img/pencil-icon.svg')" alt="pencil-icon" @click.stop="editTask(task, true)" />
@@ -1,7 +1,7 @@
<template>
<div>
<Z-tasks-search @queryChange="onSearch" />
<Z-tasks-search @seach="onSearch" @employee-selected="onEmployeeSelected" />
<W-loadable :content="filteredOngoingTasks" :emptyMessage="searchQuery.length ? 'Neboli nájdené žiadne úlohy' : 'Nenašli sa žiadne záznamy'">
<A-ongoing-table :tasks="filteredOngoingTasks" />
@@ -12,6 +12,7 @@
<script>
import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue'
import { mapGetters } from 'vuex'
import AOngoingTable from './_components/a-ongoing-table.vue'
export default {
@@ -22,47 +23,36 @@ export default {
data() {
return {
searchQuery: '',
ongoingTasks: null
searchQuery: ''
}
},
ionViewWillEnter() {
this.$store.dispatch('tm/loadOngoingSubtasks')
},
computed: {
...mapGetters('tm', ['ongoingSubtasks']),
filteredOngoingTasks() {
if (!this.ongoingTasks)
if (!this.ongoingSubtasks)
return null
if (!this.searchQuery)
return this.ongoingTasks
return this.ongoingTasks.filter((task) => {
return this.filterParams(this.searchQuery, task.title) || this.filterParams(this.searchQuery, task.project_code)
return this.ongoingSubtasks.filter(subtask => {
return this.filterParams(this.searchQuery, subtask.master_task) || this.filterParams(this.searchQuery, subtask.project_code)
})
}
},
async created() {
await this._loadData()
},
mounted() {
this.$events.on('on-task-deleted', () => this._loadData())
},
methods: {
async _loadData() {
this.ongoingTasks = null
try {
this.ongoingTasks = await this.$axios.get_auth_data('v1/overview/ongoing-tasks')
} catch (error) {
this.ongoingTasks = {error}
}
},
onSearch(searchQuery) {
this.searchQuery = searchQuery
},
onEmployeeSelected(id) {
this.$store.dispatch('tm/loadOngoingSubtasks', id)
},
filterParams(searchQuery, row) {
return row.toLowerCase().includes(searchQuery.toLowerCase())
}
@@ -11,9 +11,10 @@
<tbody>
<tr class="cursor-pointer" v-for="task in tasks" :key="task.id" @click.stop="editTask(task, false)">
<td>{{ task.project_code }}</td>
<td>{{ task.title }}</td>
<td>{{ (task.duration / 1000 / 60 / 60).toFixed(2) }} h</td>
<td>{{ task.master_task }}</td>
<td>{{ task.spent_time }} h</td>
<td class="task-action">
<div class="d-flex justify-start">
<img class="pencil-icon" :src="require('@/plugins/tm@overview/_img/pencil-icon.svg')" alt="pencil-icon" @click.stop="editTask(task, true)">
@@ -1,7 +1,7 @@
<template>
<div>
<Z-tasks-search @queryChange="onSearch" />
<Z-tasks-search @seach="onSearch" @employee-selected="onEmployeeSelected" />
<W-loadable :content="filteredUnscheduledTasks" :emptyMessage="searchQuery.length ? 'Neboli nájdené žiadne úlohy' : 'Nenašli sa žiadne záznamy'">
<A-unscheduled-table :tasks="filteredUnscheduledTasks" />
@@ -12,6 +12,7 @@
<script>
import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue'
import { mapGetters } from 'vuex'
import AUnscheduledTable from './_components/a-unscheduled-table.vue'
export default {
components: {
@@ -21,47 +22,36 @@ export default {
data() {
return {
searchQuery: '',
unscheduledTasks: null
searchQuery: ''
}
},
ionViewWillEnter() {
this.$store.dispatch('tm/loadUnscheduledSubtasks')
},
computed: {
...mapGetters('tm', ['unscheduledSubtasks']),
filteredUnscheduledTasks() {
if (!this.unscheduledTasks)
if (!this.unscheduledSubtasks)
return null
if (!this.searchQuery)
return this.unscheduledTasks
return this.unscheduledTasks.filter((task) => {
return this.filterParams(this.searchQuery, task.title) || this.filterParams(this.searchQuery, task.project_code)
return this.unscheduledSubtasks.filter(subtask => {
return this.filterParams(this.searchQuery, subtask.master_task) || this.filterParams(this.searchQuery, subtask.project_code)
})
}
},
async created() {
await this._loadData()
},
mounted() {
this.$events.on('on-task-deleted', () => this._loadData())
},
methods: {
async _loadData() {
this.unscheduledTasks = null
try {
this.unscheduledTasks = await this.$axios.get_auth_data('v1/overview/unscheduled-tasks')
} catch (error) {
this.unscheduledTasks = {error}
}
},
onSearch(searchQuery) {
this.searchQuery = searchQuery
},
onEmployeeSelected(id) {
this.$store.dispatch('tm/loadUnscheduledSubtasks', id)
},
filterParams(searchQuery, row) {
return row.toLowerCase().includes(searchQuery.toLowerCase())
}
+4 -25
View File
@@ -1,10 +1,11 @@
<template>
<W-loadable :content="teams">
<A-teams-table :teams="teams" @reloadData="_loadData" />
<A-teams-table :teams="teams" />
</W-loadable>
</template>
<script>
import { mapGetters } from 'vuex';
import ATeamsTable from './_components/a-teams-table.vue'
export default {
@@ -12,30 +13,8 @@ export default {
ATeamsTable
},
data() {
return {
teams: null
}
},
async created() {
await this._loadData()
},
mounted() {
this.$events.on('on-team-update', () => this._loadData())
},
methods: {
async _loadData() {
this.teams = null
try {
this.teams = await this.$axios.get_auth_data('v1/teams')
} catch (error) {
this.teams = {error}
}
}
computed: {
...mapGetters('tm', ['teams'])
}
}
</script>
@@ -27,27 +27,28 @@ export default {
},
async mounted() {
if (this.content) return
await this._startLoading()
},
methods: {
async _startLoading() {
this.loader = await this.$loading.show()
// this.loader = await this.$loading.show()
},
_stopLoading() {
this.loader.hide()
// this.loader?.hide()
}
},
watch: {
content(value) {
if (value) {
this._stopLoading()
}
else
this._startLoading()
}
// content(value) {
// if (value) {
// this._stopLoading()
// }
// else
// this._startLoading()
// }
}
}
</script>
+4 -6
View File
@@ -3,15 +3,14 @@ import { $t } from '@/plugins/app/_config/main'
export default {
parseAndTranslate(data) {
const errorsArray = this._parseToArrayOfStrings(data)
// console.log(errorsArray.map(error => $t(error)))
return errorsArray.join(', ')
// return errorsArray.map(error => $t(error))
},
_parseToArrayOfStrings(data) {
if (data?.data?.message) {
return [$t(data.data.message)]
}
if (typeof data == 'string') {
return [$t(data)]
}
@@ -33,7 +32,6 @@ export default {
if (data && data.data && data.data.error && typeof data.data.error == 'object') {
return Object.values(data.data.error).map(error => $t(error[0]) + ' ')
}
if (data.statusText) {
@@ -128,7 +128,7 @@ export default {
async put_auth(url: string, params = null, config?: any) {
config = this._config(config)
return axios.put(url, this._objectToFormData(params), config)
return axios.put(url, {data: params}, config)
.then(response => response.data)
.catch(error => Promise.reject(toastUtils.parseAndTranslate(error)))
},