[tm@store] Added store

This commit is contained in:
Matej Glasnák committed 2023-02-15 15:56:24 +01:00
1 parent a1a410c54d
commit 774ca7b607
44 files changed
+966 -697

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"data": { "data": {
"user": { "user": {
"id": 1, "id": 4,
"name": "Rastislav", "name": "Rastislav",
"surname": "Kuttner", "surname": "Kuttner",
"email": "rastislav.kuttner@jandl.com", "email": "rastislav.kuttner@jandl.com",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"data": { "data": {
"user": { "user": {
"id": 1, "id": 4,
"name": "Rastislav", "name": "Rastislav",
"surname": "Kuttner", "surname": "Kuttner",
"email": "rastislav.kuttner@jandl.com", "email": "rastislav.kuttner@jandl.com",
+1 -1
View File
@@ -2,7 +2,7 @@
"data": { "data": {
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZ2FtaXNmZXJhLndlemVvLmRldlwvY21zXC9hcGlcL3YxXC9hdXRoXC9sb2dpbiIsImlhdCI6MTYxNjQzNTEzMCwiZXhwIjoxNjE2NTIxNTMwLCJuYmYiOjE2MTY0MzUxMzAsImp0aSI6IlJtQmRQNnpDWXJsYWM5d3kiLCJzdWIiOjIsInBydiI6IjFkMDE2ZjgxZmMwNzljMzJkMzY2MDZkYThjOGY4Njk1NzQwYjg0OWQifQ.-a7YeLzozzliAP3UMvqgQEkuHDYG4L04Zdo_sezU9L4", "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvZ2FtaXNmZXJhLndlemVvLmRldlwvY21zXC9hcGlcL3YxXC9hdXRoXC9sb2dpbiIsImlhdCI6MTYxNjQzNTEzMCwiZXhwIjoxNjE2NTIxNTMwLCJuYmYiOjE2MTY0MzUxMzAsImp0aSI6IlJtQmRQNnpDWXJsYWM5d3kiLCJzdWIiOjIsInBydiI6IjFkMDE2ZjgxZmMwNzljMzJkMzY2MDZkYThjOGY4Njk1NzQwYjg0OWQifQ.-a7YeLzozzliAP3UMvqgQEkuHDYG4L04Zdo_sezU9L4",
"user": { "user": {
"id": 1, "id": 4,
"name": "Rastislav", "name": "Rastislav",
"surname": "Kuttner", "surname": "Kuttner",
"email": "rastislav.kuttner@jandl.com", "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>
+6
View File
@@ -42,6 +42,12 @@ export default {
ZDeleteMasterTaskModal ZDeleteMasterTaskModal
}, },
async created() {
const loader = await this.$loading.show()
await this.$store.dispatch('tm/initialize')
loader?.hide()
},
mounted() { mounted() {
this.$events.on('show-delete-team-modal', ({team}) => { this.$events.on('show-delete-team-modal', ({team}) => {
this.$refs['z-delete-team-modal'].showModal(team) this.$refs['z-delete-team-modal'].showModal(team)
+3 -25
View File
@@ -9,6 +9,7 @@ import router from './router'
import store from '@/store' import store from '@/store'
import w from '@/plugins/w/w' import w from '@/plugins/w/w'
import axiosCustom from '@/plugins/w@custom/axios' import axiosCustom from '@/plugins/w@custom/axios'
import tmStore from '@/plugins/tm@_store'
import vueCal from '@/plugins/app@vendor/vue-cal' import vueCal from '@/plugins/app@vendor/vue-cal'
import { Options } from '@/plugins/w/w/types' import { Options } from '@/plugins/w/w/types'
@@ -51,33 +52,9 @@ const options: Options = {
'POST v1/auth/login': true, 'POST v1/auth/login': true,
'POST v1/auth/post': 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/teams': true,
'GET v1/overview/ongoing-tasks': true, 'GET v1/overview/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/search': true, 'GET v1/search': true,
'POST v1/tasks': true,
'POST v1/blockers': true 'POST v1/blockers': true
} }
} }
@@ -91,6 +68,7 @@ const app = createApp(App)
.use(IonicVue) .use(IonicVue)
.use(vuetify) .use(vuetify)
.use(vueCal) .use(vueCal)
.use(tmStore)
.use(loader, { .use(loader, {
color: '#6200EE' color: '#6200EE'
}) })
-6
View File
@@ -90,12 +90,6 @@ const routes: Array<RouteRecordRaw> = [
name: 'Search', name: 'Search',
meta: { title: 'Search Results' }, meta: { title: 'Search Results' },
component: () => import('@/plugins/lib@search/search/search.vue') component: () => import('@/plugins/lib@search/search/search.vue')
},
{
path: '/app/testing',
name: 'Testing',
meta: { title: 'Testing' },
component: () => import('@/plugins/_testing/testing.vue')
} }
] ]
} }
@@ -15,8 +15,8 @@
:class="{ active: isActive(page.pageName) }" :class="{ active: isActive(page.pageName) }"
@click="$router.push({ name: page.pageName }); closeMenu()"> {{ page.title }} @click="$router.push({ name: page.pageName }); closeMenu()"> {{ page.title }}
<span <span
v-if="page.pageName == 'Overview - Unscheduled tasks'" v-if="page.pageName == 'Overview - Unscheduled tasks' && unscheduledSubtasks"
class="mb-4 text-center rounded-circle badge">{{ unscheduledTasksLength }}</span> class="mb-4 text-center rounded-circle badge">{{ unscheduledSubtasks?.length }}</span>
</ion-item> </ion-item>
</ion-list> </ion-list>
</ion-content> </ion-content>
@@ -24,10 +24,11 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
export default { export default {
data() { data() {
return { return {
unscheduledTasksLength: null,
pages: [{ pages: [{
title: 'Tasks', title: 'Tasks',
pageName: 'Dashboard - Tasks' pageName: 'Dashboard - Tasks'
@@ -55,18 +56,16 @@ export default {
} }
}, },
computed: {
...mapGetters('tm', ['unscheduledSubtasks'])
},
created() { created() {
this.$events.on("close-menu", () => this.closeMenu()) this.$events.on("close-menu", () => this.closeMenu())
this.$events.on("toggle-menu", () => this.toggleMenu()) this.$events.on("toggle-menu", () => this.toggleMenu())
this.unscheduledTasks()
}, },
methods: { methods: {
async unscheduledTasks() {
let x = await this.$axios.get_auth_data('v1/overview/unscheduled-tasks')
this.unscheduledTasksLength = x.length
},
isActive(pageName) { isActive(pageName) {
return this.$route.name == pageName return this.$route.name == pageName
}, },
+2
View File
@@ -19,6 +19,8 @@
.task-badge .task-badge
background: rgba(0, 0, 0, 0.04) background: rgba(0, 0, 0, 0.04)
width: fit-content width: fit-content
padding: 5px 20px
min-width: 50px
.description-detail .description-detail
background: rgba(0, 0, 0, 0.04) background: rgba(0, 0, 0, 0.04)
@@ -43,7 +43,6 @@ export default {
created() { created() {
this.uploadedFiles = this.files this.uploadedFiles = this.files
console.log(this.uploadedFiles)
}, },
methods: { 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)
}
}
+203
View File
@@ -0,0 +1,203 @@
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) => {
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) { formatDateTime(date) {
moment(date)
return moment(date).format('DD.MM.yyyy, HH:mm') 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) { isBefore(date, compareTo = null) {
return moment(date).isBefore(moment(compareTo)) return moment(date).isBefore(moment(compareTo))
}, },
@@ -1,13 +1,12 @@
<template> <template>
<div> <W-loadable :content="filteredEvents" empty-message="Nepodarilo sa nacitat data!">
<W-loadable :content="masterTasks" empty-message="Nepodarilo sa nacitat data!">
<A-project-filter :projects="projects" :teams="teams" :employees="employees" @filterChanged="onFilterChanged" /> <A-project-filter :projects="projects" :teams="teams" :employees="employees" @filterChanged="onFilterChanged" />
<A-project-calendar :events="filteredEvents" :month="filter.selectedMonth" @open-detail="openMasterTaskModal" /> <A-project-calendar :events="filteredEvents" :month="filter.selectedMonth" @open-detail="openMasterTaskModal" />
</W-loadable> </W-loadable>
</div>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
import AProjectCalendar from './_components/a-project-calendar.vue' import AProjectCalendar from './_components/a-project-calendar.vue'
import AProjectFilter from './_components/a-project-filter.vue' import AProjectFilter from './_components/a-project-filter.vue'
@@ -18,10 +17,6 @@ export default {
}, },
data() { data() {
return { return {
projects: null,
masterTasks: null,
teams: null,
employees: null,
selectedProject: null, selectedProject: null,
filter: { filter: {
selectedMonth: new Date().getMonth() selectedMonth: new Date().getMonth()
@@ -30,12 +25,25 @@ export default {
}, },
computed: { computed: {
...mapGetters('tm', [
'employees',
'mastertasks',
'teams',
'projects'
]),
filteredEvents() { filteredEvents() {
if (!this.masterTasks || this.masterTasks.error) if (!this.mastertasks)
return null
if (this.mastertasks.error)
return this.mastertasks
if (!this.mastertasks.length)
return [] return []
// duplicate all tasks // duplicate all tasks
let events = this.masterTasks let events = this.mastertasks
// show/hide finished tasks // show/hide finished tasks
if (!this.filter.showFinishedTasks) if (!this.filter.showFinishedTasks)
@@ -55,8 +63,6 @@ export default {
// format masterTasks to event format for calendar // format masterTasks to event format for calendar
events = events.map(event => { events = events.map(event => {
const color = this._stringToHexColor(event.color)
return { return {
id: event.id, id: event.id,
title: `${event.project_code} - ${event.name}`, title: `${event.project_code} - ${event.name}`,
@@ -65,8 +71,8 @@ export default {
end: new Date(event.deadline), end: new Date(event.deadline),
is_finished: event.is_finished, is_finished: event.is_finished,
project_code: event.project_code, project_code: event.project_code,
backgroundColor: `${color}CC`, backgroundColor: `${event.color}`,
borderColor: color borderColor: event.color
} }
}) })
@@ -74,56 +80,25 @@ export default {
} }
}, },
created() {
this._getData()
},
mounted() { mounted() {
this.$events.on('on-master-task-deleted', (masterTask) => { // this.$events.on('on-master-task-deleted', (masterTask) => {
this.masterTasks = this.masterTasks.filter(mt => mt.id != masterTask.id) // this.mastertasks = this.mastertasks.filter(mt => mt.id != masterTask.id)
}) // })
this.$events.on('on-master-task-edited', (masterTask) => { // this.$events.on('on-master-task-edited', (masterTask) => {
this.masterTasks = this.masterTasks.map(mTask => mTask.id == masterTask.id ? masterTask : mTask) // this.mastertasks = this.mastertasks.map(mTask => mTask.id == masterTask.id ? masterTask : mTask)
this._getData() // this._getData()
}) // })
}, },
methods: { 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) { onFilterChanged(filterValues) {
this.filter = filterValues this.filter = filterValues
}, },
openMasterTaskModal(masterTaskId) { openMasterTaskModal(masterTaskId) {
const masterTask = this.masterTasks.find(masterTask => masterTask.id == masterTaskId) const masterTask = this.mastertasks.find(masterTask => masterTask.id == masterTaskId)
console.log(masterTask)
this.$events.emit('show-master-task-modal', {masterTask, editMode: false}) 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> <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"> <W-loadable :content="blocker" class="h-100">
<v-card height="100%" class="d-flex flex-column"> <v-card height="100%" class="d-flex flex-column">
<div class="d-flex justify-space-between align-center modal-header"> <div class="d-flex justify-space-between align-center modal-header">
@@ -1,7 +1,7 @@
<template> <template>
<v-row no-gutters class="align-center"> <v-row no-gutters class="align-center">
<v-col cols="5"> <v-col cols="5">
<v-sheet class="pa-2 ma-2"> <v-sheet class="py-3">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon icon="mdi-drag-vertical" size="large"></v-icon> <v-icon icon="mdi-drag-vertical" size="large"></v-icon>
<p class="mx-3">Status</p> <p class="mx-3">Status</p>
@@ -9,7 +9,7 @@
</v-sheet> </v-sheet>
</v-col> </v-col>
<v-col cols="7"> <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">Ukončený</p>
<p v-if="!masterTask.is_finished" class="status-badge px-4 py-2 rounded-pill">Neukončený</p> <p v-if="!masterTask.is_finished" class="status-badge px-4 py-2 rounded-pill">Neukončený</p>
</v-sheet> </v-sheet>
@@ -17,7 +17,7 @@
</v-row> </v-row>
<v-row no-gutters class="align-center"> <v-row no-gutters class="align-center">
<v-col cols="5"> <v-col cols="5">
<v-sheet class="pa-2 ma-2"> <v-sheet class="py-3">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon icon="mdi-calendar-week" size="large"></v-icon> <v-icon icon="mdi-calendar-week" size="large"></v-icon>
<p class="mx-3">Deadline</p> <p class="mx-3">Deadline</p>
@@ -25,14 +25,14 @@
</v-sheet> </v-sheet>
</v-col> </v-col>
<v-col cols="7"> <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> <p :class="`${tmUtils.isAfter(masterTask.deadline) ? 'text-black' : 'text-red-darken-4'}`">{{ tmUtils.formatDateTime(masterTask.deadline) }}</p>
</v-sheet> </v-sheet>
</v-col> </v-col>
</v-row> </v-row>
<v-row no-gutters> <v-row no-gutters>
<v-col cols="5"> <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"> <div class="d-flex align-center">
<v-icon icon="mdi-account-multiple-outline" size="large"></v-icon> <v-icon icon="mdi-account-multiple-outline" size="large"></v-icon>
<p class="mx-3">Tím</p> <p class="mx-3">Tím</p>
@@ -53,7 +53,7 @@
</v-row> </v-row>
<v-row no-gutters class="align-center"> <v-row no-gutters class="align-center">
<v-col cols="5"> <v-col cols="5">
<v-sheet class="pa-2 ma-2"> <v-sheet class="py-3">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon> <v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Naplánovaný čas</p> <p class="mx-3">Naplánovaný čas</p>
@@ -61,14 +61,14 @@
</v-sheet> </v-sheet>
</v-col> </v-col>
<v-col cols="7"> <v-col cols="7">
<v-sheet class="ma-2"> <v-sheet class="py-3">
<p>{{ masterTask?.duration?.toFixed(2) }} h</p> <p>{{ masterTask?.duration?.toFixed(2) }} h</p>
</v-sheet> </v-sheet>
</v-col> </v-col>
</v-row> </v-row>
<v-row no-gutters class="align-center"> <v-row no-gutters class="align-center">
<v-col cols="5"> <v-col cols="5">
<v-sheet class="pa-2 ma-2"> <v-sheet class="py-3">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon icon="mdi-timer-outline" size="large"></v-icon> <v-icon icon="mdi-timer-outline" size="large"></v-icon>
<p class="mx-3">Odpracovaný čas</p> <p class="mx-3">Odpracovaný čas</p>
@@ -76,29 +76,33 @@
</v-sheet> </v-sheet>
</v-col> </v-col>
<v-col cols="7"> <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> <p :class="masterTask.time_worked > masterTask.duration ? 'text-red-darken-4' : 'text-black'">{{ masterTask.time_worked?.toFixed(2) }} h</p>
</v-sheet> </v-sheet>
</v-col> </v-col>
</v-row> </v-row>
<v-row no-gutters class="align-center border-bottom"> <v-row no-gutters class="align-center border-bottom">
<v-col cols="5"> <v-col cols="5">
<v-sheet class="pa-2 ma-2"> <v-sheet class="py-3">
<div class="d-flex align-center"> <div class="d-flex align-center">
<v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon> <v-icon icon="mdi-clock-time-three-outline" size="large"></v-icon>
<p class="mx-3">Zostávajúci čas</p> <p class="mx-3">Zostávajúci čas</p>
</div> </div>
</v-sheet> </v-sheet>
</v-col> </v-col>
<v-col cols="7"> <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> <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-sheet>
</v-col> </v-col>
</v-row> </v-row>
<div class="px-5 py-5 border-bottom"> <div class="px-5 py-5 border-bottom">
<p>Popis zadania</p> <p>Popis zadania</p>
<section v-html="masterTask.description" class="description-detail px-2 py-2 w-100 rounded mt-2" /> <section v-html="masterTask.description" class="description-detail px-2 py-2 w-100 rounded mt-2" />
<template v-if="masterTask.files?.length">
<p class="mt-5">Prílohy</p> <p class="mt-5">Prílohy</p>
<div class="w-100 d-flex align-center"> <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']"> <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']">
@@ -108,6 +112,7 @@
</v-chip> </v-chip>
</div> </div>
</div> </div>
</template>
</div> </div>
</template> </template>
@@ -1,8 +1,7 @@
export default { export default {
computed: { computed: {
employeesOptions() { employeesOptions() {
const selectedEmployees = [...this.masterTask.employees, ...this.masterTask.team?.employees] const selectedEmployees = [...this.masterTask.employees].map(employee => employee.id)
.map(employee => employee.id)
const employees = this.employees.filter( const employees = this.employees.filter(
(employee) => !selectedEmployees.includes(employee.id) (employee) => !selectedEmployees.includes(employee.id)
@@ -1,5 +1,5 @@
<template> <template>
<v-dialog v-model="visible" max-width="650"> <v-dialog v-model="visible" max-width="750">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<h1>Ste si istý, že chcete zmazať <b>{{ masterTask.name }}</b>?</h1> <h1>Ste si istý, že chcete zmazať <b>{{ masterTask.name }}</b>?</h1>
@@ -1,5 +1,5 @@
<template> <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"> <W-loadable :content="masterTask" class="h-100">
<v-card v-if="editMode" height="100%" class="d-flex flex-column scrolling"> <v-card v-if="editMode" height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center modal-header"> <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-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>
<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"> <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"> <p class="employee-select-avatar d-flex justify-center align-center">
{{ employee.name.charAt(0) + employee.surname.charAt(0) }} {{ employee.name.charAt(0) + employee.surname.charAt(0) }}
</p> </p>
</v-text-field> </v-text-field>
</template>
<!-- <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> -->
<Z-autocomplete label="Vyhľadať zamestnanca" v-if="showAddEmployeeInput" :options="employeesOptions" class="modal-input" @option-selected="onEmployeeSelected" /> <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"> <v-card-actions class="d-flex flex-column modal-content">
<div class="w-100"> <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"> <div class="d-flex justify-start align-center mt-8">
<v-btn class="button primary" @click="save()">Ukončiť mastertask</v-btn> <v-btn class="button primary" @click="save()">Ukončiť mastertask</v-btn>
</div> </div>
@@ -92,7 +89,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css'
import SelectsMixin from './_mixins/selects.mixin' import SelectsMixin from './_mixins/selects.mixin'
import ATasksTable from './_components/a-tasks-table.vue' 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 { export default {
// MIXINS // MIXINS
@@ -104,7 +101,7 @@ export default {
mixins: [VuetifyFixFocusMixin, SelectsMixin], mixins: [VuetifyFixFocusMixin, SelectsMixin],
components: { components: {
ATaskDescription, AMastertaskDescription,
ATasksTable, ATasksTable,
Datepicker, Datepicker,
ZFilesUpload, ZFilesUpload,
@@ -149,7 +146,7 @@ export default {
}) })
this.$events.on('on-task-edited', (task) => { 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)) { if (this.masterTask.subtasks.find(subtask => subtask.id == task.id)) {
this.masterTask.subtasks = this.masterTask.subtasks.map(t => t.id == task.id ? task : t) this.masterTask.subtasks = this.masterTask.subtasks.map(t => t.id == task.id ? task : t)
@@ -185,16 +182,6 @@ export default {
this.masterTask.team_id = this.masterTask.team.id this.masterTask.team_id = this.masterTask.team.id
this.masterTask.project_id = project.id this.masterTask.project_id = project.id
this.masterTask.project_code = project.code 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 { } else {
this.masterTask = { employees: [], duration: 0 } 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> <template>
<v-dialog v-model="visible" max-width="650"> <v-dialog v-model="visible" max-width="750">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <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> <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> <template>
<v-dialog v-model="visible" max-width="650"> <v-dialog v-model="visible" max-width="750">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <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> <h1>Ste si istý, že chcete zmazať task z masterTasku <b>{{ task.project_code }}</b>?</h1>
@@ -43,7 +43,9 @@ export default {
async deleteTask() { async deleteTask() {
try { try {
await this.$axios.delete(`v1/subtasks/${this.task.id}`) 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() this.closeModal()
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
@@ -1,5 +1,5 @@
<template> <template>
<v-dialog v-model="show" max-width="650"> <v-dialog v-model="show" max-width="750">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<h1>Prosím zadajte odpracovaný čas</h1> <h1>Prosím zadajte odpracovaný čas</h1>
+74 -371
View File
@@ -1,359 +1,51 @@
<template> <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"> <W-loadable :content="task" class="h-100">
<v-card v-if="editMode" height="100%" class="d-flex flex-column scrolling"> <v-card v-if="editMode" height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center modal-header"> <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> <v-icon @click.stop="closeModal()" icon="mdi-close" size="large"></v-icon>
</div> </div>
<v-card-actions class="d-flex flex-column modal-content"> <v-card-actions class="d-flex flex-column modal-content">
<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>
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 <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" />
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"
/>
<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>
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 <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>
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>
<div class="w-100 modal-input" :class="{ error: v$.task.description.$errors.length }"> <div class="w-100 modal-input" :class="{ error: v$.task.description.$errors.length }">
<QuillEditor <QuillEditor ref="description" v-model="task.description" placeholder="Popis" theme="snow" class="rich-text-editor" @input="task.description = $event.srcElement.innerHTML" />
ref="description"
v-model="task.description"
placeholder="Popis"
theme="snow"
class="rich-text-editor"
@input="task.description = $event.srcElement.innerHTML"
/>
</div> </div>
<Z-files-upload class="mt-15 files" :files="task.files" @files-uploaded="filesUploaded" /> <Z-files-upload class="mt-15 files" :files="task.files" @files-uploaded="filesUploaded" />
</v-card-actions> </v-card-actions>
<v-btn class="button primary" @click="save()">{{ <v-btn class="button primary" @click="save()">{{ task.id ? "UPRAVIŤ" : "VYTVORIŤ" }}</v-btn>
task.id ? 'UPRAVIŤ' : 'VYTVORIŤ'
}}</v-btn>
</v-card> </v-card>
<v-card v-else height="100%" class="d-flex flex-column scrolling"> <v-card v-else height="100%" class="d-flex flex-column scrolling">
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<p>{{ task.project_code }}</p> <p>{{ task.project_code }}</p>
<v-icon <v-icon @click.stop="closeModal()" icon="mdi-close" size="large" class="pb-5"></v-icon>
@click.stop="closeModal()"
icon="mdi-close"
size="large"
class="pb-5"
></v-icon>
</div> </div>
<div <div class="d-flex justify-space-between align-center modal-header border-bottom pb-4">
class="d-flex justify-space-between align-center modal-header border-bottom pb-4"
>
<h1>{{ task.title }}</h1> <h1>{{ task.title }}</h1>
<div class="d-flex justify-start"> <div class="d-flex justify-start">
<img <img class="icon" :src="require('@/plugins/tm@overview/_img/pencil-icon.svg')" alt="pencil-icon" @click.stop="editMode = !editMode" />
class="icon" <img class="icon trash" :src="require('@/plugins/tm@overview/_img/trash-icon.svg')" alt="trash-icon" @click.stop="openDeleteTaskModal(task)" />
: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>
</div> </div>
<v-card-actions class="d-flex flex-column modal-content"> <v-card-actions class="d-flex flex-column modal-content">
<div class="w-100"> <div class="w-100">
<v-row no-gutters class="align-center"> <Z-task-readonly-info :task="task" />
<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>
<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"> <div class="d-flex justify-start align-center mt-8">
<v-btn class="done-btn" @click.stop="task.is_finished = 1; save()" <v-btn class="done-btn" @click.stop="task.is_finished = 1; save()">Dokončené</v-btn>
>Dokončené</v-btn <v-btn class="button secondary" @click="save()">Uložiť zmeny</v-btn>
>
<v-btn class="button secondary" @click="save()"
>Uložiť zmeny</v-btn
>
</div> </div>
</div> </div>
</v-card-actions> </v-card-actions>
@@ -363,26 +55,27 @@
</template> </template>
<script> <script>
import Datepicker from '@vuepic/vue-datepicker' import ZFilesUpload from "@/plugins/tm@_components/z-files-upload.vue"
import ZFilesUpload from '@/plugins/tm@_components/z-files-upload.vue' import useVuelidate from "@vuelidate/core"
import useVuelidate from '@vuelidate/core' import { required } from "@vuelidate/validators"
import { required } from '@vuelidate/validators' import errorUtils from "@/plugins/lib@auth/_utils/error.utils"
import errorUtils from '@/plugins/lib@auth/_utils/error.utils' import { QuillEditor } from "@vueup/vue-quill"
import moment from 'moment' import VuetifyFixFocusMixin from "@/plugins/tm@modal/_mixins/vuetify-fix-focus"
import { QuillEditor } from '@vueup/vue-quill' import SelectsMixin from "../master-task/_mixins/selects.mixin"
import VuetifyFixFocusMixin from '@/plugins/tm@modal/_mixins/vuetify-fix-focus' import * as FileSaver from "file-saver"
import SelectsMixin from '../master-task/_mixins/selects.mixin' import tmUtils from "@/plugins/tm@_utils/tm.utils"
import * as FileSaver from 'file-saver' import "@vueup/vue-quill/dist/vue-quill.snow.css"
import tmUtils from '@/plugins/tm@_utils/tm.utils' import ZAutocomplete from "@/plugins/tm@_components/z-autocomplete.vue"
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 { export default {
emits: ['close', 'refresh'], emits: ["close", "refresh"],
mixins: [VuetifyFixFocusMixin, SelectsMixin], mixins: [VuetifyFixFocusMixin, SelectsMixin],
components: { components: {
Datepicker, ZTaskReadonlyInfo,
ZFilesUpload, ZFilesUpload,
QuillEditor, QuillEditor,
ZAutocomplete ZAutocomplete
@@ -397,8 +90,6 @@ export default {
errorUtils, errorUtils,
v$: useVuelidate(), v$: useVuelidate(),
preventInputFocus: false, preventInputFocus: false,
masterTasks: [],
employees: null,
masterTaskIdSelect: null, masterTaskIdSelect: null,
showDatePicker: false, showDatePicker: false,
selectedNewDate: [] selectedNewDate: []
@@ -406,12 +97,16 @@ export default {
}, },
computed: { computed: {
filteredEmployees() { ...mapGetters('tm', [
if (!this.employees || !this.task?.master_task) 'mastertasks',
return [] 'employees'
]),
const selectedEmployees = this.task.master_task.employees?.map(employee => employee.id) || [] filteredEmployees() {
const filteredEmployees = this.employees.filter(employee => !selectedEmployees.includes(employee.id)) if (!this.employees || !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) return tmUtils.enrichEmployeesByFullName(filteredEmployees)
} }
@@ -431,35 +126,35 @@ export default {
}, },
mounted() { 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() if (task?.id == this.task?.id) this.closeModal()
}) })
}, },
methods: { methods: {
async showModal(task, editMode = false, masterTask = {}) { async showModal(task, editMode = false, masterTask = {}) {
this.v$.$reset()
this.visible = true
this.task = null this.task = null
this.masterTaskIdSelect = null this.visible = true
try { try {
this.masterTasks = await this.$axios.get_auth_data('v1/mastertasks')
this.employees = await this.$axios.get_auth_data('v1/employees')
if (task?.id) { if (task?.id) {
const t = await this.$axios.get_auth_data(`v1/subtasks/${task.id}`) const fetchedTask = await this.$axios.get_auth_data(`v1/subtasks/${task.id}`)
t.master_task = this.masterTasks.find(mt => mt.id == t.master_task_id) this.task = this._enrichTask(fetchedTask)
t.employee = this.employees.find(employee => employee.id == t.employee_id)
this.masterTaskIdSelect = t.master_task.id
this.task = t
} else { } else {
this.task = {master_task: masterTask} this.task = { master_task: masterTask }
this.masterTaskIdSelect = masterTask.id this.masterTaskIdSelect = masterTask.id
} }
this.editMode = (editMode || !this.task.id) this.editMode = editMode || !this.task.id
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
} }
@@ -467,14 +162,14 @@ export default {
closeModal() { closeModal() {
this.visible = false this.visible = false
this.masterTaskIdSelect = null
this.$nextTick(() => this.v$.$reset())
}, },
async save() { 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 { try {
if (this.task?.id) { if (this.task?.id) {
@@ -484,7 +179,7 @@ export default {
this.task.id = subtask.id this.task.id = subtask.id
} }
this.$events.emit('on-task-edited', this.task) this.$events.emit("on-task-edited", this.task)
this.closeModal() this.closeModal()
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
@@ -504,9 +199,9 @@ export default {
async downloadFile(id) { async downloadFile(id) {
try { 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) FileSaver.saveAs(file.files[id].path, file.files[id].name)
} catch(error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
} }
}, },
@@ -516,30 +211,38 @@ export default {
}, },
openDeleteTaskModal(task) { openDeleteTaskModal(task) {
this.$events.emit('show-delete-task-modal', { task }) this.$events.emit("show-delete-task-modal", { task })
}, },
filesUploaded(files) { filesUploaded(files) {
this.task.files = files this.task.files = files
}, },
_enrichTask(task) {
task.master_task = this.mastertasks.find((masterTask) => masterTask.id == task.master_task_id)
task.employee = this.employees.find((employee) => employee.id == task.employee_id)
this.masterTaskIdSelect = task.master_task.id
return task
}
}, },
watch: { watch: {
masterTaskIdSelect(value) { masterTaskIdSelect(value) {
if (!value || !this.task) return if (!value || !this.task) return
this.task.master_task = this.masterTasks.find(masterTask => masterTask.id == value) this.task.master_task = this.mastertasks.find((masterTask) => masterTask.id == value)
this.task.project_code = this.task.master_task.project_code this.task.project_code = this.task.master_task.project_code
}, },
'task.employee_id'(value) { "task.employee_id"(value) {
if (!value) return if (!value) return
this.task.employee = this.employees.find(employee => employee.id == value) this.task.employee = this.employees.find((employee) => employee.id == value)
}, },
editMode() { editMode() {
this.$nextTick(() => { this.$nextTick(() => {
this.$refs['description']?.setHTML(this.task.description || '') this.$refs["description"]?.setHTML(this.task.description || "")
}) })
} }
} }
@@ -1,5 +1,5 @@
<template> <template>
<v-dialog v-model="visible" max-width="650"> <v-dialog v-model="visible" max-width="750">
<v-card> <v-card>
<div class="d-flex justify-space-between align-center"> <div class="d-flex justify-space-between align-center">
<h1> <h1>
@@ -42,6 +42,7 @@ export default {
await this.$axios.delete_auth(`v1/teams/${this.team.id}`) await this.$axios.delete_auth(`v1/teams/${this.team.id}`)
this.visible = false this.visible = false
this.$emit('update', this.team) this.$emit('update', this.team)
this.$store.commit('tm/delete_team', this.team)
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
} }
+13 -9
View File
@@ -1,5 +1,5 @@
<template> <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"> <W-loadable :content="team && allEmployees" class="h-100">
<v-card height="100%" class="d-flex flex-column"> <v-card height="100%" class="d-flex flex-column">
<div class="d-flex justify-space-between align-center modal-header"> <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 { required } from '@vuelidate/validators'
import errorUtils from '@/plugins/lib@auth/_utils/error.utils' import errorUtils from '@/plugins/lib@auth/_utils/error.utils'
import tmUtils from '@/plugins/tm@_utils/tm.utils' import tmUtils from '@/plugins/tm@_utils/tm.utils'
import axios from 'axios'
const atLeastOne = (value) => value.length const atLeastOne = (value) => value.length
@@ -109,7 +108,7 @@ export default {
if (team?.id) { if (team?.id) {
const t = await this.$axios.get_auth_data(`v1/teams/${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 { } else {
this.team = {employees: []} this.team = {employees: []}
} }
@@ -126,16 +125,15 @@ export default {
async save() { async save() {
if (!(await this.v$.$validate())) return if (!(await this.v$.$validate())) return
try { try {
if (this.team.id) { if (this.team.id) {
// await this.$axios.put_auth_data(`v1/teams/${this.team.id}`, this.team) const { team } = await this.$axios.put_auth(`v1/teams/${this.team.id}`, this.team)
await axios.put(`v1/teams/${this.team.id}`, {data: 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() this.closeModal()
} catch (error) { } catch (error) {
this.$toast.error(error) this.$toast.error(error)
@@ -145,6 +143,11 @@ export default {
employeeSelected(selectedOption) { employeeSelected(selectedOption) {
const employee = this.allEmployees.find((empl) => empl.id == selectedOption.key) const employee = this.allEmployees.find((empl) => empl.id == selectedOption.key)
this.team.employees.push(employee) 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( const selectedEmployeesIds = this.team.employees.map(
(employee) => employee.id (employee) => employee.id
) )
const employees = this.allEmployees.filter( const employees = this.allEmployees.filter(
(employee) => !selectedEmployeesIds.includes(employee.id) (employee) => !selectedEmployeesIds.includes(employee.id)
) )
@@ -1,14 +1,15 @@
<template> <template>
<v-row class="px-10 py-8 subheader"> <v-row class="px-10 py-8 subheader">
<v-select <v-select
v-model="selectedEmployee" v-model="selectedEmployeeId"
class="subheader-input" class="subheader-input"
variant="outlined" variant="outlined"
label="Zamestanec" label="Zamestanec"
:items="[null, ...allEmployees]" :items="[null, ...enrichedEmployees]"
item-title="fullName" item-title="_fullName"
item-value="id" item-value="id"
></v-select> ></v-select>
<v-text-field <v-text-field
v-model="searchQuery" v-model="searchQuery"
class="subheader-input" class="subheader-input"
@@ -22,47 +23,37 @@
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'
export default { export default {
emits: ['seach', 'employee-selected'],
data() { data() {
return { return {
selectedEmployee: null, selectedEmployeeId: null,
employees: [],
searchQuery: '' searchQuery: ''
} }
}, },
computed: { mounted() {
allEmployees() { this.selectedEmployeeId = this.$store.getters['auth/user'].id
const employees = this.employees.map((employee) => {
return `${employee.name} ${employee.surname}`
})
return employees
}
}, },
async created() { computed: {
try { ...mapGetters('tm', ['enrichedEmployees']),
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)
}
}, },
methods: { methods: {
onInput() { onInput() {
this.$emit('queryChange', this.searchQuery) this.$emit('query-changed', this.searchQuery)
} }
}, },
// watch: { watch: {
// selectedEmployee() { selectedEmployeeId() {
// this.search() this.$emit('employee-selected', this.selectedEmployeeId)
// } }
// } }
} }
</script> </script>
@@ -13,9 +13,9 @@
<tbody> <tbody>
<tr class="cursor-pointer" v-for="task in tasks" :key="task.id" @click.stop="editTask(task, false)"> <tr class="cursor-pointer" v-for="task in tasks" :key="task.id" @click.stop="editTask(task, false)">
<td>{{ task.project_code }}</td> <td>{{ task.project_code }}</td>
<td>{{ task.title }}</td> <td>{{ task.master_task }}</td>
<td>{{ task.duration / 1000 / 60 / 60 }} h</td> <td>{{ task.duration }} h</td>
<td>{{ (task.remaining_time / 1000 / 60 / 60).toFixed(2) }} h</td> <td>{{ task.duration - task.spent_time }} h</td>
<td class="task-action"> <td class="task-action">
<div class="d-flex justify-start"> <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)" /> <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> <template>
<div> <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'"> <W-loadable :content="filteredOngoingTasks" :emptyMessage="searchQuery.length ? 'Neboli nájdené žiadne úlohy' : 'Nenašli sa žiadne záznamy'">
<A-ongoing-table :tasks="filteredOngoingTasks" /> <A-ongoing-table :tasks="filteredOngoingTasks" />
@@ -12,6 +12,7 @@
<script> <script>
import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue' import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue'
import { mapGetters } from 'vuex'
import AOngoingTable from './_components/a-ongoing-table.vue' import AOngoingTable from './_components/a-ongoing-table.vue'
export default { export default {
@@ -22,47 +23,36 @@ export default {
data() { data() {
return { return {
searchQuery: '', searchQuery: ''
ongoingTasks: null
} }
}, },
ionViewWillEnter() {
this.$store.dispatch('tm/loadOngoingSubtasks')
},
computed: { computed: {
...mapGetters('tm', ['ongoingSubtasks']),
filteredOngoingTasks() { filteredOngoingTasks() {
if (!this.ongoingTasks) if (!this.ongoingSubtasks)
return null return null
if (!this.searchQuery) return this.ongoingSubtasks.filter(subtask => {
return this.ongoingTasks return this.filterParams(this.searchQuery, subtask.master_task) || this.filterParams(this.searchQuery, subtask.project_code)
return this.ongoingTasks.filter((task) => {
return this.filterParams(this.searchQuery, task.title) || this.filterParams(this.searchQuery, task.project_code)
}) })
} }
}, },
async created() {
await this._loadData()
},
mounted() {
this.$events.on('on-task-deleted', () => this._loadData())
},
methods: { 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) { onSearch(searchQuery) {
this.searchQuery = searchQuery this.searchQuery = searchQuery
}, },
onEmployeeSelected(id) {
this.$store.dispatch('tm/loadOngoingSubtasks', id)
},
filterParams(searchQuery, row) { filterParams(searchQuery, row) {
return row.toLowerCase().includes(searchQuery.toLowerCase()) return row.toLowerCase().includes(searchQuery.toLowerCase())
} }
@@ -11,9 +11,10 @@
<tbody> <tbody>
<tr class="cursor-pointer" v-for="task in tasks" :key="task.id" @click.stop="editTask(task, false)"> <tr class="cursor-pointer" v-for="task in tasks" :key="task.id" @click.stop="editTask(task, false)">
<td>{{ task.project_code }}</td> <td>{{ task.project_code }}</td>
<td>{{ task.title }}</td> <td>{{ task.master_task }}</td>
<td>{{ (task.duration / 1000 / 60 / 60).toFixed(2) }} h</td> <td>{{ task.spent_time }} h</td>
<td class="task-action"> <td class="task-action">
<div class="d-flex justify-start"> <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)"> <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> <template>
<div> <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'"> <W-loadable :content="filteredUnscheduledTasks" :emptyMessage="searchQuery.length ? 'Neboli nájdené žiadne úlohy' : 'Nenašli sa žiadne záznamy'">
<A-unscheduled-table :tasks="filteredUnscheduledTasks" /> <A-unscheduled-table :tasks="filteredUnscheduledTasks" />
@@ -12,6 +12,7 @@
<script> <script>
import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue' import ZTasksSearch from '@/plugins/tm@overview/_components/z-tasks-search.vue'
import { mapGetters } from 'vuex'
import AUnscheduledTable from './_components/a-unscheduled-table.vue' import AUnscheduledTable from './_components/a-unscheduled-table.vue'
export default { export default {
components: { components: {
@@ -21,47 +22,36 @@ export default {
data() { data() {
return { return {
searchQuery: '', searchQuery: ''
unscheduledTasks: null
} }
}, },
ionViewWillEnter() {
this.$store.dispatch('tm/loadUnscheduledSubtasks')
},
computed: { computed: {
...mapGetters('tm', ['unscheduledSubtasks']),
filteredUnscheduledTasks() { filteredUnscheduledTasks() {
if (!this.unscheduledTasks) if (!this.unscheduledSubtasks)
return null return null
if (!this.searchQuery) return this.unscheduledSubtasks.filter(subtask => {
return this.unscheduledTasks return this.filterParams(this.searchQuery, subtask.master_task) || this.filterParams(this.searchQuery, subtask.project_code)
return this.unscheduledTasks.filter((task) => {
return this.filterParams(this.searchQuery, task.title) || this.filterParams(this.searchQuery, task.project_code)
}) })
} }
}, },
async created() {
await this._loadData()
},
mounted() {
this.$events.on('on-task-deleted', () => this._loadData())
},
methods: { 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) { onSearch(searchQuery) {
this.searchQuery = searchQuery this.searchQuery = searchQuery
}, },
onEmployeeSelected(id) {
this.$store.dispatch('tm/loadUnscheduledSubtasks', id)
},
filterParams(searchQuery, row) { filterParams(searchQuery, row) {
return row.toLowerCase().includes(searchQuery.toLowerCase()) return row.toLowerCase().includes(searchQuery.toLowerCase())
} }
+4 -25
View File
@@ -1,10 +1,11 @@
<template> <template>
<W-loadable :content="teams"> <W-loadable :content="teams">
<A-teams-table :teams="teams" @reloadData="_loadData" /> <A-teams-table :teams="teams" />
</W-loadable> </W-loadable>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex';
import ATeamsTable from './_components/a-teams-table.vue' import ATeamsTable from './_components/a-teams-table.vue'
export default { export default {
@@ -12,30 +13,8 @@ export default {
ATeamsTable ATeamsTable
}, },
data() { computed: {
return { ...mapGetters('tm', ['teams'])
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}
}
}
} }
} }
</script> </script>
@@ -27,6 +27,7 @@ export default {
}, },
async mounted() { async mounted() {
if (this.content) return
await this._startLoading() await this._startLoading()
}, },
@@ -36,7 +37,7 @@ export default {
}, },
_stopLoading() { _stopLoading() {
this.loader.hide() this.loader?.hide()
} }
}, },
+4 -6
View File
@@ -3,15 +3,14 @@ import { $t } from '@/plugins/app/_config/main'
export default { export default {
parseAndTranslate(data) { parseAndTranslate(data) {
const errorsArray = this._parseToArrayOfStrings(data) const errorsArray = this._parseToArrayOfStrings(data)
// console.log(errorsArray.map(error => $t(error)))
return errorsArray.join(', ') return errorsArray.join(', ')
// return errorsArray.map(error => $t(error))
}, },
_parseToArrayOfStrings(data) { _parseToArrayOfStrings(data) {
if (data?.data?.message) {
return [$t(data.data.message)]
}
if (typeof data == 'string') { if (typeof data == 'string') {
return [$t(data)] return [$t(data)]
} }
@@ -33,7 +32,6 @@ export default {
if (data && data.data && data.data.error && typeof data.data.error == 'object') { if (data && data.data && data.data.error && typeof data.data.error == 'object') {
return Object.values(data.data.error).map(error => $t(error[0]) + ' ') return Object.values(data.data.error).map(error => $t(error[0]) + ' ')
} }
if (data.statusText) { if (data.statusText) {
@@ -128,7 +128,7 @@ export default {
async put_auth(url: string, params = null, config?: any) { async put_auth(url: string, params = null, config?: any) {
config = this._config(config) config = this._config(config)
return axios.put(url, this._objectToFormData(params), config) return axios.put(url, {data: params}, config)
.then(response => response.data) .then(response => response.data)
.catch(error => Promise.reject(toastUtils.parseAndTranslate(error))) .catch(error => Promise.reject(toastUtils.parseAndTranslate(error)))
}, },